mirror of
https://github.com/elyby/accounts-frontend.git
synced 2024-11-30 10:42:34 +05:30
#389: allow switch accounts, when refreshToken is invalid. Fix oauth in case, when refreshToken is invalid
This commit is contained in:
parent
f64fb3b96b
commit
bf2976c009
@ -1,9 +1,8 @@
|
||||
// @flow
|
||||
import { getJwtPayload } from 'functions';
|
||||
import { browserHistory } from 'services/history';
|
||||
import { sessionStorage } from 'services/localStorage';
|
||||
import authentication from 'services/api/authentication';
|
||||
import { setLogin } from 'components/auth/actions';
|
||||
import { relogin as navigateToLogin } from 'components/auth/actions';
|
||||
import { updateUser, setGuest } from 'components/user/actions';
|
||||
import { setLocale } from 'components/i18n/actions';
|
||||
import { setAccountSwitcher } from 'components/auth/actions';
|
||||
@ -238,8 +237,7 @@ export function relogin(email?: string) {
|
||||
email = activeAccount.email;
|
||||
}
|
||||
|
||||
email && dispatch(setLogin(email));
|
||||
browserHistory.push('/login');
|
||||
dispatch(navigateToLogin(email || null));
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@ import ContactForm from 'components/contact/ContactForm';
|
||||
|
||||
export { updateUser } from 'components/user/actions';
|
||||
export { authenticate, logoutAll as logout } from 'components/accounts/actions';
|
||||
import { getCredentials } from './reducer';
|
||||
|
||||
/**
|
||||
* Reoutes user to the previous page if it is possible
|
||||
@ -224,19 +225,40 @@ export function setLogin(login: ?string) {
|
||||
};
|
||||
}
|
||||
|
||||
export function relogin(login: ?string) {
|
||||
return (dispatch: (Function | Object) => void) => {
|
||||
dispatch({
|
||||
type: SET_CREDENTIALS,
|
||||
payload: {
|
||||
login,
|
||||
returnUrl: location.pathname + location.search,
|
||||
isRelogin: true,
|
||||
},
|
||||
});
|
||||
|
||||
browserHistory.push('/login');
|
||||
};
|
||||
}
|
||||
|
||||
function requestTotp({login, password, rememberMe}: {
|
||||
login: string,
|
||||
password: string,
|
||||
rememberMe: bool
|
||||
}) {
|
||||
return {
|
||||
type: SET_CREDENTIALS,
|
||||
payload: {
|
||||
login,
|
||||
password,
|
||||
rememberMe,
|
||||
isTotpRequired: true
|
||||
}
|
||||
return (dispatch: (Function | Object) => void, getState: () => Object) => {
|
||||
// merging with current credentials to propogate returnUrl
|
||||
const credentials = getCredentials(getState());
|
||||
|
||||
dispatch({
|
||||
type: SET_CREDENTIALS,
|
||||
payload: {
|
||||
...credentials,
|
||||
login,
|
||||
password,
|
||||
rememberMe,
|
||||
isTotpRequired: true
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -19,11 +19,13 @@ export default class ChooseAccountBody extends BaseAuthBody {
|
||||
<div>
|
||||
{this.renderErrors()}
|
||||
|
||||
<div className={styles.description}>
|
||||
<Message {...messages.description} values={{
|
||||
appName: <span className={styles.appName}>{client.name}</span>
|
||||
}} />
|
||||
</div>
|
||||
{client && (
|
||||
<div className={styles.description}>
|
||||
<Message {...messages.description} values={{
|
||||
appName: <span className={styles.appName}>{client.name}</span>
|
||||
}} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className={styles.accountSwitcherContainer}>
|
||||
<AccountSwitcher
|
||||
|
@ -13,6 +13,15 @@ import {
|
||||
SET_SWITCHER
|
||||
} from './actions';
|
||||
|
||||
type Credentials = {
|
||||
login?: string,
|
||||
password?: string,
|
||||
rememberMe?: bool,
|
||||
returnUrl?: string,
|
||||
isRelogin?: bool,
|
||||
isTotpRequired?: bool,
|
||||
};
|
||||
|
||||
export default combineReducers({
|
||||
credentials,
|
||||
error,
|
||||
@ -44,12 +53,7 @@ function credentials(
|
||||
state = {},
|
||||
{type, payload}: {
|
||||
type: string,
|
||||
payload: ?{
|
||||
login?: string,
|
||||
password?: string,
|
||||
rememberMe?: bool,
|
||||
isTotpRequired?: bool
|
||||
}
|
||||
payload: ?Credentials
|
||||
}
|
||||
) {
|
||||
if (type === SET_CREDENTIALS) {
|
||||
@ -166,11 +170,6 @@ export function getLogin(state: Object): ?string {
|
||||
return state.auth.credentials.login || null;
|
||||
}
|
||||
|
||||
export function getCredentials(state: Object): {
|
||||
login?: string,
|
||||
password?: string,
|
||||
rememberMe?: bool,
|
||||
isTotpRequired?: bool
|
||||
} {
|
||||
export function getCredentials(state: Object): Credentials {
|
||||
return state.auth.credentials;
|
||||
}
|
||||
|
@ -59,6 +59,7 @@ class AuthPage extends Component<{
|
||||
<Route path="/activation/:key?" render={renderPanelTransition(Activation)} />
|
||||
<Route path="/resend-activation" render={renderPanelTransition(ResendActivation)} />
|
||||
<Route path="/oauth/permissions" render={renderPanelTransition(Permissions)} />
|
||||
<Route path="/choose-account" render={renderPanelTransition(ChooseAccount)} />
|
||||
<Route path="/oauth/choose-account" render={renderPanelTransition(ChooseAccount)} />
|
||||
<Route path="/oauth/finish" component={Finish} />
|
||||
<Route path="/accept-rules" render={renderPanelTransition(AcceptRules)} />
|
||||
|
@ -11,6 +11,7 @@ import ForgotPasswordState from './ForgotPasswordState';
|
||||
import RecoverPasswordState from './RecoverPasswordState';
|
||||
import ActivationState from './ActivationState';
|
||||
import CompleteState from './CompleteState';
|
||||
import ChooseAccountState from './ChooseAccountState';
|
||||
import ResendActivationState from './ResendActivationState';
|
||||
import type AbstractState from './AbstractState';
|
||||
|
||||
@ -219,6 +220,10 @@ export default class AuthFlow implements AuthContext {
|
||||
this.setState(new ResendActivationState());
|
||||
break;
|
||||
|
||||
case '/choose-account':
|
||||
this.setState(new ChooseAccountState());
|
||||
break;
|
||||
|
||||
case '/':
|
||||
case '/login':
|
||||
case '/password':
|
||||
|
@ -13,6 +13,7 @@ import ActivationState from 'services/authFlow/ActivationState';
|
||||
import ResendActivationState from 'services/authFlow/ResendActivationState';
|
||||
import LoginState from 'services/authFlow/LoginState';
|
||||
import CompleteState from 'services/authFlow/CompleteState';
|
||||
import ChooseAccountState from 'services/authFlow/ChooseAccountState';
|
||||
|
||||
describe('AuthFlow', () => {
|
||||
let flow;
|
||||
@ -275,6 +276,7 @@ describe('AuthFlow', () => {
|
||||
'/oauth2/v1': OAuthState,
|
||||
'/oauth2': OAuthState,
|
||||
'/register': RegisterState,
|
||||
'/choose-account': ChooseAccountState,
|
||||
'/recover-password': RecoverPasswordState,
|
||||
'/recover-password/key123': RecoverPasswordState,
|
||||
'/forgot-password': ForgotPasswordState,
|
||||
|
@ -4,7 +4,13 @@ import CompleteState from './CompleteState';
|
||||
|
||||
export default class ChooseAccountState extends AbstractState {
|
||||
enter(context) {
|
||||
context.navigate('/oauth/choose-account');
|
||||
const { auth } = context.getState();
|
||||
|
||||
if (auth.oauth) {
|
||||
context.navigate('/oauth/choose-account');
|
||||
} else {
|
||||
context.navigate('/choose-account');
|
||||
}
|
||||
}
|
||||
|
||||
resolve(context, payload) {
|
||||
@ -12,6 +18,7 @@ export default class ChooseAccountState extends AbstractState {
|
||||
context.setState(new CompleteState());
|
||||
} else {
|
||||
context.navigate('/login');
|
||||
context.run('setLogin', null);
|
||||
context.setState(new LoginState());
|
||||
}
|
||||
}
|
||||
|
@ -23,10 +23,28 @@ describe('ChooseAccountState', () => {
|
||||
|
||||
describe('#enter', () => {
|
||||
it('should navigate to /oauth/choose-account', () => {
|
||||
context.getState.returns({
|
||||
auth: {
|
||||
oauth: {},
|
||||
},
|
||||
});
|
||||
|
||||
expectNavigate(mock, '/oauth/choose-account');
|
||||
|
||||
state.enter(context);
|
||||
});
|
||||
|
||||
it('should navigate to /choose-account if not oauth', () => {
|
||||
context.getState.returns({
|
||||
auth: {
|
||||
oauth: null,
|
||||
},
|
||||
});
|
||||
|
||||
expectNavigate(mock, '/choose-account');
|
||||
|
||||
state.enter(context);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#resolve', () => {
|
||||
@ -38,6 +56,7 @@ describe('ChooseAccountState', () => {
|
||||
|
||||
it('should transition to login if user wants to add new account', () => {
|
||||
expectNavigate(mock, '/login');
|
||||
expectRun(mock, 'setLogin', null);
|
||||
expectState(mock, LoginState);
|
||||
|
||||
state.resolve(context, {});
|
||||
|
@ -3,6 +3,7 @@ import logger from 'services/logger';
|
||||
import { getCredentials } from 'components/auth/reducer';
|
||||
|
||||
import AbstractState from './AbstractState';
|
||||
import ChooseAccountState from './ChooseAccountState';
|
||||
import CompleteState from './CompleteState';
|
||||
import ForgotPasswordState from './ForgotPasswordState';
|
||||
import LoginState from './LoginState';
|
||||
@ -31,7 +32,7 @@ export default class PasswordState extends AbstractState {
|
||||
rememberMe: bool
|
||||
}
|
||||
) {
|
||||
const {login} = getCredentials(context.getState());
|
||||
const { login, returnUrl } = getCredentials(context.getState());
|
||||
|
||||
return context.run('login', {
|
||||
password,
|
||||
@ -39,12 +40,17 @@ export default class PasswordState extends AbstractState {
|
||||
login
|
||||
})
|
||||
.then(() => {
|
||||
const {isTotpRequired} = getCredentials(context.getState());
|
||||
const { isTotpRequired } = getCredentials(context.getState());
|
||||
|
||||
if (isTotpRequired) {
|
||||
return context.setState(new MfaState());
|
||||
}
|
||||
|
||||
if (returnUrl) {
|
||||
context.navigate(returnUrl);
|
||||
return;
|
||||
}
|
||||
|
||||
return context.setState(new CompleteState());
|
||||
})
|
||||
.catch((err = {}) =>
|
||||
@ -57,7 +63,13 @@ export default class PasswordState extends AbstractState {
|
||||
}
|
||||
|
||||
goBack(context: AuthContext) {
|
||||
context.run('setLogin', null);
|
||||
context.setState(new LoginState());
|
||||
const { isRelogin } = getCredentials(context.getState());
|
||||
|
||||
if (isRelogin) {
|
||||
context.setState(new ChooseAccountState());
|
||||
} else {
|
||||
context.run('setLogin', null);
|
||||
context.setState(new LoginState());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,8 +3,10 @@ import sinon from 'sinon';
|
||||
|
||||
import PasswordState from 'services/authFlow/PasswordState';
|
||||
import CompleteState from 'services/authFlow/CompleteState';
|
||||
import MfaState from 'services/authFlow/MfaState';
|
||||
import LoginState from 'services/authFlow/LoginState';
|
||||
import ForgotPasswordState from 'services/authFlow/ForgotPasswordState';
|
||||
import ChooseAccountState from 'services/authFlow/ChooseAccountState';
|
||||
|
||||
import { bootstrap, expectState, expectNavigate, expectRun } from './helpers';
|
||||
|
||||
@ -82,6 +84,67 @@ describe('PasswordState', () => {
|
||||
|
||||
return expect(state.resolve(context, payload), 'to be fulfilled');
|
||||
});
|
||||
|
||||
it('should go to MfaState if totp required', () => {
|
||||
const expectedLogin = 'foo';
|
||||
const expectedPassword = 'bar';
|
||||
const expectedRememberMe = true;
|
||||
|
||||
context.getState.returns({
|
||||
auth: {
|
||||
credentials: {
|
||||
login: expectedLogin,
|
||||
isTotpRequired: true,
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
expectRun(
|
||||
mock,
|
||||
'login',
|
||||
sinon.match({
|
||||
login: expectedLogin,
|
||||
password: expectedPassword,
|
||||
rememberMe: expectedRememberMe,
|
||||
})
|
||||
).returns(Promise.resolve());
|
||||
expectState(mock, MfaState);
|
||||
|
||||
const payload = {password: expectedPassword, rememberMe: expectedRememberMe};
|
||||
|
||||
return expect(state.resolve(context, payload), 'to be fulfilled');
|
||||
});
|
||||
|
||||
it('should navigate to returnUrl if any', () => {
|
||||
const expectedLogin = 'foo';
|
||||
const expectedPassword = 'bar';
|
||||
const expectedReturnUrl = '/returnUrl';
|
||||
const expectedRememberMe = true;
|
||||
|
||||
context.getState.returns({
|
||||
auth: {
|
||||
credentials: {
|
||||
login: expectedLogin,
|
||||
returnUrl: expectedReturnUrl,
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
expectRun(
|
||||
mock,
|
||||
'login',
|
||||
sinon.match({
|
||||
login: expectedLogin,
|
||||
password: expectedPassword,
|
||||
rememberMe: expectedRememberMe,
|
||||
})
|
||||
).returns(Promise.resolve());
|
||||
expectNavigate(mock, expectedReturnUrl);
|
||||
|
||||
const payload = {password: expectedPassword, rememberMe: expectedRememberMe};
|
||||
|
||||
return expect(state.resolve(context, payload), 'to be fulfilled');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#reject', () => {
|
||||
@ -94,10 +157,33 @@ describe('PasswordState', () => {
|
||||
|
||||
describe('#goBack', () => {
|
||||
it('should transition to login state', () => {
|
||||
context.getState.returns({
|
||||
auth: {
|
||||
credentials: {
|
||||
login: 'foo'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
expectRun(mock, 'setLogin', null);
|
||||
expectState(mock, LoginState);
|
||||
|
||||
state.goBack(context);
|
||||
});
|
||||
|
||||
it('should transition to ChooseAccountState if this is relogin', () => {
|
||||
context.getState.returns({
|
||||
auth: {
|
||||
credentials: {
|
||||
login: 'foo',
|
||||
isRelogin: true,
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
expectState(mock, ChooseAccountState);
|
||||
|
||||
state.goBack(context);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user