bigbuffet-rw/app/soapbox/actions/auth.js

272 lines
9 KiB
JavaScript
Raw Normal View History

/**
* Auth: login & registration workflow.
* This file contains abstractions over auth concepts.
* @module soapbox/actions/auth
* @see module:soapbox/actions/apps
* @see module:soapbox/actions/oauth
* @see module:soapbox/actions/security
*/
import { defineMessages } from 'react-intl';
2021-03-31 12:47:54 -07:00
import api, { baseClient } from '../api';
2021-03-23 19:01:50 -07:00
import { importFetchedAccount } from './importer';
import snackbar from 'soapbox/actions/snackbar';
2021-03-26 13:29:15 -07:00
import { createAccount } from 'soapbox/actions/accounts';
import { fetchMeSuccess, fetchMeFail } from 'soapbox/actions/me';
2021-08-21 18:41:29 -07:00
import { getLoggedInAccount, parseBaseURL } from 'soapbox/utils/auth';
2021-08-21 17:05:59 -07:00
import { createApp } from 'soapbox/actions/apps';
2021-08-21 17:37:28 -07:00
import { obtainOAuthToken, revokeOAuthToken } from 'soapbox/actions/oauth';
2021-08-21 20:46:33 -07:00
import sourceCode from 'soapbox/utils/code';
2021-08-22 17:13:09 -07:00
import { getFeatures } from 'soapbox/utils/features';
import { isStandalone } from 'soapbox/utils/state';
2021-10-20 14:27:36 -07:00
import KVStore from 'soapbox/storage/kv_store';
2020-04-04 13:28:57 -07:00
export const SWITCH_ACCOUNT = 'SWITCH_ACCOUNT';
2020-04-05 16:39:22 -07:00
export const AUTH_APP_CREATED = 'AUTH_APP_CREATED';
export const AUTH_APP_AUTHORIZED = 'AUTH_APP_AUTHORIZED';
export const AUTH_LOGGED_IN = 'AUTH_LOGGED_IN';
2020-04-11 12:41:13 -07:00
export const AUTH_LOGGED_OUT = 'AUTH_LOGGED_OUT';
2020-04-05 14:54:51 -07:00
export const VERIFY_CREDENTIALS_REQUEST = 'VERIFY_CREDENTIALS_REQUEST';
export const VERIFY_CREDENTIALS_SUCCESS = 'VERIFY_CREDENTIALS_SUCCESS';
export const VERIFY_CREDENTIALS_FAIL = 'VERIFY_CREDENTIALS_FAIL';
2021-10-20 14:27:36 -07:00
export const AUTH_ACCOUNT_REMEMBER_REQUEST = 'AUTH_ACCOUNT_REMEMBER_REQUEST';
export const AUTH_ACCOUNT_REMEMBER_SUCCESS = 'AUTH_ACCOUNT_REMEMBER_SUCCESS';
export const AUTH_ACCOUNT_REMEMBER_FAIL = 'AUTH_ACCOUNT_REMEMBER_FAIL';
export const messages = defineMessages({
loggedOut: { id: 'auth.logged_out', defaultMessage: 'Logged out.' },
invalidCredentials: { id: 'auth.invalid_credentials', defaultMessage: 'Wrong username or password' },
});
2020-04-29 16:29:41 -07:00
const noOp = () => () => new Promise(f => f());
2020-04-29 12:07:22 -07:00
2021-08-22 17:13:09 -07:00
const getScopes = state => {
const instance = state.get('instance');
const { scopes } = getFeatures(instance);
return scopes;
};
function createAppAndToken() {
return (dispatch, getState) => {
2021-08-21 17:05:59 -07:00
return dispatch(createAuthApp()).then(() => {
return dispatch(createAppToken());
2020-04-29 12:07:22 -07:00
});
};
}
2021-08-21 17:05:59 -07:00
function createAuthApp() {
2020-04-04 13:28:57 -07:00
return (dispatch, getState) => {
2021-08-21 17:05:59 -07:00
const params = {
2021-08-21 20:46:33 -07:00
client_name: sourceCode.displayName,
2020-04-04 13:28:57 -07:00
redirect_uris: 'urn:ietf:wg:oauth:2.0:oob',
2021-08-22 17:13:09 -07:00
scopes: getScopes(getState()),
website: sourceCode.homepage,
2021-08-21 17:05:59 -07:00
};
return dispatch(createApp(params)).then(app => {
return dispatch({ type: AUTH_APP_CREATED, app });
2020-04-29 12:07:22 -07:00
});
};
}
function createAppToken() {
2020-04-29 12:07:22 -07:00
return (dispatch, getState) => {
const app = getState().getIn(['auth', 'app']);
2021-08-21 17:37:28 -07:00
const params = {
2020-04-29 12:07:22 -07:00
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',
2021-08-22 17:13:09 -07:00
scope: getScopes(getState()),
2021-08-21 17:37:28 -07:00
};
return dispatch(obtainOAuthToken(params)).then(token => {
return dispatch({ type: AUTH_APP_AUTHORIZED, app, token });
2020-04-04 13:28:57 -07:00
});
2020-04-14 11:44:40 -07:00
};
2020-04-04 13:28:57 -07:00
}
function createUserToken(username, password) {
2020-04-04 13:28:57 -07:00
return (dispatch, getState) => {
2020-04-05 14:54:51 -07:00
const app = getState().getIn(['auth', 'app']);
2021-08-21 17:37:28 -07:00
const params = {
client_id: app.get('client_id'),
2020-04-05 16:39:22 -07:00
client_secret: app.get('client_secret'),
redirect_uri: 'urn:ietf:wg:oauth:2.0:oob',
grant_type: 'password',
username: username,
password: password,
2021-08-22 17:13:09 -07:00
scope: getScopes(getState()),
2021-08-21 17:37:28 -07:00
};
return dispatch(obtainOAuthToken(params))
.then(token => dispatch(authLoggedIn(token)));
2020-04-29 17:38:24 -07:00
};
}
export function refreshUserToken() {
return (dispatch, getState) => {
const refreshToken = getState().getIn(['auth', 'user', 'refresh_token']);
const app = getState().getIn(['auth', 'app']);
if (!refreshToken) return dispatch(noOp());
2021-08-21 17:37:28 -07:00
const params = {
2020-04-29 17:38:24 -07:00
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',
2021-08-22 17:13:09 -07:00
scope: getScopes(getState()),
2021-08-21 17:37:28 -07:00
};
return dispatch(obtainOAuthToken(params))
.then(token => dispatch(authLoggedIn(token)));
};
}
2020-08-07 13:17:13 -07:00
export function otpVerify(code, mfa_token) {
return (dispatch, getState) => {
const app = getState().getIn(['auth', 'app']);
return api(getState, 'app').post('/oauth/mfa/challenge', {
client_id: app.get('client_id'),
client_secret: app.get('client_secret'),
mfa_token: mfa_token,
code: code,
challenge_type: 'totp',
redirect_uri: 'urn:ietf:wg:oauth:2.0:oob',
}).then(({ data: token }) => dispatch(authLoggedIn(token)));
2020-08-07 13:17:13 -07:00
};
}
2021-08-21 18:41:29 -07:00
export function verifyCredentials(token, accountUrl) {
const baseURL = parseBaseURL(accountUrl);
return (dispatch, getState) => {
dispatch({ type: VERIFY_CREDENTIALS_REQUEST, token });
2021-08-21 18:41:29 -07:00
return baseClient(token, baseURL).get('/api/v1/accounts/verify_credentials').then(({ data: account }) => {
2021-03-23 19:01:50 -07:00
dispatch(importFetchedAccount(account));
dispatch({ type: VERIFY_CREDENTIALS_SUCCESS, token, account });
2021-05-09 08:37:49 -07:00
if (account.id === getState().get('me')) dispatch(fetchMeSuccess(account));
return account;
}).catch(error => {
2021-05-09 08:37:49 -07:00
if (getState().get('me') === null) dispatch(fetchMeFail(error));
2021-10-19 07:32:15 -07:00
dispatch({ type: VERIFY_CREDENTIALS_FAIL, token, error, skipAlert: true });
});
};
}
2021-10-20 14:27:36 -07:00
export function rememberAuthAccount(accountUrl) {
return (dispatch, getState) => {
dispatch({ type: AUTH_ACCOUNT_REMEMBER_REQUEST, accountUrl });
return KVStore.getItemOrError(`authAccount:${accountUrl}`).then(account => {
dispatch(importFetchedAccount(account));
dispatch({ type: AUTH_ACCOUNT_REMEMBER_SUCCESS, account, accountUrl });
if (account.id === getState().get('me')) dispatch(fetchMeSuccess(account));
return account;
}).catch(error => {
dispatch({ type: AUTH_ACCOUNT_REMEMBER_FAIL, error, accountUrl, skipAlert: true });
});
};
}
export function loadCredentials(token, accountUrl) {
return (dispatch, getState) => {
return dispatch(rememberAuthAccount(accountUrl)).finally(() => {
return dispatch(verifyCredentials(token, accountUrl));
});
};
}
export function logIn(intl, username, password) {
return (dispatch, getState) => {
return dispatch(createAppAndToken()).then(() => {
return dispatch(createUserToken(username, password));
}).catch(error => {
2020-08-07 13:17:13 -07:00
if (error.response.data.error === 'mfa_required') {
// If MFA is required, throw the error and handle it in the component.
2020-08-07 13:17:13 -07:00
throw error;
} else if (error.response.data.error === 'invalid_grant') {
// Mastodon returns this user-unfriendly error as a catch-all
// for everything from "bad request" to "wrong password".
// Assume our code is correct and it's a wrong password.
dispatch(snackbar.error(intl.formatMessage(messages.invalidCredentials)));
} else if (error.response.data.error) {
// If the backend returns an error, display it.
2020-09-29 21:12:33 -07:00
dispatch(snackbar.error(error.response.data.error));
2020-08-07 13:17:13 -07:00
} else {
// Return "wrong password" message.
dispatch(snackbar.error(intl.formatMessage(messages.invalidCredentials)));
2020-08-07 13:17:13 -07:00
}
2020-04-11 12:41:13 -07:00
throw error;
2020-04-04 13:28:57 -07:00
});
2020-04-14 11:44:40 -07:00
};
2020-04-04 13:28:57 -07:00
}
2020-04-05 14:54:51 -07:00
export function logOut(intl) {
2020-04-11 12:41:13 -07:00
return (dispatch, getState) => {
const state = getState();
const account = getLoggedInAccount(state);
const standalone = isStandalone(state);
2021-08-21 17:37:28 -07:00
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']),
2021-08-21 17:37:28 -07:00
};
return dispatch(revokeOAuthToken(params)).finally(() => {
dispatch({ type: AUTH_LOGGED_OUT, account, standalone });
dispatch(snackbar.success(intl.formatMessage(messages.loggedOut)));
});
2020-04-11 12:41:13 -07:00
};
}
export function switchAccount(accountId, background = false) {
return (dispatch, getState) => {
const account = getState().getIn(['accounts', accountId]);
dispatch({ type: SWITCH_ACCOUNT, account, background });
};
}
2021-03-23 19:01:50 -07:00
export function fetchOwnAccounts() {
2021-03-25 11:47:01 -07:00
return (dispatch, getState) => {
2021-03-23 19:01:50 -07:00
const state = getState();
2021-03-23 22:05:06 -07:00
state.getIn(['auth', 'users']).forEach(user => {
const account = state.getIn(['accounts', user.get('id')]);
2021-03-23 19:01:50 -07:00
if (!account) {
2021-08-21 18:41:29 -07:00
dispatch(verifyCredentials(user.get('access_token'), user.get('url')));
2021-03-23 19:01:50 -07:00
}
});
2021-03-25 11:47:01 -07:00
};
2021-03-23 19:01:50 -07:00
}
2020-04-23 16:41:20 -07:00
export function register(params) {
return (dispatch, getState) => {
params.fullname = params.username;
2021-03-26 13:29:15 -07:00
return dispatch(createAppAndToken())
.then(() => dispatch(createAccount(params)))
.then(({ token }) => dispatch(authLoggedIn(token)));
2020-04-23 16:41:20 -07:00
};
}
2020-04-23 18:48:25 -07:00
export function fetchCaptcha() {
return (dispatch, getState) => {
return api(getState).get('/api/pleroma/captcha');
};
}
2021-03-23 19:52:08 -07:00
export function authLoggedIn(token) {
return (dispatch, getState) => {
dispatch({ type: AUTH_LOGGED_IN, token });
return token;
2020-04-05 14:54:51 -07:00
};
}