2020-01-18 02:07:52 +05:30
|
|
|
import { Action as ReduxAction } from 'redux';
|
2019-12-08 00:32:00 +05:30
|
|
|
import { browserHistory } from 'app/services/history';
|
|
|
|
import logger from 'app/services/logger';
|
|
|
|
import localStorage from 'app/services/localStorage';
|
2020-01-18 02:07:52 +05:30
|
|
|
import * as loader from 'app/services/loader';
|
2019-12-08 00:32:00 +05:30
|
|
|
import history from 'app/services/history';
|
2019-11-27 14:33:32 +05:30
|
|
|
import {
|
|
|
|
updateUser,
|
|
|
|
acceptRules as userAcceptRules,
|
2019-12-08 00:32:00 +05:30
|
|
|
} from 'app/components/user/actions';
|
|
|
|
import { authenticate, logoutAll } from 'app/components/accounts/actions';
|
|
|
|
import { getActiveAccount } from 'app/components/accounts/reducer';
|
2019-01-28 00:42:58 +05:30
|
|
|
import {
|
2019-11-27 14:33:32 +05:30
|
|
|
login as loginEndpoint,
|
|
|
|
forgotPassword as forgotPasswordEndpoint,
|
|
|
|
recoverPassword as recoverPasswordEndpoint,
|
2019-12-07 16:58:52 +05:30
|
|
|
OAuthResponse,
|
2019-12-08 00:32:00 +05:30
|
|
|
} from 'app/services/api/authentication';
|
2020-01-18 02:07:52 +05:30
|
|
|
import oauth, { OauthData, Scope } from 'app/services/api/oauth';
|
|
|
|
import {
|
|
|
|
register as registerEndpoint,
|
|
|
|
activate as activateEndpoint,
|
|
|
|
resendActivation as resendActivationEndpoint,
|
|
|
|
} from 'app/services/api/signup';
|
2019-12-08 00:32:00 +05:30
|
|
|
import dispatchBsod from 'app/components/ui/bsod/dispatchBsod';
|
|
|
|
import { create as createPopup } from 'app/components/ui/popup/actions';
|
|
|
|
import ContactForm from 'app/components/contact/ContactForm';
|
2019-12-26 17:48:58 +05:30
|
|
|
import { Account } from 'app/components/accounts/reducer';
|
2019-12-08 00:32:00 +05:30
|
|
|
import { ThunkAction, Dispatch } from 'app/reducers';
|
2020-01-18 02:07:52 +05:30
|
|
|
import { Resp } from 'app/services/request';
|
|
|
|
|
|
|
|
import { Credentials, Client, OAuthState, getCredentials } from './reducer';
|
2019-06-30 19:02:50 +05:30
|
|
|
|
2020-01-18 02:07:52 +05:30
|
|
|
interface ValidationErrorLiteral {
|
|
|
|
type: string;
|
|
|
|
payload: Record<string, any>;
|
|
|
|
}
|
2016-02-13 20:58:47 +05:30
|
|
|
|
2020-01-18 02:07:52 +05:30
|
|
|
type ValidationError = string | ValidationErrorLiteral;
|
2019-06-30 19:02:50 +05:30
|
|
|
|
2017-01-29 21:06:21 +05:30
|
|
|
/**
|
2019-12-26 17:48:58 +05:30
|
|
|
* Routes user to the previous page if it is possible
|
2017-01-29 21:06:21 +05:30
|
|
|
*
|
2017-12-31 00:34:31 +05:30
|
|
|
* @param {object} options
|
|
|
|
* @param {string} options.fallbackUrl - an url to route user to if goBack is not possible
|
2017-01-29 21:06:21 +05:30
|
|
|
*
|
2019-11-27 14:33:32 +05:30
|
|
|
* @returns {object} - action definition
|
2017-01-29 21:06:21 +05:30
|
|
|
*/
|
2019-11-27 14:33:32 +05:30
|
|
|
export function goBack(options: { fallbackUrl?: string }) {
|
|
|
|
const { fallbackUrl } = options || {};
|
|
|
|
|
|
|
|
if (history.canGoBack()) {
|
|
|
|
browserHistory.goBack();
|
|
|
|
} else if (fallbackUrl) {
|
|
|
|
browserHistory.push(fallbackUrl);
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
type: 'noop',
|
|
|
|
};
|
2017-01-29 21:06:21 +05:30
|
|
|
}
|
|
|
|
|
2019-12-07 16:58:52 +05:30
|
|
|
export function redirect(url: string): () => Promise<void> {
|
2019-11-27 14:33:32 +05:30
|
|
|
loader.show();
|
2017-02-01 11:52:12 +05:30
|
|
|
|
2019-11-27 14:33:32 +05:30
|
|
|
return () =>
|
|
|
|
new Promise(() => {
|
|
|
|
// do not resolve promise to make loader visible and
|
|
|
|
// overcome app rendering
|
|
|
|
location.href = url;
|
2017-02-01 11:52:12 +05:30
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-10-17 22:16:00 +05:30
|
|
|
const PASSWORD_REQUIRED = 'error.password_required';
|
|
|
|
const LOGIN_REQUIRED = 'error.login_required';
|
|
|
|
const ACTIVATION_REQUIRED = 'error.account_not_activated';
|
|
|
|
const TOTP_REQUIRED = 'error.totp_required';
|
|
|
|
|
2017-08-23 00:09:08 +05:30
|
|
|
export function login({
|
2019-11-27 14:33:32 +05:30
|
|
|
login = '',
|
|
|
|
password = '',
|
|
|
|
totp,
|
|
|
|
rememberMe = false,
|
2017-08-23 00:09:08 +05:30
|
|
|
}: {
|
2019-12-07 16:58:52 +05:30
|
|
|
login: string;
|
|
|
|
password?: string;
|
|
|
|
totp?: string;
|
|
|
|
rememberMe?: boolean;
|
2017-08-23 00:09:08 +05:30
|
|
|
}) {
|
2020-05-20 22:05:52 +05:30
|
|
|
return wrapInLoader((dispatch) =>
|
2019-11-27 14:33:32 +05:30
|
|
|
loginEndpoint({ login, password, totp, rememberMe })
|
|
|
|
.then(authHandler(dispatch))
|
2020-05-20 22:05:52 +05:30
|
|
|
.catch((resp) => {
|
2019-11-27 14:33:32 +05:30
|
|
|
if (resp.errors) {
|
|
|
|
if (resp.errors.password === PASSWORD_REQUIRED) {
|
|
|
|
return dispatch(setLogin(login));
|
|
|
|
} else if (resp.errors.login === ACTIVATION_REQUIRED) {
|
|
|
|
return dispatch(needActivation());
|
|
|
|
} else if (resp.errors.totp === TOTP_REQUIRED) {
|
|
|
|
return dispatch(
|
|
|
|
requestTotp({
|
|
|
|
login,
|
|
|
|
password,
|
|
|
|
rememberMe,
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
} else if (resp.errors.login === LOGIN_REQUIRED && password) {
|
|
|
|
logger.warn('No login on password panel');
|
|
|
|
|
|
|
|
return dispatch(logoutAll());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return Promise.reject(resp);
|
|
|
|
})
|
|
|
|
.catch(validationErrorsHandler(dispatch)),
|
|
|
|
);
|
2016-02-13 20:58:47 +05:30
|
|
|
}
|
|
|
|
|
2016-08-03 00:29:29 +05:30
|
|
|
export function acceptRules() {
|
2020-05-20 22:05:52 +05:30
|
|
|
return wrapInLoader((dispatch) =>
|
2019-11-27 14:33:32 +05:30
|
|
|
dispatch(userAcceptRules()).catch(validationErrorsHandler(dispatch)),
|
|
|
|
);
|
2016-08-03 00:29:29 +05:30
|
|
|
}
|
|
|
|
|
2016-05-15 02:23:58 +05:30
|
|
|
export function forgotPassword({
|
2019-11-27 14:33:32 +05:30
|
|
|
login = '',
|
|
|
|
captcha = '',
|
2017-08-23 00:09:08 +05:30
|
|
|
}: {
|
2019-12-07 16:58:52 +05:30
|
|
|
login: string;
|
|
|
|
captcha: string;
|
2016-05-15 02:23:58 +05:30
|
|
|
}) {
|
2019-11-27 14:33:32 +05:30
|
|
|
return wrapInLoader((dispatch, getState) =>
|
|
|
|
forgotPasswordEndpoint(login, captcha)
|
|
|
|
.then(({ data = {} }) =>
|
|
|
|
dispatch(
|
|
|
|
updateUser({
|
|
|
|
maskedEmail: data.emailMask || getState().user.email,
|
|
|
|
}),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
.catch(validationErrorsHandler(dispatch)),
|
|
|
|
);
|
2016-05-15 02:23:58 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
export function recoverPassword({
|
2019-11-27 14:33:32 +05:30
|
|
|
key = '',
|
|
|
|
newPassword = '',
|
|
|
|
newRePassword = '',
|
2017-08-23 00:09:08 +05:30
|
|
|
}: {
|
2019-12-07 16:58:52 +05:30
|
|
|
key: string;
|
|
|
|
newPassword: string;
|
|
|
|
newRePassword: string;
|
2016-05-15 02:23:58 +05:30
|
|
|
}) {
|
2020-05-20 22:05:52 +05:30
|
|
|
return wrapInLoader((dispatch) =>
|
2019-11-27 14:33:32 +05:30
|
|
|
recoverPasswordEndpoint(key, newPassword, newRePassword)
|
|
|
|
.then(authHandler(dispatch))
|
|
|
|
.catch(validationErrorsHandler(dispatch, '/forgot-password')),
|
|
|
|
);
|
2016-03-16 11:24:42 +05:30
|
|
|
}
|
|
|
|
|
2016-02-13 20:58:47 +05:30
|
|
|
export function register({
|
2019-11-27 14:33:32 +05:30
|
|
|
email = '',
|
|
|
|
username = '',
|
|
|
|
password = '',
|
|
|
|
rePassword = '',
|
|
|
|
captcha = '',
|
|
|
|
rulesAgreement = false,
|
2017-08-23 00:09:08 +05:30
|
|
|
}: {
|
2019-12-07 16:58:52 +05:30
|
|
|
email: string;
|
|
|
|
username: string;
|
|
|
|
password: string;
|
|
|
|
rePassword: string;
|
|
|
|
captcha: string;
|
|
|
|
rulesAgreement: boolean;
|
2016-02-13 20:58:47 +05:30
|
|
|
}) {
|
2019-11-27 14:33:32 +05:30
|
|
|
return wrapInLoader((dispatch, getState) =>
|
2020-01-18 02:07:52 +05:30
|
|
|
registerEndpoint({
|
|
|
|
email,
|
|
|
|
username,
|
|
|
|
password,
|
|
|
|
rePassword,
|
|
|
|
rulesAgreement,
|
|
|
|
lang: getState().user.lang,
|
|
|
|
captcha,
|
|
|
|
})
|
2019-11-27 14:33:32 +05:30
|
|
|
.then(() => {
|
|
|
|
dispatch(
|
|
|
|
updateUser({
|
|
|
|
username,
|
|
|
|
email,
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
|
|
|
|
dispatch(needActivation());
|
|
|
|
|
|
|
|
browserHistory.push('/activation');
|
|
|
|
})
|
|
|
|
.catch(validationErrorsHandler(dispatch)),
|
|
|
|
);
|
2016-02-13 20:58:47 +05:30
|
|
|
}
|
|
|
|
|
2019-12-07 16:58:52 +05:30
|
|
|
export function activate({
|
|
|
|
key = '',
|
|
|
|
}: {
|
|
|
|
key: string;
|
|
|
|
}): ThunkAction<Promise<Account>> {
|
2020-05-20 22:05:52 +05:30
|
|
|
return wrapInLoader((dispatch) =>
|
2020-01-18 02:07:52 +05:30
|
|
|
activateEndpoint(key)
|
2019-11-27 14:33:32 +05:30
|
|
|
.then(authHandler(dispatch))
|
|
|
|
.catch(validationErrorsHandler(dispatch, '/resend-activation')),
|
|
|
|
);
|
2016-05-23 00:28:43 +05:30
|
|
|
}
|
|
|
|
|
2017-08-23 00:09:08 +05:30
|
|
|
export function resendActivation({
|
2019-11-27 14:33:32 +05:30
|
|
|
email = '',
|
|
|
|
captcha,
|
2017-08-23 00:09:08 +05:30
|
|
|
}: {
|
2019-12-07 16:58:52 +05:30
|
|
|
email: string;
|
|
|
|
captcha: string;
|
2017-08-23 00:09:08 +05:30
|
|
|
}) {
|
2020-05-20 22:05:52 +05:30
|
|
|
return wrapInLoader((dispatch) =>
|
2020-01-18 02:07:52 +05:30
|
|
|
resendActivationEndpoint(email, captcha)
|
2020-05-20 22:05:52 +05:30
|
|
|
.then((resp) => {
|
2019-11-27 14:33:32 +05:30
|
|
|
dispatch(
|
|
|
|
updateUser({
|
|
|
|
email,
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
|
|
|
|
return resp;
|
|
|
|
})
|
|
|
|
.catch(validationErrorsHandler(dispatch)),
|
|
|
|
);
|
2016-02-13 20:58:47 +05:30
|
|
|
}
|
|
|
|
|
2017-02-24 02:37:05 +05:30
|
|
|
export function contactUs() {
|
2019-11-27 14:33:32 +05:30
|
|
|
return createPopup({ Popup: ContactForm });
|
2017-02-24 02:37:05 +05:30
|
|
|
}
|
|
|
|
|
2020-01-18 02:07:52 +05:30
|
|
|
interface SetCredentialsAction extends ReduxAction {
|
|
|
|
type: 'auth:setCredentials';
|
|
|
|
payload: Credentials | null;
|
|
|
|
}
|
|
|
|
|
|
|
|
function setCredentials(payload: Credentials | null): SetCredentialsAction {
|
|
|
|
return {
|
|
|
|
type: 'auth:setCredentials',
|
|
|
|
payload,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2017-08-23 00:09:08 +05:30
|
|
|
/**
|
|
|
|
* Sets login in credentials state
|
|
|
|
* Resets the state, when `null` is passed
|
|
|
|
*
|
2020-01-18 02:07:52 +05:30
|
|
|
* @param login
|
2017-08-23 00:09:08 +05:30
|
|
|
*/
|
2020-01-18 02:07:52 +05:30
|
|
|
export function setLogin(login: string | null): SetCredentialsAction {
|
|
|
|
return setCredentials(login ? { login } : null);
|
2017-08-23 00:09:08 +05:30
|
|
|
}
|
|
|
|
|
2019-12-07 16:58:52 +05:30
|
|
|
export function relogin(login: string | null): ThunkAction {
|
|
|
|
return (dispatch, getState) => {
|
2019-11-27 14:33:32 +05:30
|
|
|
const credentials = getCredentials(getState());
|
|
|
|
const returnUrl =
|
|
|
|
credentials.returnUrl || location.pathname + location.search;
|
|
|
|
|
2020-01-18 02:07:52 +05:30
|
|
|
dispatch(
|
|
|
|
setCredentials({
|
2019-11-27 14:33:32 +05:30
|
|
|
login,
|
|
|
|
returnUrl,
|
|
|
|
isRelogin: true,
|
2020-01-18 02:07:52 +05:30
|
|
|
}),
|
|
|
|
);
|
2018-02-18 01:29:35 +05:30
|
|
|
|
2019-11-27 14:33:32 +05:30
|
|
|
browserHistory.push('/login');
|
|
|
|
};
|
2018-02-18 01:29:35 +05:30
|
|
|
}
|
|
|
|
|
2020-01-18 02:07:52 +05:30
|
|
|
export type CredentialsAction = SetCredentialsAction;
|
|
|
|
|
2019-11-27 14:33:32 +05:30
|
|
|
function requestTotp({
|
|
|
|
login,
|
|
|
|
password,
|
|
|
|
rememberMe,
|
|
|
|
}: {
|
2019-12-07 16:58:52 +05:30
|
|
|
login: string;
|
|
|
|
password: string;
|
|
|
|
rememberMe: boolean;
|
|
|
|
}): ThunkAction {
|
|
|
|
return (dispatch, getState) => {
|
2019-11-27 14:33:32 +05:30
|
|
|
// merging with current credentials to propogate returnUrl
|
|
|
|
const credentials = getCredentials(getState());
|
|
|
|
|
2020-01-18 02:07:52 +05:30
|
|
|
dispatch(
|
|
|
|
setCredentials({
|
2019-11-27 14:33:32 +05:30
|
|
|
...credentials,
|
|
|
|
login,
|
|
|
|
password,
|
|
|
|
rememberMe,
|
|
|
|
isTotpRequired: true,
|
2020-01-18 02:07:52 +05:30
|
|
|
}),
|
|
|
|
);
|
2019-11-27 14:33:32 +05:30
|
|
|
};
|
2016-11-13 02:01:44 +05:30
|
|
|
}
|
|
|
|
|
2020-01-18 02:07:52 +05:30
|
|
|
interface SetSwitcherAction extends ReduxAction {
|
|
|
|
type: 'auth:setAccountSwitcher';
|
|
|
|
payload: boolean;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function setAccountSwitcher(isOn: boolean): SetSwitcherAction {
|
2019-11-27 14:33:32 +05:30
|
|
|
return {
|
2020-01-18 02:07:52 +05:30
|
|
|
type: 'auth:setAccountSwitcher',
|
2019-11-27 14:33:32 +05:30
|
|
|
payload: isOn,
|
|
|
|
};
|
2016-11-13 20:17:56 +05:30
|
|
|
}
|
|
|
|
|
2020-01-18 02:07:52 +05:30
|
|
|
export type AccountSwitcherAction = SetSwitcherAction;
|
|
|
|
|
|
|
|
interface SetErrorAction extends ReduxAction {
|
|
|
|
type: 'auth:error';
|
|
|
|
payload: Record<string, ValidationError> | null;
|
|
|
|
error: boolean;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function setErrors(
|
|
|
|
errors: Record<string, ValidationError> | null,
|
|
|
|
): SetErrorAction {
|
2019-11-27 14:33:32 +05:30
|
|
|
return {
|
2020-01-18 02:07:52 +05:30
|
|
|
type: 'auth:error',
|
2019-11-27 14:33:32 +05:30
|
|
|
payload: errors,
|
|
|
|
error: true,
|
|
|
|
};
|
2016-02-13 20:58:47 +05:30
|
|
|
}
|
|
|
|
|
2020-01-18 02:07:52 +05:30
|
|
|
export function clearErrors(): SetErrorAction {
|
2019-11-27 14:33:32 +05:30
|
|
|
return setErrors(null);
|
2016-02-13 20:58:47 +05:30
|
|
|
}
|
|
|
|
|
2020-01-18 02:07:52 +05:30
|
|
|
export type ErrorAction = SetErrorAction;
|
|
|
|
|
|
|
|
const KNOWN_SCOPES: ReadonlyArray<string> = [
|
2019-11-27 14:33:32 +05:30
|
|
|
'minecraft_server_session',
|
|
|
|
'offline_access',
|
|
|
|
'account_info',
|
|
|
|
'account_email',
|
2017-10-17 23:20:34 +05:30
|
|
|
];
|
2016-08-23 10:00:06 +05:30
|
|
|
/**
|
|
|
|
* @param {object} oauthData
|
|
|
|
* @param {string} oauthData.clientId
|
|
|
|
* @param {string} oauthData.redirectUrl
|
|
|
|
* @param {string} oauthData.responseType
|
|
|
|
* @param {string} oauthData.description
|
|
|
|
* @param {string} oauthData.scope
|
2016-11-19 20:11:15 +05:30
|
|
|
* @param {string} [oauthData.prompt='none'] - comma-separated list of values to adjust auth flow
|
|
|
|
* Posible values:
|
|
|
|
* * none - default behaviour
|
|
|
|
* * consent - forcibly prompt user for rules acceptance
|
|
|
|
* * select_account - force account choosage, even if user has only one
|
|
|
|
* @param {string} oauthData.loginHint - allows to choose the account, which will be used for auth
|
|
|
|
* The possible values: account id, email, username
|
2016-08-23 10:00:06 +05:30
|
|
|
* @param {string} oauthData.state
|
|
|
|
*
|
2019-11-27 14:33:32 +05:30
|
|
|
* @returns {Promise}
|
2016-08-23 10:00:06 +05:30
|
|
|
*/
|
2018-05-03 10:45:09 +05:30
|
|
|
export function oAuthValidate(oauthData: OauthData) {
|
2019-11-27 14:33:32 +05:30
|
|
|
// TODO: move to oAuth actions?
|
|
|
|
// test request: /oauth?client_id=ely&redirect_uri=http%3A%2F%2Fely.by&response_type=code&scope=minecraft_server_session&description=foo
|
2020-05-20 22:05:52 +05:30
|
|
|
return wrapInLoader((dispatch) =>
|
2019-11-27 14:33:32 +05:30
|
|
|
oauth
|
|
|
|
.validate(oauthData)
|
2020-05-20 22:05:52 +05:30
|
|
|
.then((resp) => {
|
2019-11-27 14:33:32 +05:30
|
|
|
const { scopes } = resp.session;
|
|
|
|
const invalidScopes = scopes.filter(
|
2020-05-20 22:05:52 +05:30
|
|
|
(scope) => !KNOWN_SCOPES.includes(scope),
|
2019-11-27 14:33:32 +05:30
|
|
|
);
|
|
|
|
let prompt = (oauthData.prompt || 'none')
|
|
|
|
.split(',')
|
2020-05-20 22:05:52 +05:30
|
|
|
.map((item) => item.trim());
|
2019-11-27 14:33:32 +05:30
|
|
|
|
|
|
|
if (prompt.includes('none')) {
|
|
|
|
prompt = ['none'];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (invalidScopes.length) {
|
|
|
|
logger.error('Got invalid scopes after oauth validation', {
|
|
|
|
invalidScopes,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
dispatch(setClient(resp.client));
|
|
|
|
dispatch(
|
|
|
|
setOAuthRequest({
|
|
|
|
...resp.oAuth,
|
|
|
|
prompt: oauthData.prompt || 'none',
|
|
|
|
loginHint: oauthData.loginHint,
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
dispatch(setScopes(scopes));
|
|
|
|
localStorage.setItem(
|
|
|
|
'oauthData',
|
|
|
|
JSON.stringify({
|
|
|
|
// @see services/authFlow/AuthFlow
|
|
|
|
timestamp: Date.now(),
|
|
|
|
payload: oauthData,
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
})
|
|
|
|
.catch(handleOauthParamsValidation),
|
|
|
|
);
|
2016-02-27 16:23:58 +05:30
|
|
|
}
|
|
|
|
|
2016-08-27 15:49:02 +05:30
|
|
|
/**
|
|
|
|
* @param {object} params
|
|
|
|
* @param {bool} params.accept=false
|
2020-05-20 22:05:52 +05:30
|
|
|
* @param params.accept
|
2019-11-27 14:33:32 +05:30
|
|
|
* @returns {Promise}
|
2016-08-27 15:49:02 +05:30
|
|
|
*/
|
2019-11-27 14:33:32 +05:30
|
|
|
export function oAuthComplete(params: { accept?: boolean } = {}) {
|
2019-12-07 16:58:52 +05:30
|
|
|
return wrapInLoader(
|
|
|
|
async (
|
|
|
|
dispatch,
|
|
|
|
getState,
|
|
|
|
): Promise<{
|
|
|
|
success: boolean;
|
|
|
|
redirectUri: string;
|
|
|
|
}> => {
|
|
|
|
const oauthData = getState().auth.oauth;
|
|
|
|
|
|
|
|
if (!oauthData) {
|
|
|
|
throw new Error('Can not complete oAuth. Oauth data does not exist');
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
const resp = await oauth.complete(oauthData, params);
|
2019-11-27 14:33:32 +05:30
|
|
|
localStorage.removeItem('oauthData');
|
|
|
|
|
|
|
|
if (resp.redirectUri.startsWith('static_page')) {
|
2019-12-26 17:48:58 +05:30
|
|
|
const displayCode = /static_page_with_code/.test(resp.redirectUri);
|
2019-12-07 16:58:52 +05:30
|
|
|
|
|
|
|
const [, code] = resp.redirectUri.match(/code=(.+)&/) || [];
|
|
|
|
[, resp.redirectUri] = resp.redirectUri.match(/^(.+)\?/) || [];
|
2019-11-27 14:33:32 +05:30
|
|
|
|
|
|
|
dispatch(
|
|
|
|
setOAuthCode({
|
|
|
|
success: resp.success,
|
|
|
|
code,
|
|
|
|
displayCode,
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return resp;
|
2019-12-07 16:58:52 +05:30
|
|
|
} catch (error) {
|
|
|
|
const resp:
|
2019-11-27 14:33:32 +05:30
|
|
|
| {
|
2019-12-07 16:58:52 +05:30
|
|
|
acceptRequired: boolean;
|
2019-11-27 14:33:32 +05:30
|
|
|
}
|
|
|
|
| {
|
2019-12-07 16:58:52 +05:30
|
|
|
unauthorized: boolean;
|
|
|
|
} = error;
|
|
|
|
|
|
|
|
if ('acceptRequired' in resp) {
|
2019-11-27 14:33:32 +05:30
|
|
|
dispatch(requirePermissionsAccept());
|
|
|
|
|
|
|
|
return Promise.reject(resp);
|
|
|
|
}
|
|
|
|
|
|
|
|
return handleOauthParamsValidation(resp);
|
2019-12-07 16:58:52 +05:30
|
|
|
}
|
|
|
|
},
|
2019-11-27 14:33:32 +05:30
|
|
|
);
|
2016-02-27 16:23:58 +05:30
|
|
|
}
|
|
|
|
|
2019-11-27 14:33:32 +05:30
|
|
|
function handleOauthParamsValidation(
|
2019-12-07 16:58:52 +05:30
|
|
|
resp: {
|
|
|
|
[key: string]: any;
|
|
|
|
userMessage?: string;
|
|
|
|
} = {},
|
2019-11-27 14:33:32 +05:30
|
|
|
) {
|
|
|
|
dispatchBsod();
|
|
|
|
localStorage.removeItem('oauthData');
|
2016-08-07 20:20:00 +05:30
|
|
|
|
2019-11-27 14:33:32 +05:30
|
|
|
// eslint-disable-next-line no-alert
|
|
|
|
resp.userMessage && setTimeout(() => alert(resp.userMessage), 500); // 500 ms to allow re-render
|
2016-07-27 23:57:21 +05:30
|
|
|
|
2019-11-27 14:33:32 +05:30
|
|
|
return Promise.reject(resp);
|
2016-02-23 11:27:16 +05:30
|
|
|
}
|
|
|
|
|
2020-01-18 02:07:52 +05:30
|
|
|
interface SetClientAction extends ReduxAction {
|
|
|
|
type: 'set_client';
|
|
|
|
payload: Client;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function setClient(payload: Client): SetClientAction {
|
2019-11-27 14:33:32 +05:30
|
|
|
return {
|
2020-01-18 02:07:52 +05:30
|
|
|
type: 'set_client',
|
|
|
|
payload,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
export type ClientAction = SetClientAction;
|
|
|
|
|
|
|
|
interface SetOauthAction extends ReduxAction {
|
|
|
|
type: 'set_oauth';
|
|
|
|
payload: Pick<
|
|
|
|
OAuthState,
|
|
|
|
| 'clientId'
|
|
|
|
| 'redirectUrl'
|
|
|
|
| 'responseType'
|
|
|
|
| 'scope'
|
|
|
|
| 'prompt'
|
|
|
|
| 'loginHint'
|
|
|
|
| 'state'
|
|
|
|
>;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Input data is coming right from the query string, so the names
|
|
|
|
// are the same, as used for initializing OAuth2 request
|
|
|
|
export function setOAuthRequest(data: {
|
|
|
|
client_id?: string;
|
|
|
|
redirect_uri?: string;
|
|
|
|
response_type?: string;
|
|
|
|
scope?: string;
|
|
|
|
prompt?: string;
|
|
|
|
loginHint?: string;
|
|
|
|
state?: string;
|
|
|
|
}): SetOauthAction {
|
|
|
|
return {
|
|
|
|
type: 'set_oauth',
|
|
|
|
payload: {
|
|
|
|
// TODO: there is too much default empty string. Maybe we can somehow validate it
|
|
|
|
// on the level, where this action is called?
|
|
|
|
clientId: data.client_id || '',
|
|
|
|
redirectUrl: data.redirect_uri || '',
|
|
|
|
responseType: data.response_type || '',
|
|
|
|
scope: data.scope || '',
|
|
|
|
prompt: data.prompt || '',
|
|
|
|
loginHint: data.loginHint || '',
|
|
|
|
state: data.state || '',
|
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
interface SetOAuthResultAction extends ReduxAction {
|
|
|
|
type: 'set_oauth_result';
|
|
|
|
payload: Pick<OAuthState, 'success' | 'code' | 'displayCode'>;
|
|
|
|
}
|
|
|
|
|
|
|
|
export const SET_OAUTH_RESULT = 'set_oauth_result'; // TODO: remove
|
|
|
|
|
|
|
|
export function setOAuthCode(payload: {
|
|
|
|
success: boolean;
|
|
|
|
code: string;
|
|
|
|
displayCode: boolean;
|
|
|
|
}): SetOAuthResultAction {
|
|
|
|
return {
|
|
|
|
type: 'set_oauth_result',
|
|
|
|
payload,
|
2019-11-27 14:33:32 +05:30
|
|
|
};
|
2016-02-23 11:27:16 +05:30
|
|
|
}
|
2016-02-27 16:23:58 +05:30
|
|
|
|
2019-12-07 16:58:52 +05:30
|
|
|
export function resetOAuth(): ThunkAction {
|
|
|
|
return (dispatch): void => {
|
2019-11-27 14:33:32 +05:30
|
|
|
localStorage.removeItem('oauthData');
|
|
|
|
dispatch(setOAuthRequest({}));
|
|
|
|
};
|
2016-11-19 16:24:24 +05:30
|
|
|
}
|
|
|
|
|
2017-02-01 11:55:15 +05:30
|
|
|
/**
|
|
|
|
* Resets all temporary state related to auth
|
|
|
|
*/
|
2019-12-07 16:58:52 +05:30
|
|
|
export function resetAuth(): ThunkAction {
|
|
|
|
return (dispatch, getSate): Promise<void> => {
|
2019-11-27 14:33:32 +05:30
|
|
|
dispatch(setLogin(null));
|
|
|
|
dispatch(resetOAuth());
|
|
|
|
// ensure current account is valid
|
|
|
|
const activeAccount = getActiveAccount(getSate());
|
|
|
|
|
|
|
|
if (activeAccount) {
|
2019-12-07 16:58:52 +05:30
|
|
|
return Promise.resolve(dispatch(authenticate(activeAccount)))
|
|
|
|
.then(() => {})
|
|
|
|
.catch(() => {
|
2019-11-27 14:33:32 +05:30
|
|
|
// its okay. user will be redirected to an appropriate place
|
2019-12-07 16:58:52 +05:30
|
|
|
});
|
2019-11-27 14:33:32 +05:30
|
|
|
}
|
2018-03-01 03:28:45 +05:30
|
|
|
|
2019-11-27 14:33:32 +05:30
|
|
|
return Promise.resolve();
|
|
|
|
};
|
2017-02-01 11:55:15 +05:30
|
|
|
}
|
|
|
|
|
2020-01-18 02:07:52 +05:30
|
|
|
interface RequestPermissionsAcceptAction extends ReduxAction {
|
|
|
|
type: 'require_permissions_accept';
|
2016-02-27 16:23:58 +05:30
|
|
|
}
|
2016-02-29 23:46:33 +05:30
|
|
|
|
2020-01-18 02:07:52 +05:30
|
|
|
export function requirePermissionsAccept(): RequestPermissionsAcceptAction {
|
2019-11-27 14:33:32 +05:30
|
|
|
return {
|
2020-01-18 02:07:52 +05:30
|
|
|
type: 'require_permissions_accept',
|
2019-11-27 14:33:32 +05:30
|
|
|
};
|
2016-03-15 12:06:13 +05:30
|
|
|
}
|
|
|
|
|
2020-01-18 02:07:52 +05:30
|
|
|
export type OAuthAction =
|
|
|
|
| SetOauthAction
|
|
|
|
| SetOAuthResultAction
|
|
|
|
| RequestPermissionsAcceptAction;
|
|
|
|
|
|
|
|
interface SetScopesAction extends ReduxAction {
|
|
|
|
type: 'set_scopes';
|
|
|
|
payload: Array<Scope>;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function setScopes(payload: Array<Scope>): SetScopesAction {
|
2019-11-27 14:33:32 +05:30
|
|
|
return {
|
2020-01-18 02:07:52 +05:30
|
|
|
type: 'set_scopes',
|
|
|
|
payload,
|
2019-11-27 14:33:32 +05:30
|
|
|
};
|
2016-04-15 01:24:35 +05:30
|
|
|
}
|
|
|
|
|
2020-01-18 02:07:52 +05:30
|
|
|
export type ScopesAction = SetScopesAction;
|
2019-11-27 14:33:32 +05:30
|
|
|
|
2020-01-18 02:07:52 +05:30
|
|
|
interface SetLoadingAction extends ReduxAction {
|
|
|
|
type: 'set_loading_state';
|
|
|
|
payload: boolean;
|
2016-02-29 23:46:33 +05:30
|
|
|
}
|
2016-04-02 16:28:54 +05:30
|
|
|
|
2020-01-18 02:07:52 +05:30
|
|
|
export function setLoadingState(isLoading: boolean): SetLoadingAction {
|
2019-11-27 14:33:32 +05:30
|
|
|
return {
|
2020-01-18 02:07:52 +05:30
|
|
|
type: 'set_loading_state',
|
2019-11-27 14:33:32 +05:30
|
|
|
payload: isLoading,
|
|
|
|
};
|
2016-04-02 16:28:54 +05:30
|
|
|
}
|
|
|
|
|
2019-12-07 16:58:52 +05:30
|
|
|
function wrapInLoader<T>(fn: ThunkAction<Promise<T>>): ThunkAction<Promise<T>> {
|
|
|
|
return (dispatch, getState) => {
|
2019-11-27 14:33:32 +05:30
|
|
|
dispatch(setLoadingState(true));
|
|
|
|
const endLoading = () => dispatch(setLoadingState(false));
|
2016-04-02 16:28:54 +05:30
|
|
|
|
2019-12-07 16:58:52 +05:30
|
|
|
return fn(dispatch, getState, undefined).then(
|
2020-05-20 22:05:52 +05:30
|
|
|
(resp) => {
|
2019-11-27 14:33:32 +05:30
|
|
|
endLoading();
|
2016-04-02 16:28:54 +05:30
|
|
|
|
2019-11-27 14:33:32 +05:30
|
|
|
return resp;
|
|
|
|
},
|
2020-05-20 22:05:52 +05:30
|
|
|
(resp) => {
|
2019-11-27 14:33:32 +05:30
|
|
|
endLoading();
|
2016-04-02 16:28:54 +05:30
|
|
|
|
2019-11-27 14:33:32 +05:30
|
|
|
return Promise.reject(resp);
|
|
|
|
},
|
|
|
|
);
|
|
|
|
};
|
2016-04-02 16:28:54 +05:30
|
|
|
}
|
2016-04-15 09:39:04 +05:30
|
|
|
|
2020-01-18 02:07:52 +05:30
|
|
|
export type LoadingAction = SetLoadingAction;
|
|
|
|
|
2016-04-15 09:39:04 +05:30
|
|
|
function needActivation() {
|
2019-11-27 14:33:32 +05:30
|
|
|
return updateUser({
|
|
|
|
isActive: false,
|
|
|
|
isGuest: false,
|
|
|
|
});
|
2016-04-15 09:39:04 +05:30
|
|
|
}
|
2016-05-15 02:23:58 +05:30
|
|
|
|
2019-12-07 16:58:52 +05:30
|
|
|
function authHandler(dispatch: Dispatch) {
|
2019-12-26 17:48:58 +05:30
|
|
|
return (oAuthResp: OAuthResponse): Promise<Account> =>
|
2019-11-27 14:33:32 +05:30
|
|
|
dispatch(
|
|
|
|
authenticate({
|
2019-12-26 17:48:58 +05:30
|
|
|
token: oAuthResp.access_token,
|
|
|
|
refreshToken: oAuthResp.refresh_token || null,
|
2019-11-27 14:33:32 +05:30
|
|
|
}),
|
2020-05-20 22:05:52 +05:30
|
|
|
).then((resp) => {
|
2019-11-27 14:33:32 +05:30
|
|
|
dispatch(setLogin(null));
|
2016-11-13 02:01:44 +05:30
|
|
|
|
2019-11-27 14:33:32 +05:30
|
|
|
return resp;
|
2016-11-13 02:01:44 +05:30
|
|
|
});
|
2016-06-04 19:16:39 +05:30
|
|
|
}
|
|
|
|
|
2020-01-18 02:07:52 +05:30
|
|
|
function validationErrorsHandler(
|
|
|
|
dispatch: Dispatch,
|
|
|
|
repeatUrl?: string,
|
|
|
|
): (
|
|
|
|
resp: Resp<{
|
2020-05-20 22:29:06 +05:30
|
|
|
errors?: Record<string, string | ValidationError>;
|
2020-01-18 02:07:52 +05:30
|
|
|
data?: Record<string, any>;
|
|
|
|
}>,
|
|
|
|
) => Promise<never> {
|
2020-05-20 22:05:52 +05:30
|
|
|
return (resp) => {
|
2019-11-27 14:33:32 +05:30
|
|
|
if (resp.errors) {
|
2019-12-26 17:48:58 +05:30
|
|
|
const [firstError] = Object.keys(resp.errors);
|
2020-01-18 02:07:52 +05:30
|
|
|
const firstErrorObj: ValidationError = {
|
|
|
|
type: resp.errors[firstError] as string,
|
2019-11-27 14:33:32 +05:30
|
|
|
payload: {
|
|
|
|
isGuest: true,
|
|
|
|
repeatUrl: '',
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
if (resp.data) {
|
|
|
|
// TODO: this should be formatted on backend
|
2020-01-18 02:07:52 +05:30
|
|
|
Object.assign(firstErrorObj.payload, resp.data);
|
2019-11-27 14:33:32 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
2020-01-18 02:07:52 +05:30
|
|
|
['error.key_not_exists', 'error.key_expire'].includes(
|
|
|
|
firstErrorObj.type,
|
|
|
|
) &&
|
2019-11-27 14:33:32 +05:30
|
|
|
repeatUrl
|
|
|
|
) {
|
|
|
|
// TODO: this should be formatted on backend
|
2020-01-18 02:07:52 +05:30
|
|
|
firstErrorObj.payload.repeatUrl = repeatUrl;
|
2019-11-27 14:33:32 +05:30
|
|
|
}
|
|
|
|
|
2020-01-18 02:07:52 +05:30
|
|
|
// TODO: can I clone the object or its necessary to catch modified errors list on corresponding catches?
|
2020-05-20 22:05:52 +05:30
|
|
|
const { errors } = resp;
|
2020-01-18 02:07:52 +05:30
|
|
|
errors[firstError] = firstErrorObj;
|
2019-11-27 14:33:32 +05:30
|
|
|
|
2020-01-18 02:07:52 +05:30
|
|
|
dispatch(setErrors(errors));
|
2019-11-27 14:33:32 +05:30
|
|
|
}
|
2016-05-15 02:23:58 +05:30
|
|
|
|
2019-11-27 14:33:32 +05:30
|
|
|
return Promise.reject(resp);
|
|
|
|
};
|
2016-05-15 02:23:58 +05:30
|
|
|
}
|