mirror of
https://github.com/elyby/accounts-frontend.git
synced 2025-05-05 17:23:44 +05:30
Merge branch '245-multiacc-improvement' into develop
Conflicts: frontend/src/index.js
This commit is contained in:
commit
477b79918f
@ -3,8 +3,19 @@ import { routeActions } from 'react-router-redux';
|
|||||||
import authentication from 'services/api/authentication';
|
import authentication from 'services/api/authentication';
|
||||||
import { updateUser, setGuest } from 'components/user/actions';
|
import { updateUser, setGuest } from 'components/user/actions';
|
||||||
import { setLocale } from 'components/i18n/actions';
|
import { setLocale } from 'components/i18n/actions';
|
||||||
|
import { setAccountSwitcher } from 'components/auth/actions';
|
||||||
import logger from 'services/logger';
|
import logger from 'services/logger';
|
||||||
|
|
||||||
|
import {
|
||||||
|
add,
|
||||||
|
remove,
|
||||||
|
activate,
|
||||||
|
reset,
|
||||||
|
updateToken
|
||||||
|
} from 'components/accounts/actions/pure-actions';
|
||||||
|
|
||||||
|
export { updateToken };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {object} Account
|
* @typedef {object} Account
|
||||||
* @property {string} id
|
* @property {string} id
|
||||||
@ -22,7 +33,7 @@ import logger from 'services/logger';
|
|||||||
* @return {function}
|
* @return {function}
|
||||||
*/
|
*/
|
||||||
export function authenticate({token, refreshToken}) {
|
export function authenticate({token, refreshToken}) {
|
||||||
return (dispatch) =>
|
return (dispatch, getState) =>
|
||||||
authentication.validateToken({token, refreshToken})
|
authentication.validateToken({token, refreshToken})
|
||||||
.catch((resp) => {
|
.catch((resp) => {
|
||||||
logger.warn('Error validating token during auth', {
|
logger.warn('Error validating token during auth', {
|
||||||
@ -46,6 +57,8 @@ export function authenticate({token, refreshToken}) {
|
|||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.then(({user, account}) => {
|
.then(({user, account}) => {
|
||||||
|
const {auth} = getState();
|
||||||
|
|
||||||
dispatch(add(account));
|
dispatch(add(account));
|
||||||
dispatch(activate(account));
|
dispatch(activate(account));
|
||||||
dispatch(updateUser(user));
|
dispatch(updateUser(user));
|
||||||
@ -58,12 +71,22 @@ export function authenticate({token, refreshToken}) {
|
|||||||
sessionStorage.setItem(`stranger${account.id}`, 1);
|
sessionStorage.setItem(`stranger${account.id}`, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (auth && auth.oauth && auth.oauth.clientId) {
|
||||||
|
// if we authenticating during oauth, we disable account chooser
|
||||||
|
// because user probably has made his choise now
|
||||||
|
// this may happen, when user registers, logs in or uses account
|
||||||
|
// chooser panel during oauth
|
||||||
|
dispatch(setAccountSwitcher(false));
|
||||||
|
}
|
||||||
|
|
||||||
return dispatch(setLocale(user.lang))
|
return dispatch(setLocale(user.lang))
|
||||||
.then(() => account);
|
.then(() => account);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Remove one account from current user's account list
|
||||||
|
*
|
||||||
* @param {Account} account
|
* @param {Account} account
|
||||||
*
|
*
|
||||||
* @return {function}
|
* @return {function}
|
||||||
@ -135,73 +158,3 @@ export function logoutStrangers() {
|
|||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ADD = 'accounts:add';
|
|
||||||
/**
|
|
||||||
* @api private
|
|
||||||
*
|
|
||||||
* @param {Account} account
|
|
||||||
*
|
|
||||||
* @return {object} - action definition
|
|
||||||
*/
|
|
||||||
export function add(account) {
|
|
||||||
return {
|
|
||||||
type: ADD,
|
|
||||||
payload: account
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export const REMOVE = 'accounts:remove';
|
|
||||||
/**
|
|
||||||
* @api private
|
|
||||||
*
|
|
||||||
* @param {Account} account
|
|
||||||
*
|
|
||||||
* @return {object} - action definition
|
|
||||||
*/
|
|
||||||
export function remove(account) {
|
|
||||||
return {
|
|
||||||
type: REMOVE,
|
|
||||||
payload: account
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ACTIVATE = 'accounts:activate';
|
|
||||||
/**
|
|
||||||
* @api private
|
|
||||||
*
|
|
||||||
* @param {Account} account
|
|
||||||
*
|
|
||||||
* @return {object} - action definition
|
|
||||||
*/
|
|
||||||
export function activate(account) {
|
|
||||||
return {
|
|
||||||
type: ACTIVATE,
|
|
||||||
payload: account
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export const RESET = 'accounts:reset';
|
|
||||||
/**
|
|
||||||
* @api private
|
|
||||||
*
|
|
||||||
* @return {object} - action definition
|
|
||||||
*/
|
|
||||||
export function reset() {
|
|
||||||
return {
|
|
||||||
type: RESET
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export const UPDATE_TOKEN = 'accounts:updateToken';
|
|
||||||
/**
|
|
||||||
* @param {string} token
|
|
||||||
*
|
|
||||||
* @return {object} - action definition
|
|
||||||
*/
|
|
||||||
export function updateToken(token) {
|
|
||||||
return {
|
|
||||||
type: UPDATE_TOKEN,
|
|
||||||
payload: token
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
69
src/components/accounts/actions/pure-actions.js
Normal file
69
src/components/accounts/actions/pure-actions.js
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
export const ADD = 'accounts:add';
|
||||||
|
/**
|
||||||
|
* @api private
|
||||||
|
*
|
||||||
|
* @param {Account} account
|
||||||
|
*
|
||||||
|
* @return {object} - action definition
|
||||||
|
*/
|
||||||
|
export function add(account) {
|
||||||
|
return {
|
||||||
|
type: ADD,
|
||||||
|
payload: account
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const REMOVE = 'accounts:remove';
|
||||||
|
/**
|
||||||
|
* @api private
|
||||||
|
*
|
||||||
|
* @param {Account} account
|
||||||
|
*
|
||||||
|
* @return {object} - action definition
|
||||||
|
*/
|
||||||
|
export function remove(account) {
|
||||||
|
return {
|
||||||
|
type: REMOVE,
|
||||||
|
payload: account
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ACTIVATE = 'accounts:activate';
|
||||||
|
/**
|
||||||
|
* @api private
|
||||||
|
*
|
||||||
|
* @param {Account} account
|
||||||
|
*
|
||||||
|
* @return {object} - action definition
|
||||||
|
*/
|
||||||
|
export function activate(account) {
|
||||||
|
return {
|
||||||
|
type: ACTIVATE,
|
||||||
|
payload: account
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RESET = 'accounts:reset';
|
||||||
|
/**
|
||||||
|
* @api private
|
||||||
|
*
|
||||||
|
* @return {object} - action definition
|
||||||
|
*/
|
||||||
|
export function reset() {
|
||||||
|
return {
|
||||||
|
type: RESET
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const UPDATE_TOKEN = 'accounts:updateToken';
|
||||||
|
/**
|
||||||
|
* @param {string} token
|
||||||
|
*
|
||||||
|
* @return {object} - action definition
|
||||||
|
*/
|
||||||
|
export function updateToken(token) {
|
||||||
|
return {
|
||||||
|
type: UPDATE_TOKEN,
|
||||||
|
payload: token
|
||||||
|
};
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
import { ADD, REMOVE, ACTIVATE, RESET, UPDATE_TOKEN } from './actions';
|
import { ADD, REMOVE, ACTIVATE, RESET, UPDATE_TOKEN } from './actions/pure-actions';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {AccountsState}
|
* @typedef {AccountsState}
|
||||||
|
@ -67,6 +67,9 @@ class PanelTransition extends Component {
|
|||||||
login: PropTypes.string
|
login: PropTypes.string
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
user: userShape.isRequired,
|
user: userShape.isRequired,
|
||||||
|
accounts: PropTypes.shape({
|
||||||
|
available: PropTypes.array
|
||||||
|
}),
|
||||||
setErrors: PropTypes.func.isRequired,
|
setErrors: PropTypes.func.isRequired,
|
||||||
clearErrors: PropTypes.func.isRequired,
|
clearErrors: PropTypes.func.isRequired,
|
||||||
resolve: PropTypes.func.isRequired,
|
resolve: PropTypes.func.isRequired,
|
||||||
@ -320,9 +323,15 @@ class PanelTransition extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getHeader({key, style, data}) {
|
getHeader({key, style, data}) {
|
||||||
const {Title, hasBackButton} = data;
|
const {Title} = data;
|
||||||
const {transformSpring} = style;
|
const {transformSpring} = style;
|
||||||
|
|
||||||
|
let {hasBackButton} = data;
|
||||||
|
|
||||||
|
if (typeof hasBackButton === 'function') {
|
||||||
|
hasBackButton = hasBackButton(this.props);
|
||||||
|
}
|
||||||
|
|
||||||
style = {
|
style = {
|
||||||
...this.getDefaultTransitionStyles(key, style),
|
...this.getDefaultTransitionStyles(key, style),
|
||||||
opacity: 1 // reset default
|
opacity: 1 // reset default
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { routeActions } from 'react-router-redux';
|
import { routeActions } from 'react-router-redux';
|
||||||
|
|
||||||
import logger from 'services/logger';
|
import logger from 'services/logger';
|
||||||
|
import history from 'services/history';
|
||||||
import { updateUser, acceptRules as userAcceptRules } from 'components/user/actions';
|
import { updateUser, acceptRules as userAcceptRules } from 'components/user/actions';
|
||||||
import { authenticate, logoutAll } from 'components/accounts/actions';
|
import { authenticate, logoutAll } from 'components/accounts/actions';
|
||||||
import authentication from 'services/api/authentication';
|
import authentication from 'services/api/authentication';
|
||||||
@ -11,6 +12,25 @@ import dispatchBsod from 'components/ui/bsod/dispatchBsod';
|
|||||||
export { updateUser } from 'components/user/actions';
|
export { updateUser } from 'components/user/actions';
|
||||||
export { authenticate, logoutAll as logout } from 'components/accounts/actions';
|
export { authenticate, logoutAll as logout } from 'components/accounts/actions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reoutes user to the previous page if it is possible
|
||||||
|
*
|
||||||
|
* @param {string} fallbackUrl - an url to route user to if goBack is not possible
|
||||||
|
*
|
||||||
|
* @return {object} - action definition
|
||||||
|
*/
|
||||||
|
export function goBack(fallbackUrl = null) {
|
||||||
|
if (history.canGoBack()) {
|
||||||
|
return routeActions.goBack();
|
||||||
|
} else if (fallbackUrl) {
|
||||||
|
return routeActions.push(fallbackUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'noop'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function login({login = '', password = '', rememberMe = false}) {
|
export function login({login = '', password = '', rememberMe = false}) {
|
||||||
const PASSWORD_REQUIRED = 'error.password_required';
|
const PASSWORD_REQUIRED = 'error.password_required';
|
||||||
const LOGIN_REQUIRED = 'error.login_required';
|
const LOGIN_REQUIRED = 'error.login_required';
|
||||||
|
@ -2,5 +2,6 @@
|
|||||||
"chooseAccountTitle": "Choose an account",
|
"chooseAccountTitle": "Choose an account",
|
||||||
"addAccount": "Log into another account",
|
"addAccount": "Log into another account",
|
||||||
"logoutAll": "Log out from all accounts",
|
"logoutAll": "Log out from all accounts",
|
||||||
|
"createNewAccount": "Create new account",
|
||||||
"description": "You have logged in into multiple accounts. Please choose the one, you want to use to authorize {appName}"
|
"description": "You have logged in into multiple accounts. Please choose the one, you want to use to authorize {appName}"
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,11 @@ export default factory({
|
|||||||
},
|
},
|
||||||
links: [
|
links: [
|
||||||
{
|
{
|
||||||
label: messages.logoutAll
|
label: messages.createNewAccount
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: messages.logoutAll,
|
||||||
|
payload: {logout: true}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
import { Input } from 'components/ui/form';
|
import { Input } from 'components/ui/form';
|
||||||
import BaseAuthBody from 'components/auth/BaseAuthBody';
|
import BaseAuthBody from 'components/auth/BaseAuthBody';
|
||||||
|
|
||||||
@ -8,6 +6,9 @@ import messages from './Login.intl.json';
|
|||||||
export default class LoginBody extends BaseAuthBody {
|
export default class LoginBody extends BaseAuthBody {
|
||||||
static displayName = 'LoginBody';
|
static displayName = 'LoginBody';
|
||||||
static panelId = 'login';
|
static panelId = 'login';
|
||||||
|
static hasGoBack = (state) => {
|
||||||
|
return !state.user.isGuest;
|
||||||
|
};
|
||||||
|
|
||||||
autoFocusField = 'login';
|
autoFocusField = 'login';
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { Component } from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { FormattedMessage as Message } from 'react-intl';
|
import { FormattedMessage as Message } from 'react-intl';
|
||||||
import { Link } from 'react-router';
|
import { Link } from 'react-router';
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
|
||||||
import { FormattedMessage as Message } from 'react-intl';
|
import { FormattedMessage as Message } from 'react-intl';
|
||||||
import { Link } from 'react-router';
|
|
||||||
import Helmet from 'react-helmet';
|
import Helmet from 'react-helmet';
|
||||||
|
|
||||||
import { Input, Button, Checkbox, Form, FormModel } from 'components/ui/form';
|
import { Input, Button, Checkbox, Form, FormModel } from 'components/ui/form';
|
||||||
|
@ -14,6 +14,9 @@ import bsodFactory from 'components/ui/bsod/factory';
|
|||||||
import loader from 'services/loader';
|
import loader from 'services/loader';
|
||||||
import logger from 'services/logger';
|
import logger from 'services/logger';
|
||||||
import font from 'services/font';
|
import font from 'services/font';
|
||||||
|
import history from 'services/history';
|
||||||
|
|
||||||
|
history.init();
|
||||||
|
|
||||||
logger.init({
|
logger.init({
|
||||||
sentryCdn: window.SENTRY_CDN
|
sentryCdn: window.SENTRY_CDN
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import AbstractState from './AbstractState';
|
import AbstractState from './AbstractState';
|
||||||
import LoginState from './LoginState';
|
import LoginState from './LoginState';
|
||||||
import CompleteState from './CompleteState';
|
import CompleteState from './CompleteState';
|
||||||
|
import RegisterState from './RegisterState';
|
||||||
|
|
||||||
export default class ChooseAccountState extends AbstractState {
|
export default class ChooseAccountState extends AbstractState {
|
||||||
enter(context) {
|
enter(context) {
|
||||||
@ -8,9 +9,6 @@ export default class ChooseAccountState extends AbstractState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
resolve(context, payload) {
|
resolve(context, payload) {
|
||||||
// do not ask again after user adds account, or chooses an existed one
|
|
||||||
context.run('setAccountSwitcher', false);
|
|
||||||
|
|
||||||
if (payload.id) {
|
if (payload.id) {
|
||||||
context.setState(new CompleteState());
|
context.setState(new CompleteState());
|
||||||
} else {
|
} else {
|
||||||
@ -19,7 +17,16 @@ export default class ChooseAccountState extends AbstractState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reject(context) {
|
/**
|
||||||
context.run('logout');
|
* @param {object} context
|
||||||
|
* @param {object} payload
|
||||||
|
* @param {bool} [payload.logout=false]
|
||||||
|
*/
|
||||||
|
reject(context, payload = {}) {
|
||||||
|
if (payload.logout) {
|
||||||
|
context.run('logout');
|
||||||
|
} else {
|
||||||
|
context.setState(new RegisterState());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ export default class CompleteState extends AbstractState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enter(context) {
|
enter(context) {
|
||||||
const {auth = {}, user, accounts} = context.getState();
|
const {auth = {}, user} = context.getState();
|
||||||
|
|
||||||
if (user.isGuest) {
|
if (user.isGuest) {
|
||||||
context.setState(new LoginState());
|
context.setState(new LoginState());
|
||||||
@ -26,67 +26,75 @@ 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) {
|
||||||
let isSwitcherEnabled = auth.isSwitcherEnabled;
|
return this.processOAuth(context);
|
||||||
|
|
||||||
if (auth.oauth.loginHint) {
|
|
||||||
const account = accounts.available.filter((account) =>
|
|
||||||
account.id === auth.oauth.loginHint * 1
|
|
||||||
|| account.email === auth.oauth.loginHint
|
|
||||||
|| account.username === auth.oauth.loginHint
|
|
||||||
)[0];
|
|
||||||
|
|
||||||
if (account) {
|
|
||||||
// disable switching, because we are know the account, user must be authorized with
|
|
||||||
context.run('setAccountSwitcher', false);
|
|
||||||
isSwitcherEnabled = false;
|
|
||||||
|
|
||||||
if (account.id !== accounts.active.id) {
|
|
||||||
// lets switch user to an account, that is needed for auth
|
|
||||||
return context.run('authenticate', account)
|
|
||||||
.then(() => context.setState(new CompleteState()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isSwitcherEnabled
|
|
||||||
&& (accounts.available.length > 1
|
|
||||||
|| auth.oauth.prompt.includes(PROMPT_ACCOUNT_CHOOSE)
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
context.setState(new ChooseAccountState());
|
|
||||||
} else if (auth.oauth.code) {
|
|
||||||
context.setState(new FinishState());
|
|
||||||
} else {
|
|
||||||
const data = {};
|
|
||||||
if (typeof this.isPermissionsAccepted !== 'undefined') {
|
|
||||||
data.accept = this.isPermissionsAccepted;
|
|
||||||
} else if (auth.oauth.acceptRequired || auth.oauth.prompt.includes(PROMPT_PERMISSIONS)) {
|
|
||||||
context.setState(new PermissionsState());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// TODO: it seams that oAuthComplete may be a separate state
|
|
||||||
return context.run('oAuthComplete', data).then((resp) => {
|
|
||||||
// TODO: пусть в стейт попадает флаг или тип авторизации
|
|
||||||
// вместо волшебства над редирект урлой
|
|
||||||
if (resp.redirectUri.indexOf('static_page') === 0) {
|
|
||||||
context.setState(new FinishState());
|
|
||||||
} else {
|
|
||||||
return new Promise(() => {
|
|
||||||
// do not resolve promise to make loader visible and
|
|
||||||
// overcome app rendering
|
|
||||||
context.run('redirect', resp.redirectUri);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, (resp) => {
|
|
||||||
if (resp.unauthorized) {
|
|
||||||
context.setState(new LoginState());
|
|
||||||
} else if (resp.acceptRequired) {
|
|
||||||
context.setState(new PermissionsState());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
context.navigate('/');
|
context.navigate('/');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
processOAuth(context) {
|
||||||
|
const {auth = {}, accounts} = context.getState();
|
||||||
|
|
||||||
|
let isSwitcherEnabled = auth.isSwitcherEnabled;
|
||||||
|
const loginHint = auth.oauth.loginHint;
|
||||||
|
|
||||||
|
if (loginHint) {
|
||||||
|
const account = accounts.available.filter((account) =>
|
||||||
|
account.id === loginHint * 1
|
||||||
|
|| account.email === loginHint
|
||||||
|
|| account.username === loginHint
|
||||||
|
)[0];
|
||||||
|
|
||||||
|
if (account) {
|
||||||
|
// disable switching, because we are know the account, user must be authorized with
|
||||||
|
context.run('setAccountSwitcher', false);
|
||||||
|
isSwitcherEnabled = false;
|
||||||
|
|
||||||
|
if (account.id !== accounts.active.id) {
|
||||||
|
// lets switch user to an account, that is needed for auth
|
||||||
|
return context.run('authenticate', account)
|
||||||
|
.then(() => context.setState(new CompleteState()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSwitcherEnabled
|
||||||
|
&& (accounts.available.length > 1
|
||||||
|
|| auth.oauth.prompt.includes(PROMPT_ACCOUNT_CHOOSE)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
context.setState(new ChooseAccountState());
|
||||||
|
} else if (auth.oauth.code) {
|
||||||
|
context.setState(new FinishState());
|
||||||
|
} else {
|
||||||
|
const data = {};
|
||||||
|
if (typeof this.isPermissionsAccepted !== 'undefined') {
|
||||||
|
data.accept = this.isPermissionsAccepted;
|
||||||
|
} else if (auth.oauth.acceptRequired || auth.oauth.prompt.includes(PROMPT_PERMISSIONS)) {
|
||||||
|
context.setState(new PermissionsState());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: it seams that oAuthComplete may be a separate state
|
||||||
|
return context.run('oAuthComplete', data).then((resp) => {
|
||||||
|
// TODO: пусть в стейт попадает флаг или тип авторизации
|
||||||
|
// вместо волшебства над редирект урлой
|
||||||
|
if (resp.redirectUri.indexOf('static_page') === 0) {
|
||||||
|
context.setState(new FinishState());
|
||||||
|
} else {
|
||||||
|
return new Promise(() => {
|
||||||
|
// do not resolve promise to make loader visible and
|
||||||
|
// overcome app rendering
|
||||||
|
context.run('redirect', resp.redirectUri);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, (resp) => {
|
||||||
|
if (resp.unauthorized) {
|
||||||
|
context.setState(new LoginState());
|
||||||
|
} else if (resp.acceptRequired) {
|
||||||
|
context.setState(new PermissionsState());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,15 +7,16 @@ export default class LoginState extends AbstractState {
|
|||||||
enter(context) {
|
enter(context) {
|
||||||
const {auth, user} = context.getState();
|
const {auth, user} = context.getState();
|
||||||
|
|
||||||
|
const isUserAddsSecondAccount = !user.isGuest
|
||||||
|
&& /login|password/.test(context.getRequest().path); // TODO: improve me
|
||||||
|
|
||||||
// TODO: it may not allow user to leave password state till he click back or enters password
|
// TODO: it may not allow user to leave password state till he click back or enters password
|
||||||
if (auth.login) {
|
if (auth.login) {
|
||||||
context.setState(new PasswordState());
|
context.setState(new PasswordState());
|
||||||
} else if (user.isGuest
|
} else if (user.isGuest || isUserAddsSecondAccount) {
|
||||||
// for the case, when user is logged in and wants to add a new aacount
|
|
||||||
|| /login|password/.test(context.getRequest().path) // TODO: improve me
|
|
||||||
) {
|
|
||||||
context.navigate('/login');
|
context.navigate('/login');
|
||||||
} else {
|
} else {
|
||||||
|
// can not detect needed state. Delegating decision to the next state
|
||||||
context.setState(new PasswordState());
|
context.setState(new PasswordState());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -25,4 +26,8 @@ export default class LoginState extends AbstractState {
|
|||||||
.then(() => context.setState(new PasswordState()))
|
.then(() => context.setState(new PasswordState()))
|
||||||
.catch((err = {}) => err.errors || logger.warn(err));
|
.catch((err = {}) => err.errors || logger.warn(err));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
goBack(context) {
|
||||||
|
context.run('goBack', '/');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ export default class PasswordState extends AbstractState {
|
|||||||
resolve(context, {password, rememberMe}) {
|
resolve(context, {password, rememberMe}) {
|
||||||
const {auth: {login}} = context.getState();
|
const {auth: {login}} = context.getState();
|
||||||
|
|
||||||
context.run('login', {
|
return context.run('login', {
|
||||||
password,
|
password,
|
||||||
rememberMe,
|
rememberMe,
|
||||||
login
|
login
|
||||||
|
@ -7,13 +7,7 @@ import ResendActivationState from './ResendActivationState';
|
|||||||
|
|
||||||
export default class RegisterState extends AbstractState {
|
export default class RegisterState extends AbstractState {
|
||||||
enter(context) {
|
enter(context) {
|
||||||
const {user} = context.getState();
|
context.navigate('/register');
|
||||||
|
|
||||||
if (user.isGuest) {
|
|
||||||
context.navigate('/register');
|
|
||||||
} else {
|
|
||||||
context.setState(new CompleteState());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve(context, payload) {
|
resolve(context, payload) {
|
||||||
|
17
src/services/history.js
Normal file
17
src/services/history.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* A helper wrapper service around window.history
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default {
|
||||||
|
init() {
|
||||||
|
this.initialLength = window.history.length;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {bool} - whether history.back() can be safetly called
|
||||||
|
*/
|
||||||
|
canGoBack() {
|
||||||
|
return document.referrer.includes(`${location.protocol}//${location.host}`)
|
||||||
|
|| this.initialLength < window.history.length;
|
||||||
|
}
|
||||||
|
};
|
@ -3,21 +3,23 @@ import sinon from 'sinon';
|
|||||||
|
|
||||||
import { routeActions } from 'react-router-redux';
|
import { routeActions } from 'react-router-redux';
|
||||||
|
|
||||||
import accounts from 'services/api/accounts';
|
|
||||||
import authentication from 'services/api/authentication';
|
import authentication from 'services/api/authentication';
|
||||||
import {
|
import {
|
||||||
authenticate,
|
authenticate,
|
||||||
revoke,
|
revoke,
|
||||||
add, ADD,
|
|
||||||
activate, ACTIVATE,
|
|
||||||
remove,
|
|
||||||
reset,
|
|
||||||
logoutAll,
|
logoutAll,
|
||||||
logoutStrangers
|
logoutStrangers
|
||||||
} from 'components/accounts/actions';
|
} from 'components/accounts/actions';
|
||||||
|
import {
|
||||||
|
add, ADD,
|
||||||
|
activate, ACTIVATE,
|
||||||
|
remove,
|
||||||
|
reset
|
||||||
|
} from 'components/accounts/actions/pure-actions';
|
||||||
import { SET_LOCALE } from 'components/i18n/actions';
|
import { SET_LOCALE } from 'components/i18n/actions';
|
||||||
|
|
||||||
import { updateUser, setUser } from 'components/user/actions';
|
import { updateUser, setUser } from 'components/user/actions';
|
||||||
|
import { setAccountSwitcher } from 'components/auth/actions';
|
||||||
|
|
||||||
const account = {
|
const account = {
|
||||||
id: 1,
|
id: 1,
|
||||||
@ -66,7 +68,7 @@ describe('components/accounts/actions', () => {
|
|||||||
|
|
||||||
describe('#authenticate()', () => {
|
describe('#authenticate()', () => {
|
||||||
it('should request user state using token', () =>
|
it('should request user state using token', () =>
|
||||||
authenticate(account)(dispatch).then(() =>
|
authenticate(account)(dispatch, getState).then(() =>
|
||||||
expect(authentication.validateToken, 'to have a call satisfying', [
|
expect(authentication.validateToken, 'to have a call satisfying', [
|
||||||
{token: account.token, refreshToken: account.refreshToken}
|
{token: account.token, refreshToken: account.refreshToken}
|
||||||
])
|
])
|
||||||
@ -74,7 +76,7 @@ describe('components/accounts/actions', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
it(`dispatches ${ADD} action`, () =>
|
it(`dispatches ${ADD} action`, () =>
|
||||||
authenticate(account)(dispatch).then(() =>
|
authenticate(account)(dispatch, getState).then(() =>
|
||||||
expect(dispatch, 'to have a call satisfying', [
|
expect(dispatch, 'to have a call satisfying', [
|
||||||
add(account)
|
add(account)
|
||||||
])
|
])
|
||||||
@ -82,7 +84,7 @@ describe('components/accounts/actions', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
it(`dispatches ${ACTIVATE} action`, () =>
|
it(`dispatches ${ACTIVATE} action`, () =>
|
||||||
authenticate(account)(dispatch).then(() =>
|
authenticate(account)(dispatch, getState).then(() =>
|
||||||
expect(dispatch, 'to have a call satisfying', [
|
expect(dispatch, 'to have a call satisfying', [
|
||||||
activate(account)
|
activate(account)
|
||||||
])
|
])
|
||||||
@ -90,7 +92,7 @@ describe('components/accounts/actions', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
it(`dispatches ${SET_LOCALE} action`, () =>
|
it(`dispatches ${SET_LOCALE} action`, () =>
|
||||||
authenticate(account)(dispatch).then(() =>
|
authenticate(account)(dispatch, getState).then(() =>
|
||||||
expect(dispatch, 'to have a call satisfying', [
|
expect(dispatch, 'to have a call satisfying', [
|
||||||
{type: SET_LOCALE, payload: {locale: 'be'}}
|
{type: SET_LOCALE, payload: {locale: 'be'}}
|
||||||
])
|
])
|
||||||
@ -98,7 +100,7 @@ describe('components/accounts/actions', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
it('should update user state', () =>
|
it('should update user state', () =>
|
||||||
authenticate(account)(dispatch).then(() =>
|
authenticate(account)(dispatch, getState).then(() =>
|
||||||
expect(dispatch, 'to have a call satisfying', [
|
expect(dispatch, 'to have a call satisfying', [
|
||||||
updateUser({...user, isGuest: false})
|
updateUser({...user, isGuest: false})
|
||||||
])
|
])
|
||||||
@ -106,7 +108,7 @@ describe('components/accounts/actions', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
it('resolves with account', () =>
|
it('resolves with account', () =>
|
||||||
authenticate(account)(dispatch).then((resp) =>
|
authenticate(account)(dispatch, getState).then((resp) =>
|
||||||
expect(resp, 'to equal', account)
|
expect(resp, 'to equal', account)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -114,7 +116,7 @@ describe('components/accounts/actions', () => {
|
|||||||
it('rejects when bad auth data', () => {
|
it('rejects when bad auth data', () => {
|
||||||
authentication.validateToken.returns(Promise.reject({}));
|
authentication.validateToken.returns(Promise.reject({}));
|
||||||
|
|
||||||
return expect(authenticate(account)(dispatch), 'to be rejected').then(() => {
|
return expect(authenticate(account)(dispatch, getState), 'to be rejected').then(() => {
|
||||||
expect(dispatch, 'to have a call satisfying', [
|
expect(dispatch, 'to have a call satisfying', [
|
||||||
{payload: {isGuest: true}},
|
{payload: {isGuest: true}},
|
||||||
]);
|
]);
|
||||||
@ -133,11 +135,37 @@ describe('components/accounts/actions', () => {
|
|||||||
|
|
||||||
sessionStorage.removeItem(expectedKey);
|
sessionStorage.removeItem(expectedKey);
|
||||||
|
|
||||||
return authenticate(account)(dispatch).then(() => {
|
return authenticate(account)(dispatch, getState).then(() => {
|
||||||
expect(sessionStorage.getItem(expectedKey), 'not to be null');
|
expect(sessionStorage.getItem(expectedKey), 'not to be null');
|
||||||
sessionStorage.removeItem(expectedKey);
|
sessionStorage.removeItem(expectedKey);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('when user authenticated during oauth', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
getState.returns({
|
||||||
|
accounts: {
|
||||||
|
available: [],
|
||||||
|
active: null
|
||||||
|
},
|
||||||
|
user: {},
|
||||||
|
auth: {
|
||||||
|
oauth: {
|
||||||
|
clientId: 'ely.by',
|
||||||
|
prompt: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should dispatch setAccountSwitcher', () =>
|
||||||
|
authenticate(account)(dispatch, getState).then(() =>
|
||||||
|
expect(dispatch, 'to have a call satisfying', [
|
||||||
|
setAccountSwitcher(false)
|
||||||
|
])
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#revoke()', () => {
|
describe('#revoke()', () => {
|
||||||
|
@ -2,9 +2,12 @@ import expect from 'unexpected';
|
|||||||
|
|
||||||
import accounts from 'components/accounts/reducer';
|
import accounts from 'components/accounts/reducer';
|
||||||
import {
|
import {
|
||||||
updateToken, add, remove, activate, reset,
|
updateToken
|
||||||
ADD, REMOVE, ACTIVATE, UPDATE_TOKEN, RESET
|
|
||||||
} from 'components/accounts/actions';
|
} from 'components/accounts/actions';
|
||||||
|
import {
|
||||||
|
add, remove, activate, reset,
|
||||||
|
ADD, REMOVE, ACTIVATE, UPDATE_TOKEN, RESET
|
||||||
|
} from 'components/accounts/actions/pure-actions';
|
||||||
|
|
||||||
const account = {
|
const account = {
|
||||||
id: 1,
|
id: 1,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import ChooseAccountState from 'services/authFlow/ChooseAccountState';
|
import ChooseAccountState from 'services/authFlow/ChooseAccountState';
|
||||||
import CompleteState from 'services/authFlow/CompleteState';
|
import CompleteState from 'services/authFlow/CompleteState';
|
||||||
import LoginState from 'services/authFlow/LoginState';
|
import LoginState from 'services/authFlow/LoginState';
|
||||||
|
import RegisterState from 'services/authFlow/RegisterState';
|
||||||
|
|
||||||
import { bootstrap, expectState, expectNavigate, expectRun } from './helpers';
|
import { bootstrap, expectState, expectNavigate, expectRun } from './helpers';
|
||||||
|
|
||||||
@ -31,14 +32,12 @@ describe('ChooseAccountState', () => {
|
|||||||
|
|
||||||
describe('#resolve', () => {
|
describe('#resolve', () => {
|
||||||
it('should transition to complete if existed account was choosen', () => {
|
it('should transition to complete if existed account was choosen', () => {
|
||||||
expectRun(mock, 'setAccountSwitcher', false);
|
|
||||||
expectState(mock, CompleteState);
|
expectState(mock, CompleteState);
|
||||||
|
|
||||||
state.resolve(context, {id: 123});
|
state.resolve(context, {id: 123});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should transition to login if user wants to add new account', () => {
|
it('should transition to login if user wants to add new account', () => {
|
||||||
expectRun(mock, 'setAccountSwitcher', false);
|
|
||||||
expectNavigate(mock, '/login');
|
expectNavigate(mock, '/login');
|
||||||
expectState(mock, LoginState);
|
expectState(mock, LoginState);
|
||||||
|
|
||||||
@ -47,10 +46,16 @@ describe('ChooseAccountState', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('#reject', () => {
|
describe('#reject', () => {
|
||||||
it('should logout', () => {
|
it('should transition to register', () => {
|
||||||
expectRun(mock, 'logout');
|
expectState(mock, RegisterState);
|
||||||
|
|
||||||
state.reject(context);
|
state.reject(context);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should logout', () => {
|
||||||
|
expectRun(mock, 'logout');
|
||||||
|
|
||||||
|
state.reject(context, {logout: true});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import expect from 'unexpected';
|
import expect from 'unexpected';
|
||||||
|
import sinon from 'sinon';
|
||||||
|
|
||||||
import CompleteState from 'services/authFlow/CompleteState';
|
import CompleteState from 'services/authFlow/CompleteState';
|
||||||
import LoginState from 'services/authFlow/LoginState';
|
import LoginState from 'services/authFlow/LoginState';
|
||||||
@ -6,6 +7,7 @@ import ActivationState from 'services/authFlow/ActivationState';
|
|||||||
import AcceptRulesState from 'services/authFlow/AcceptRulesState';
|
import AcceptRulesState from 'services/authFlow/AcceptRulesState';
|
||||||
import FinishState from 'services/authFlow/FinishState';
|
import FinishState from 'services/authFlow/FinishState';
|
||||||
import PermissionsState from 'services/authFlow/PermissionsState';
|
import PermissionsState from 'services/authFlow/PermissionsState';
|
||||||
|
import ChooseAccountState from 'services/authFlow/ChooseAccountState';
|
||||||
|
|
||||||
import { bootstrap, expectState, expectNavigate, expectRun } from './helpers';
|
import { bootstrap, expectState, expectNavigate, expectRun } from './helpers';
|
||||||
|
|
||||||
@ -133,9 +135,144 @@ describe('CompleteState', () => {
|
|||||||
|
|
||||||
state.enter(context);
|
state.enter(context);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should transition to permissions state if prompt=consent', () => {
|
||||||
|
context.getState.returns({
|
||||||
|
user: {
|
||||||
|
isActive: true,
|
||||||
|
isGuest: false
|
||||||
|
},
|
||||||
|
auth: {
|
||||||
|
oauth: {
|
||||||
|
clientId: 'ely.by',
|
||||||
|
prompt: ['consent']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expectState(mock, PermissionsState);
|
||||||
|
|
||||||
|
state.enter(context);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should transition to ChooseAccountState if user has multiple accs and switcher enabled', () => {
|
||||||
|
context.getState.returns({
|
||||||
|
user: {
|
||||||
|
isActive: true,
|
||||||
|
isGuest: false
|
||||||
|
},
|
||||||
|
accounts: {
|
||||||
|
available: [
|
||||||
|
{id: 1},
|
||||||
|
{id: 2}
|
||||||
|
],
|
||||||
|
active: {
|
||||||
|
id: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
auth: {
|
||||||
|
isSwitcherEnabled: true,
|
||||||
|
oauth: {
|
||||||
|
clientId: 'ely.by',
|
||||||
|
prompt: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expectState(mock, ChooseAccountState);
|
||||||
|
|
||||||
|
state.enter(context);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should NOT transition to ChooseAccountState if user has multiple accs and switcher disabled', () => {
|
||||||
|
context.getState.returns({
|
||||||
|
user: {
|
||||||
|
isActive: true,
|
||||||
|
isGuest: false
|
||||||
|
},
|
||||||
|
accounts: {
|
||||||
|
available: [
|
||||||
|
{id: 1},
|
||||||
|
{id: 2}
|
||||||
|
],
|
||||||
|
active: {
|
||||||
|
id: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
auth: {
|
||||||
|
isSwitcherEnabled: false,
|
||||||
|
oauth: {
|
||||||
|
clientId: 'ely.by',
|
||||||
|
prompt: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expectRun(mock, 'oAuthComplete', {})
|
||||||
|
.returns({then() {}});
|
||||||
|
|
||||||
|
state.enter(context);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should transition to ChooseAccountState if prompt=select_account and switcher enabled', () => {
|
||||||
|
context.getState.returns({
|
||||||
|
user: {
|
||||||
|
isActive: true,
|
||||||
|
isGuest: false
|
||||||
|
},
|
||||||
|
accounts: {
|
||||||
|
available: [
|
||||||
|
{id: 1}
|
||||||
|
],
|
||||||
|
active: {
|
||||||
|
id: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
auth: {
|
||||||
|
isSwitcherEnabled: true,
|
||||||
|
oauth: {
|
||||||
|
clientId: 'ely.by',
|
||||||
|
prompt: ['select_account']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expectState(mock, ChooseAccountState);
|
||||||
|
|
||||||
|
state.enter(context);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should NOT transition to ChooseAccountState if prompt=select_account and switcher disabled', () => {
|
||||||
|
context.getState.returns({
|
||||||
|
user: {
|
||||||
|
isActive: true,
|
||||||
|
isGuest: false
|
||||||
|
},
|
||||||
|
accounts: {
|
||||||
|
available: [
|
||||||
|
{id: 1}
|
||||||
|
],
|
||||||
|
active: {
|
||||||
|
id: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
auth: {
|
||||||
|
isSwitcherEnabled: false,
|
||||||
|
oauth: {
|
||||||
|
clientId: 'ely.by',
|
||||||
|
prompt: ['select_account']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expectRun(mock, 'oAuthComplete', {})
|
||||||
|
.returns({then() {}});
|
||||||
|
|
||||||
|
state.enter(context);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('oAuthComplete', () => {
|
describe('when user completes oauth', () => {
|
||||||
it('should run oAuthComplete', () => {
|
it('should run oAuthComplete', () => {
|
||||||
context.getState.returns({
|
context.getState.returns({
|
||||||
user: {
|
user: {
|
||||||
@ -185,7 +322,7 @@ describe('CompleteState', () => {
|
|||||||
state.enter(context);
|
state.enter(context);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should transition run redirect by default', () => {
|
it('should run redirect by default', () => {
|
||||||
const expectedUrl = 'foo/bar';
|
const expectedUrl = 'foo/bar';
|
||||||
const promise = Promise.resolve({redirectUri: expectedUrl});
|
const promise = Promise.resolve({redirectUri: expectedUrl});
|
||||||
|
|
||||||
@ -261,6 +398,122 @@ describe('CompleteState', () => {
|
|||||||
it('should transition to permissions state if rejected with acceptRequired', () =>
|
it('should transition to permissions state if rejected with acceptRequired', () =>
|
||||||
testOAuth('reject', {acceptRequired: true}, PermissionsState)
|
testOAuth('reject', {acceptRequired: true}, PermissionsState)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
describe('when loginHint is set', () => {
|
||||||
|
const testSuccessLoginHint = (field) => {
|
||||||
|
const account = {
|
||||||
|
id: 9,
|
||||||
|
email: 'some@email.com',
|
||||||
|
username: 'thatUsername'
|
||||||
|
};
|
||||||
|
|
||||||
|
context.getState.returns({
|
||||||
|
user: {
|
||||||
|
isActive: true,
|
||||||
|
isGuest: false
|
||||||
|
},
|
||||||
|
accounts: {
|
||||||
|
available: [
|
||||||
|
account
|
||||||
|
],
|
||||||
|
active: {
|
||||||
|
id: 100
|
||||||
|
}
|
||||||
|
},
|
||||||
|
auth: {
|
||||||
|
oauth: {
|
||||||
|
clientId: 'ely.by',
|
||||||
|
loginHint: account[field],
|
||||||
|
prompt: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expectRun(mock, 'setAccountSwitcher', false);
|
||||||
|
expectRun(mock, 'authenticate', account)
|
||||||
|
.returns(Promise.resolve());
|
||||||
|
expectState(mock, CompleteState);
|
||||||
|
|
||||||
|
return expect(state.enter(context), 'to be fulfilled');
|
||||||
|
};
|
||||||
|
|
||||||
|
it('should authenticate account if id matches', () =>
|
||||||
|
testSuccessLoginHint('id')
|
||||||
|
);
|
||||||
|
|
||||||
|
it('should authenticate account if email matches', () =>
|
||||||
|
testSuccessLoginHint('email')
|
||||||
|
);
|
||||||
|
|
||||||
|
it('should authenticate account if username matches', () =>
|
||||||
|
testSuccessLoginHint('username')
|
||||||
|
);
|
||||||
|
|
||||||
|
it('should not authenticate if account is already authenticated', () => {
|
||||||
|
const account = {
|
||||||
|
id: 9,
|
||||||
|
email: 'some@email.com',
|
||||||
|
username: 'thatUsername'
|
||||||
|
};
|
||||||
|
|
||||||
|
context.getState.returns({
|
||||||
|
user: {
|
||||||
|
isActive: true,
|
||||||
|
isGuest: false
|
||||||
|
},
|
||||||
|
accounts: {
|
||||||
|
available: [
|
||||||
|
account
|
||||||
|
],
|
||||||
|
active: account
|
||||||
|
},
|
||||||
|
auth: {
|
||||||
|
oauth: {
|
||||||
|
clientId: 'ely.by',
|
||||||
|
loginHint: account.id,
|
||||||
|
prompt: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expectRun(mock, 'setAccountSwitcher', false);
|
||||||
|
expectRun(mock, 'oAuthComplete', {})
|
||||||
|
.returns({then: () => Promise.resolve()});
|
||||||
|
|
||||||
|
return expect(state.enter(context), 'to be fulfilled');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not authenticate if account was not found and continue auth', () => {
|
||||||
|
const account = {
|
||||||
|
id: 9,
|
||||||
|
email: 'some@email.com',
|
||||||
|
username: 'thatUsername'
|
||||||
|
};
|
||||||
|
|
||||||
|
context.getState.returns({
|
||||||
|
user: {
|
||||||
|
isActive: true,
|
||||||
|
isGuest: false
|
||||||
|
},
|
||||||
|
accounts: {
|
||||||
|
available: [{id: 1}],
|
||||||
|
active: {id: 1}
|
||||||
|
},
|
||||||
|
auth: {
|
||||||
|
oauth: {
|
||||||
|
clientId: 'ely.by',
|
||||||
|
loginHint: account.id,
|
||||||
|
prompt: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expectRun(mock, 'oAuthComplete', {})
|
||||||
|
.returns({then: () => Promise.resolve()});
|
||||||
|
|
||||||
|
return expect(state.enter(context), 'to be fulfilled');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('permissions accept', () => {
|
describe('permissions accept', () => {
|
||||||
|
@ -81,4 +81,12 @@ describe('LoginState', () => {
|
|||||||
return promise.catch(mock.verify.bind(mock));
|
return promise.catch(mock.verify.bind(mock));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('#goBack', () => {
|
||||||
|
it('should return to previous page', () => {
|
||||||
|
expectRun(mock, 'goBack', '/');
|
||||||
|
|
||||||
|
state.goBack(context);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import expect from 'unexpected';
|
||||||
import sinon from 'sinon';
|
import sinon from 'sinon';
|
||||||
|
|
||||||
import PasswordState from 'services/authFlow/PasswordState';
|
import PasswordState from 'services/authFlow/PasswordState';
|
||||||
@ -69,27 +70,11 @@ describe('PasswordState', () => {
|
|||||||
rememberMe: expectedRememberMe,
|
rememberMe: expectedRememberMe,
|
||||||
})
|
})
|
||||||
).returns(Promise.resolve());
|
).returns(Promise.resolve());
|
||||||
|
|
||||||
state.resolve(context, {password: expectedPassword, rememberMe: expectedRememberMe});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should transition to complete state on successfull login', () => {
|
|
||||||
const promise = Promise.resolve();
|
|
||||||
const expectedLogin = 'login';
|
|
||||||
const expectedPassword = 'password';
|
|
||||||
|
|
||||||
context.getState.returns({
|
|
||||||
auth: {
|
|
||||||
login: expectedLogin
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
mock.expects('run').returns(promise);
|
|
||||||
expectState(mock, CompleteState);
|
expectState(mock, CompleteState);
|
||||||
|
|
||||||
state.resolve(context, {password: expectedPassword});
|
const payload = {password: expectedPassword, rememberMe: expectedRememberMe};
|
||||||
|
|
||||||
return promise;
|
return expect(state.resolve(context, payload), 'to be fulfilled');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -34,16 +34,6 @@ describe('RegisterState', () => {
|
|||||||
|
|
||||||
state.enter(context);
|
state.enter(context);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should transition to complete if not guest', () => {
|
|
||||||
context.getState.returns({
|
|
||||||
user: {isGuest: false}
|
|
||||||
});
|
|
||||||
|
|
||||||
expectState(mock, CompleteState);
|
|
||||||
|
|
||||||
state.enter(context);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#resolve', () => {
|
describe('#resolve', () => {
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
* A helpers for testing states in isolation from AuthFlow
|
* A helpers for testing states in isolation from AuthFlow
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import sinon from 'sinon';
|
||||||
|
|
||||||
export function bootstrap() {
|
export function bootstrap() {
|
||||||
const context = {
|
const context = {
|
||||||
getState: sinon.stub(),
|
getState: sinon.stub(),
|
||||||
@ -28,9 +30,9 @@ export function expectState(mock, state) {
|
|||||||
export function expectNavigate(mock, route, options) {
|
export function expectNavigate(mock, route, options) {
|
||||||
if (options) {
|
if (options) {
|
||||||
return mock.expects('navigate').once().withExactArgs(route, sinon.match(options));
|
return mock.expects('navigate').once().withExactArgs(route, sinon.match(options));
|
||||||
} else {
|
|
||||||
return mock.expects('navigate').once().withExactArgs(route);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return mock.expects('navigate').once().withExactArgs(route);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function expectRun(mock, ...args) {
|
export function expectRun(mock, ...args) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user