#48: account switcher for oauth

This commit is contained in:
SleepWalker
2016-11-13 16:47:56 +02:00
parent 81a5437be0
commit b6b8468904
11 changed files with 101 additions and 19 deletions

View File

@@ -17,6 +17,7 @@ export class AccountSwitcher extends Component {
switchAccount: PropTypes.func.isRequired, switchAccount: PropTypes.func.isRequired,
removeAccount: PropTypes.func.isRequired, removeAccount: PropTypes.func.isRequired,
onAfterAction: PropTypes.func, // called after each action performed 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 accounts: PropTypes.shape({ // TODO: accounts shape
active: PropTypes.shape({ active: PropTypes.shape({
id: PropTypes.number id: PropTypes.number
@@ -36,7 +37,8 @@ export class AccountSwitcher extends Component {
highlightActiveAccount: true, highlightActiveAccount: true,
allowLogout: true, allowLogout: true,
allowAdd: true, allowAdd: true,
onAfterAction() {} onAfterAction() {},
onSwitch() {}
}; };
render() { render() {
@@ -136,7 +138,8 @@ export class AccountSwitcher extends Component {
event.preventDefault(); event.preventDefault();
this.props.switchAccount(account) this.props.switchAccount(account)
.then(() => this.props.onAfterAction()); .then(() => this.props.onAfterAction())
.then(() => this.props.onSwitch(account));
}; };
onRemove = (account) => (event) => { onRemove = (account) => (event) => {

View File

@@ -64,10 +64,7 @@ class PanelTransition extends Component {
payload: PropTypes.object payload: PropTypes.object
})]), })]),
isLoading: PropTypes.bool, isLoading: PropTypes.bool,
login: PropTypes.shape({ login: PropTypes.string
login: PropTypes.string,
password: PropTypes.string
})
}).isRequired, }).isRequired,
user: userShape.isRequired, user: userShape.isRequired,
setErrors: PropTypes.func.isRequired, setErrors: PropTypes.func.isRequired,
@@ -89,10 +86,7 @@ class PanelTransition extends Component {
type: PropTypes.string, type: PropTypes.string,
payload: PropTypes.object payload: PropTypes.object
})]), })]),
login: PropTypes.shape({ login: PropTypes.string
login: PropTypes.string,
password: PropTypes.string
})
}), }),
user: userShape, user: userShape,
requestRedraw: PropTypes.func, requestRedraw: PropTypes.func,

View File

@@ -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 const ERROR = 'auth:error';
export function setErrors(errors) { export function setErrors(errors) {
return { return {

View File

@@ -13,8 +13,6 @@ export default class ChooseAccountBody extends BaseAuthBody {
static panelId = 'chooseAccount'; static panelId = 'chooseAccount';
render() { render() {
const {user} = this.context;
this.context.auth.client = {name: 'foo'}; // TODO: remove me
const {client} = this.context.auth; const {client} = this.context.auth;
return ( return (
@@ -28,9 +26,18 @@ export default class ChooseAccountBody extends BaseAuthBody {
</div> </div>
<div className={styles.accountSwitcherContainer}> <div className={styles.accountSwitcherContainer}>
<AccountSwitcher allowAdd={false} allowLogout={false} highlightActiveAccount={false} /> <AccountSwitcher
allowAdd={false}
allowLogout={false}
highlightActiveAccount={false}
onSwitch={this.onSwitch}
/>
</div> </div>
</div> </div>
); );
} }
onSwitch = (account) => {
this.context.resolve(account);
};
} }

View File

@@ -8,13 +8,15 @@ import {
SET_SCOPES, SET_SCOPES,
SET_LOADING_STATE, SET_LOADING_STATE,
REQUIRE_PERMISSIONS_ACCEPT, REQUIRE_PERMISSIONS_ACCEPT,
SET_LOGIN SET_LOGIN,
SET_SWITCHER
} from './actions'; } from './actions';
export default combineReducers({ export default combineReducers({
login, login,
error, error,
isLoading, isLoading,
isSwitcherEnabled,
client, client,
oauth, oauth,
scopes 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( function isLoading(
state = false, state = false,

View File

@@ -152,14 +152,13 @@ export default class AuthFlow {
this.setState(new ResendActivationState()); this.setState(new ResendActivationState());
break; break;
case '/oauth/choose-account':
break;
case '/': case '/':
case '/login': case '/login':
case '/password': case '/password':
case '/accept-rules': case '/accept-rules':
case '/oauth/permissions': case '/oauth/permissions':
case '/oauth/finish': case '/oauth/finish':
case '/oauth/choose-account':
this.setState(new LoginState()); this.setState(new LoginState());
break; break;

View File

@@ -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());
}
}
}

View File

@@ -1,6 +1,7 @@
import AbstractState from './AbstractState'; import AbstractState from './AbstractState';
import LoginState from './LoginState'; import LoginState from './LoginState';
import PermissionsState from './PermissionsState'; import PermissionsState from './PermissionsState';
import ChooseAccountState from './ChooseAccountState';
import ActivationState from './ActivationState'; import ActivationState from './ActivationState';
import AcceptRulesState from './AcceptRulesState'; import AcceptRulesState from './AcceptRulesState';
import FinishState from './FinishState'; import FinishState from './FinishState';
@@ -22,7 +23,9 @@ export default class CompleteState extends AbstractState {
} else if (user.shouldAcceptRules) { } else if (user.shouldAcceptRules) {
context.setState(new AcceptRulesState()); context.setState(new AcceptRulesState());
} else if (auth.oauth && auth.oauth.clientId) { } 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()); context.setState(new FinishState());
} else { } else {
const data = {}; const data = {};

View File

@@ -13,6 +13,8 @@ export default class LoginState extends AbstractState {
|| /login|password/.test(context.getRequest().path) // TODO: improve me || /login|password/.test(context.getRequest().path) // TODO: improve me
) { ) {
context.navigate('/login'); context.navigate('/login');
} else {
context.setState(new PasswordState());
} }
} }

View File

@@ -1,9 +1,12 @@
import expect from 'unexpected'; import expect from 'unexpected';
import auth from 'components/auth/reducer'; 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, () => { describe(SET_LOGIN, () => {
it('should set login', () => { it('should set login', () => {
const expectedLogin = 'foo'; 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
});
});
});
}); });

View File

@@ -267,6 +267,7 @@ describe('AuthFlow', () => {
'/password': LoginState, '/password': LoginState,
'/accept-rules': LoginState, '/accept-rules': LoginState,
'/oauth/permissions': LoginState, '/oauth/permissions': LoginState,
'/oauth/choose-account': LoginState,
'/oauth/finish': LoginState, '/oauth/finish': LoginState,
'/oauth2/v1/foo': OAuthState, '/oauth2/v1/foo': OAuthState,
'/oauth2/v1': OAuthState, '/oauth2/v1': OAuthState,