From 74f4c590049b417fb3b9116b0a1ad8de1d248203 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 21 Aug 2021 19:37:28 -0500 Subject: [PATCH] Auth: refactor OAuth token actions --- app/soapbox/actions/auth.js | 67 ++++++++------------------ app/soapbox/actions/oauth.js | 35 ++++++++++++++ app/soapbox/actions/security.js | 31 ++++++++++++ app/soapbox/features/security/index.js | 5 +- app/soapbox/reducers/auth.js | 2 +- app/soapbox/reducers/security.js | 2 +- 6 files changed, 90 insertions(+), 52 deletions(-) create mode 100644 app/soapbox/actions/oauth.js create mode 100644 app/soapbox/actions/security.js diff --git a/app/soapbox/actions/auth.js b/app/soapbox/actions/auth.js index b9a315eec..efcaf830e 100644 --- a/app/soapbox/actions/auth.js +++ b/app/soapbox/actions/auth.js @@ -6,6 +6,7 @@ import { createAccount } from 'soapbox/actions/accounts'; import { fetchMeSuccess, fetchMeFail } from 'soapbox/actions/me'; import { getLoggedInAccount } from 'soapbox/utils/auth'; import { createApp } from 'soapbox/actions/apps'; +import { obtainOAuthToken, revokeOAuthToken } from 'soapbox/actions/oauth'; export const SWITCH_ACCOUNT = 'SWITCH_ACCOUNT'; @@ -34,14 +35,6 @@ export const CHANGE_PASSWORD_REQUEST = 'CHANGE_PASSWORD_REQUEST'; export const CHANGE_PASSWORD_SUCCESS = 'CHANGE_PASSWORD_SUCCESS'; export const CHANGE_PASSWORD_FAIL = 'CHANGE_PASSWORD_FAIL'; -export const FETCH_TOKENS_REQUEST = 'FETCH_TOKENS_REQUEST'; -export const FETCH_TOKENS_SUCCESS = 'FETCH_TOKENS_SUCCESS'; -export const FETCH_TOKENS_FAIL = 'FETCH_TOKENS_FAIL'; - -export const REVOKE_TOKEN_REQUEST = 'REVOKE_TOKEN_REQUEST'; -export const REVOKE_TOKEN_SUCCESS = 'REVOKE_TOKEN_SUCCESS'; -export const REVOKE_TOKEN_FAIL = 'REVOKE_TOKEN_FAIL'; - const messages = defineMessages({ loggedOut: { id: 'auth.logged_out', defaultMessage: 'Logged out.' }, invalidCredentials: { id: 'auth.invalid_credentials', defaultMessage: 'Wrong username or password' }, @@ -80,13 +73,15 @@ function createAppToken() { return (dispatch, getState) => { const app = getState().getIn(['auth', 'app']); - return api(getState, 'app').post('/oauth/token', { + const params = { client_id: app.get('client_id'), client_secret: app.get('client_secret'), redirect_uri: 'urn:ietf:wg:oauth:2.0:oob', grant_type: 'client_credentials', - }).then(response => { - return dispatch(authAppAuthorized(response.data)); + }; + + return dispatch(obtainOAuthToken(params)).then(token => { + return dispatch({ type: AUTH_APP_AUTHORIZED, app, token }); }); }; } @@ -94,14 +89,17 @@ function createAppToken() { function createUserToken(username, password) { return (dispatch, getState) => { const app = getState().getIn(['auth', 'app']); - return api(getState, 'app').post('/oauth/token', { + + const params = { client_id: app.get('client_id'), client_secret: app.get('client_secret'), redirect_uri: 'urn:ietf:wg:oauth:2.0:oob', grant_type: 'password', username: username, password: password, - }).then(({ data: token }) => { + }; + + return dispatch(obtainOAuthToken(params)).then(token => { dispatch(authLoggedIn(token)); return token; }); @@ -115,14 +113,16 @@ export function refreshUserToken() { if (!refreshToken) return dispatch(noOp()); - return api(getState, 'app').post('/oauth/token', { + const params = { client_id: app.get('client_id'), client_secret: app.get('client_secret'), refresh_token: refreshToken, redirect_uri: 'urn:ietf:wg:oauth:2.0:oob', grant_type: 'refresh_token', - }).then(response => { - dispatch(authLoggedIn(response.data)); + }; + + return dispatch(obtainOAuthToken(params)).then(token => { + dispatch(authLoggedIn(token)); }); }; } @@ -182,11 +182,13 @@ export function logOut(intl) { const state = getState(); const account = getLoggedInAccount(state); - return api(getState).post('/oauth/revoke', { + const params = { client_id: state.getIn(['auth', 'app', 'client_id']), client_secret: state.getIn(['auth', 'app', 'client_secret']), token: state.getIn(['auth', 'users', account.get('url'), 'access_token']), - }).finally(() => { + }; + + return dispatch(revokeOAuthToken(params)).finally(() => { dispatch({ type: AUTH_LOGGED_OUT, account }); dispatch(snackbar.success(intl.formatMessage(messages.loggedOut))); }); @@ -299,35 +301,6 @@ export function changePassword(oldPassword, newPassword, confirmation) { }; } -export function fetchOAuthTokens() { - return (dispatch, getState) => { - dispatch({ type: FETCH_TOKENS_REQUEST }); - return api(getState).get('/api/oauth_tokens.json').then(response => { - dispatch({ type: FETCH_TOKENS_SUCCESS, tokens: response.data }); - }).catch(error => { - dispatch({ type: FETCH_TOKENS_FAIL }); - }); - }; -} - -export function revokeOAuthToken(id) { - return (dispatch, getState) => { - dispatch({ type: REVOKE_TOKEN_REQUEST, id }); - return api(getState).delete(`/api/oauth_tokens/${id}`).then(response => { - dispatch({ type: REVOKE_TOKEN_SUCCESS, id }); - }).catch(error => { - dispatch({ type: REVOKE_TOKEN_FAIL, id }); - }); - }; -} - -export function authAppAuthorized(app) { - return { - type: AUTH_APP_AUTHORIZED, - app, - }; -} - export function authLoggedIn(token) { return { type: AUTH_LOGGED_IN, diff --git a/app/soapbox/actions/oauth.js b/app/soapbox/actions/oauth.js new file mode 100644 index 000000000..a3f83960f --- /dev/null +++ b/app/soapbox/actions/oauth.js @@ -0,0 +1,35 @@ +import { baseClient } from '../api'; + +export const OAUTH_TOKEN_CREATE_REQUEST = 'OAUTH_TOKEN_CREATE_REQUEST'; +export const OAUTH_TOKEN_CREATE_SUCCESS = 'OAUTH_TOKEN_CREATE_SUCCESS'; +export const OAUTH_TOKEN_CREATE_FAIL = 'OAUTH_TOKEN_CREATE_FAIL'; + +export const OAUTH_TOKEN_REVOKE_REQUEST = 'OAUTH_TOKEN_REVOKE_REQUEST'; +export const OAUTH_TOKEN_REVOKE_SUCCESS = 'OAUTH_TOKEN_REVOKE_SUCCESS'; +export const OAUTH_TOKEN_REVOKE_FAIL = 'OAUTH_TOKEN_REVOKE_FAIL'; + +export function obtainOAuthToken(params) { + return (dispatch, getState) => { + dispatch({ type: OAUTH_TOKEN_CREATE_REQUEST, params }); + return baseClient().post('/oauth/token', params).then(({ data: token }) => { + dispatch({ type: OAUTH_TOKEN_CREATE_SUCCESS, params, token }); + return token; + }).catch(error => { + dispatch({ type: OAUTH_TOKEN_CREATE_FAIL, params, error }); + throw error; + }); + }; +} + +export function revokeOAuthToken(params) { + return (dispatch, getState) => { + dispatch({ type: OAUTH_TOKEN_REVOKE_REQUEST, params }); + return baseClient().post('/oauth/revoke', params).then(({ data }) => { + dispatch({ type: OAUTH_TOKEN_REVOKE_SUCCESS, params, data }); + return data; + }).catch(error => { + dispatch({ type: OAUTH_TOKEN_REVOKE_FAIL, params, error }); + throw error; + }); + }; +} diff --git a/app/soapbox/actions/security.js b/app/soapbox/actions/security.js new file mode 100644 index 000000000..abb3f6972 --- /dev/null +++ b/app/soapbox/actions/security.js @@ -0,0 +1,31 @@ +import api from '../api'; + +export const FETCH_TOKENS_REQUEST = 'FETCH_TOKENS_REQUEST'; +export const FETCH_TOKENS_SUCCESS = 'FETCH_TOKENS_SUCCESS'; +export const FETCH_TOKENS_FAIL = 'FETCH_TOKENS_FAIL'; + +export const REVOKE_TOKEN_REQUEST = 'REVOKE_TOKEN_REQUEST'; +export const REVOKE_TOKEN_SUCCESS = 'REVOKE_TOKEN_SUCCESS'; +export const REVOKE_TOKEN_FAIL = 'REVOKE_TOKEN_FAIL'; + +export function fetchOAuthTokens() { + return (dispatch, getState) => { + dispatch({ type: FETCH_TOKENS_REQUEST }); + return api(getState).get('/api/oauth_tokens.json').then(({ data: tokens }) => { + dispatch({ type: FETCH_TOKENS_SUCCESS, tokens }); + }).catch(error => { + dispatch({ type: FETCH_TOKENS_FAIL }); + }); + }; +} + +export function revokeOAuthTokenById(id) { + return (dispatch, getState) => { + dispatch({ type: REVOKE_TOKEN_REQUEST, id }); + return api(getState).delete(`/api/oauth_tokens/${id}`).then(() => { + dispatch({ type: REVOKE_TOKEN_SUCCESS, id }); + }).catch(error => { + dispatch({ type: REVOKE_TOKEN_FAIL, id }); + }); + }; +} diff --git a/app/soapbox/features/security/index.js b/app/soapbox/features/security/index.js index a70bd83cd..1783255e9 100644 --- a/app/soapbox/features/security/index.js +++ b/app/soapbox/features/security/index.js @@ -15,10 +15,9 @@ import { import { changeEmail, changePassword, - fetchOAuthTokens, - revokeOAuthToken, deleteAccount, } from 'soapbox/actions/auth'; +import { fetchOAuthTokens, revokeOAuthTokenById } from 'soapbox/actions/security'; import { fetchUserMfaSettings } from '../../actions/mfa'; import snackbar from 'soapbox/actions/snackbar'; import { changeSetting, getSettings } from 'soapbox/actions/settings'; @@ -306,7 +305,7 @@ class AuthTokenList extends ImmutablePureComponent { handleRevoke = id => { return e => { - this.props.dispatch(revokeOAuthToken(id)); + this.props.dispatch(revokeOAuthTokenById(id)); }; } diff --git a/app/soapbox/reducers/auth.js b/app/soapbox/reducers/auth.js index d332125a7..42aaa586f 100644 --- a/app/soapbox/reducers/auth.js +++ b/app/soapbox/reducers/auth.js @@ -222,7 +222,7 @@ const reducer = (state, action) => { case AUTH_APP_CREATED: return state.set('app', fromJS(action.app)); case AUTH_APP_AUTHORIZED: - return state.update('app', ImmutableMap(), app => app.merge(fromJS(action.app))); + return state.update('app', ImmutableMap(), app => app.merge(fromJS(action.token))); case AUTH_LOGGED_IN: return importToken(state, action.token); case AUTH_LOGGED_OUT: diff --git a/app/soapbox/reducers/security.js b/app/soapbox/reducers/security.js index b50b39801..497f4fb61 100644 --- a/app/soapbox/reducers/security.js +++ b/app/soapbox/reducers/security.js @@ -1,7 +1,7 @@ import { FETCH_TOKENS_SUCCESS, REVOKE_TOKEN_SUCCESS, -} from '../actions/auth'; +} from '../actions/security'; import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable'; const initialState = ImmutableMap({