#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,
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) => {

View File

@ -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,

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 function setErrors(errors) {
return {

View File

@ -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 {
</div>
<div className={styles.accountSwitcherContainer}>
<AccountSwitcher allowAdd={false} allowLogout={false} highlightActiveAccount={false} />
<AccountSwitcher
allowAdd={false}
allowLogout={false}
highlightActiveAccount={false}
onSwitch={this.onSwitch}
/>
</div>
</div>
);
}
onSwitch = (account) => {
this.context.resolve(account);
};
}

View File

@ -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,

View File

@ -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;

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 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 = {};

View File

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

View File

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

View File

@ -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,