From b6b8468904ee695959b1783e918b1068d941dfcb Mon Sep 17 00:00:00 2001 From: SleepWalker Date: Sun, 13 Nov 2016 16:47:56 +0200 Subject: [PATCH] #48: account switcher for oauth --- src/components/accounts/AccountSwitcher.jsx | 7 +++-- src/components/auth/PanelTransition.jsx | 10 ++---- src/components/auth/actions.js | 8 +++++ .../auth/chooseAccount/ChooseAccountBody.jsx | 13 ++++++-- src/components/auth/reducer.js | 20 +++++++++++- src/services/authFlow/AuthFlow.js | 3 +- src/services/authFlow/ChooseAccountState.js | 20 ++++++++++++ src/services/authFlow/CompleteState.js | 5 ++- src/services/authFlow/LoginState.js | 2 ++ tests/components/auth/reducer.test.js | 31 +++++++++++++++++-- tests/services/authFlow/AuthFlow.test.js | 1 + 11 files changed, 101 insertions(+), 19 deletions(-) create mode 100644 src/services/authFlow/ChooseAccountState.js diff --git a/src/components/accounts/AccountSwitcher.jsx b/src/components/accounts/AccountSwitcher.jsx index 428886f..856a939 100644 --- a/src/components/accounts/AccountSwitcher.jsx +++ b/src/components/accounts/AccountSwitcher.jsx @@ -17,6 +17,7 @@ export class AccountSwitcher extends Component { switchAccount: PropTypes.func.isRequired, removeAccount: PropTypes.func.isRequired, onAfterAction: PropTypes.func, // called after each action performed + onSwitch: PropTypes.func, // called after switching an account. The active account will be passed as arg accounts: PropTypes.shape({ // TODO: accounts shape active: PropTypes.shape({ id: PropTypes.number @@ -36,7 +37,8 @@ export class AccountSwitcher extends Component { highlightActiveAccount: true, allowLogout: true, allowAdd: true, - onAfterAction() {} + onAfterAction() {}, + onSwitch() {} }; render() { @@ -136,7 +138,8 @@ export class AccountSwitcher extends Component { event.preventDefault(); this.props.switchAccount(account) - .then(() => this.props.onAfterAction()); + .then(() => this.props.onAfterAction()) + .then(() => this.props.onSwitch(account)); }; onRemove = (account) => (event) => { diff --git a/src/components/auth/PanelTransition.jsx b/src/components/auth/PanelTransition.jsx index 1e2d795..8d09d73 100644 --- a/src/components/auth/PanelTransition.jsx +++ b/src/components/auth/PanelTransition.jsx @@ -64,10 +64,7 @@ class PanelTransition extends Component { payload: PropTypes.object })]), isLoading: PropTypes.bool, - login: PropTypes.shape({ - login: PropTypes.string, - password: PropTypes.string - }) + login: PropTypes.string }).isRequired, user: userShape.isRequired, setErrors: PropTypes.func.isRequired, @@ -89,10 +86,7 @@ class PanelTransition extends Component { type: PropTypes.string, payload: PropTypes.object })]), - login: PropTypes.shape({ - login: PropTypes.string, - password: PropTypes.string - }) + login: PropTypes.string }), user: userShape, requestRedraw: PropTypes.func, diff --git a/src/components/auth/actions.js b/src/components/auth/actions.js index 1191b65..5642904 100644 --- a/src/components/auth/actions.js +++ b/src/components/auth/actions.js @@ -122,6 +122,14 @@ export function setLogin(login) { }; } +export const SET_SWITCHER = 'auth:setAccountSwitcher'; +export function setAccountSwitcher(isOn) { + return { + type: SET_SWITCHER, + payload: isOn + }; +} + export const ERROR = 'auth:error'; export function setErrors(errors) { return { diff --git a/src/components/auth/chooseAccount/ChooseAccountBody.jsx b/src/components/auth/chooseAccount/ChooseAccountBody.jsx index a09f95d..b80ba1b 100644 --- a/src/components/auth/chooseAccount/ChooseAccountBody.jsx +++ b/src/components/auth/chooseAccount/ChooseAccountBody.jsx @@ -13,8 +13,6 @@ export default class ChooseAccountBody extends BaseAuthBody { static panelId = 'chooseAccount'; render() { - const {user} = this.context; - this.context.auth.client = {name: 'foo'}; // TODO: remove me const {client} = this.context.auth; return ( @@ -28,9 +26,18 @@ export default class ChooseAccountBody extends BaseAuthBody {
- +
); } + + onSwitch = (account) => { + this.context.resolve(account); + }; } diff --git a/src/components/auth/reducer.js b/src/components/auth/reducer.js index 0c884bf..4d9cee3 100644 --- a/src/components/auth/reducer.js +++ b/src/components/auth/reducer.js @@ -8,13 +8,15 @@ import { SET_SCOPES, SET_LOADING_STATE, REQUIRE_PERMISSIONS_ACCEPT, - SET_LOGIN + SET_LOGIN, + SET_SWITCHER } from './actions'; export default combineReducers({ login, error, isLoading, + isSwitcherEnabled, client, oauth, scopes @@ -54,6 +56,22 @@ function login( } } +function isSwitcherEnabled( + state = true, + {type, payload = false} +) { + switch (type) { + case SET_SWITCHER: + if (typeof payload !== 'boolean') { + throw new Error('Expected payload of boolean type'); + } + + return payload; + + default: + return state; + } +} function isLoading( state = false, diff --git a/src/services/authFlow/AuthFlow.js b/src/services/authFlow/AuthFlow.js index a305a4d..32e22e0 100644 --- a/src/services/authFlow/AuthFlow.js +++ b/src/services/authFlow/AuthFlow.js @@ -152,14 +152,13 @@ export default class AuthFlow { this.setState(new ResendActivationState()); break; - case '/oauth/choose-account': - break; case '/': case '/login': case '/password': case '/accept-rules': case '/oauth/permissions': case '/oauth/finish': + case '/oauth/choose-account': this.setState(new LoginState()); break; diff --git a/src/services/authFlow/ChooseAccountState.js b/src/services/authFlow/ChooseAccountState.js new file mode 100644 index 0000000..069b3a6 --- /dev/null +++ b/src/services/authFlow/ChooseAccountState.js @@ -0,0 +1,20 @@ +import AbstractState from './AbstractState'; +import LoginState from './LoginState'; +import CompleteState from './CompleteState'; + +export default class ChooseAccountState extends AbstractState { + enter(context) { + context.navigate('/oauth/choose-account'); + } + + resolve(context, payload) { + context.run('setAccountSwitcher', false); + + if (payload.id) { + context.setState(new CompleteState()); + } else { + context.navigate('/login'); + context.setState(new LoginState()); + } + } +} diff --git a/src/services/authFlow/CompleteState.js b/src/services/authFlow/CompleteState.js index 248090f..bb4bc7d 100644 --- a/src/services/authFlow/CompleteState.js +++ b/src/services/authFlow/CompleteState.js @@ -1,6 +1,7 @@ import AbstractState from './AbstractState'; import LoginState from './LoginState'; import PermissionsState from './PermissionsState'; +import ChooseAccountState from './ChooseAccountState'; import ActivationState from './ActivationState'; import AcceptRulesState from './AcceptRulesState'; import FinishState from './FinishState'; @@ -22,7 +23,9 @@ export default class CompleteState extends AbstractState { } else if (user.shouldAcceptRules) { context.setState(new AcceptRulesState()); } else if (auth.oauth && auth.oauth.clientId) { - if (auth.oauth.code) { + if (auth.isSwitcherEnabled) { + context.setState(new ChooseAccountState()); + } else if (auth.oauth.code) { context.setState(new FinishState()); } else { const data = {}; diff --git a/src/services/authFlow/LoginState.js b/src/services/authFlow/LoginState.js index bbb5c5a..cb63349 100644 --- a/src/services/authFlow/LoginState.js +++ b/src/services/authFlow/LoginState.js @@ -13,6 +13,8 @@ export default class LoginState extends AbstractState { || /login|password/.test(context.getRequest().path) // TODO: improve me ) { context.navigate('/login'); + } else { + context.setState(new PasswordState()); } } diff --git a/tests/components/auth/reducer.test.js b/tests/components/auth/reducer.test.js index 66024a9..0cb40ab 100644 --- a/tests/components/auth/reducer.test.js +++ b/tests/components/auth/reducer.test.js @@ -1,9 +1,12 @@ import expect from 'unexpected'; import auth from 'components/auth/reducer'; -import { setLogin, SET_LOGIN } from 'components/auth/actions'; +import { + setLogin, SET_LOGIN, + setAccountSwitcher, SET_SWITCHER +} from 'components/auth/actions'; -describe('auth reducer', () => { +describe('components/auth/reducer', () => { describe(SET_LOGIN, () => { it('should set login', () => { const expectedLogin = 'foo'; @@ -13,4 +16,28 @@ describe('auth reducer', () => { }); }); }); + + describe(SET_SWITCHER, () => { + it('should be enabled by default', () => + expect(auth(undefined, {}), 'to satisfy', { + isSwitcherEnabled: true + }) + ); + + it('should enable switcher', () => { + const expectedValue = true; + + expect(auth(undefined, setAccountSwitcher(expectedValue)), 'to satisfy', { + isSwitcherEnabled: expectedValue + }); + }); + + it('should disable switcher', () => { + const expectedValue = false; + + expect(auth(undefined, setAccountSwitcher(expectedValue)), 'to satisfy', { + isSwitcherEnabled: expectedValue + }); + }); + }); }); diff --git a/tests/services/authFlow/AuthFlow.test.js b/tests/services/authFlow/AuthFlow.test.js index 3c3fe82..7d00a2c 100644 --- a/tests/services/authFlow/AuthFlow.test.js +++ b/tests/services/authFlow/AuthFlow.test.js @@ -267,6 +267,7 @@ describe('AuthFlow', () => { '/password': LoginState, '/accept-rules': LoginState, '/oauth/permissions': LoginState, + '/oauth/choose-account': LoginState, '/oauth/finish': LoginState, '/oauth2/v1/foo': OAuthState, '/oauth2/v1': OAuthState,