mirror of
https://github.com/elyby/accounts-frontend.git
synced 2024-11-30 02:32:58 +05:30
#389: fix logout in case, when all the accounts have invalid tokens
This commit is contained in:
parent
206627be17
commit
f1d33bf7ec
@ -1,14 +1,14 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { FormattedMessage as Message } from 'react-intl';
|
import { FormattedMessage as Message } from 'react-intl';
|
||||||
|
|
||||||
import loader from 'services/loader';
|
import loader from 'services/loader';
|
||||||
import { skins, SKIN_DARK, COLOR_WHITE } from 'components/ui';
|
import { skins, SKIN_DARK, COLOR_WHITE } from 'components/ui';
|
||||||
import { Button } from 'components/ui/form';
|
import { Button } from 'components/ui/form';
|
||||||
import { userShape } from 'components/user/User';
|
import { authenticate, revoke } from 'components/accounts/actions';
|
||||||
|
import { getActiveAccount } from 'components/accounts/reducer';
|
||||||
|
|
||||||
import styles from './accountSwitcher.scss';
|
import styles from './accountSwitcher.scss';
|
||||||
import messages from './AccountSwitcher.intl.json';
|
import messages from './AccountSwitcher.intl.json';
|
||||||
@ -22,7 +22,6 @@ export class AccountSwitcher extends Component {
|
|||||||
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
|
onSwitch: PropTypes.func, // called after switching an account. The active account will be passed as arg
|
||||||
accounts: PropTypes.object, // eslint-disable-line
|
accounts: PropTypes.object, // eslint-disable-line
|
||||||
user: userShape, // TODO: remove me, when we will be sure, that accounts.active is always set for user (event after register)
|
|
||||||
skin: PropTypes.oneOf(skins),
|
skin: PropTypes.oneOf(skins),
|
||||||
highlightActiveAccount: PropTypes.bool, // whether active account should be expanded and shown on the top
|
highlightActiveAccount: PropTypes.bool, // whether active account should be expanded and shown on the top
|
||||||
allowLogout: PropTypes.bool, // whether to show logout icon near each account
|
allowLogout: PropTypes.bool, // whether to show logout icon near each account
|
||||||
@ -40,8 +39,7 @@ export class AccountSwitcher extends Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { accounts, skin, allowAdd, allowLogout, highlightActiveAccount } = this.props;
|
const { accounts, skin, allowAdd, allowLogout, highlightActiveAccount } = this.props;
|
||||||
// const activeAccount = accounts.active || this.props.user;
|
const activeAccount = getActiveAccount({ accounts });
|
||||||
const activeAccount = this.props.user;
|
|
||||||
|
|
||||||
let {available} = accounts;
|
let {available} = accounts;
|
||||||
|
|
||||||
@ -83,14 +81,14 @@ export class AccountSwitcher extends Component {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
{available.map((account, id) => (
|
{available.map((account, index) => (
|
||||||
<div className={classNames(styles.item, styles.accountSwitchItem)}
|
<div className={classNames(styles.item, styles.accountSwitchItem)}
|
||||||
key={account.id}
|
key={account.id}
|
||||||
onClick={this.onSwitch(account)}
|
onClick={this.onSwitch(account)}
|
||||||
>
|
>
|
||||||
<div className={classNames(
|
<div className={classNames(
|
||||||
styles.accountIcon,
|
styles.accountIcon,
|
||||||
styles[`accountIcon${id % 7 + (highlightActiveAccount ? 2 : 1)}`]
|
styles[`accountIcon${index % 7 + (highlightActiveAccount ? 2 : 1)}`]
|
||||||
)} />
|
)} />
|
||||||
|
|
||||||
{allowLogout ? (
|
{allowLogout ? (
|
||||||
@ -156,12 +154,8 @@ export class AccountSwitcher extends Component {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
import { connect } from 'react-redux';
|
export default connect(({accounts}) => ({
|
||||||
import { authenticate, revoke } from 'components/accounts/actions';
|
|
||||||
|
|
||||||
export default connect(({accounts, user}) => ({
|
|
||||||
accounts,
|
accounts,
|
||||||
user
|
|
||||||
}), {
|
}), {
|
||||||
switchAccount: authenticate,
|
switchAccount: authenticate,
|
||||||
removeAccount: revoke
|
removeAccount: revoke
|
||||||
|
@ -9,6 +9,7 @@ import { setAccountSwitcher } from 'components/auth/actions';
|
|||||||
import { getActiveAccount } from 'components/accounts/reducer';
|
import { getActiveAccount } from 'components/accounts/reducer';
|
||||||
import logger from 'services/logger';
|
import logger from 'services/logger';
|
||||||
|
|
||||||
|
import type { Account, State as AccountsState } from './reducer';
|
||||||
import {
|
import {
|
||||||
add,
|
add,
|
||||||
remove,
|
remove,
|
||||||
@ -16,7 +17,6 @@ import {
|
|||||||
reset,
|
reset,
|
||||||
updateToken
|
updateToken
|
||||||
} from './actions/pure-actions';
|
} from './actions/pure-actions';
|
||||||
import type { Account, State as AccountsState } from './reducer';
|
|
||||||
|
|
||||||
type Dispatch = (action: Object) => Promise<*>;
|
type Dispatch = (action: Object) => Promise<*>;
|
||||||
|
|
||||||
@ -45,8 +45,19 @@ export function authenticate(account: Account | {
|
|||||||
const {token, refreshToken} = account;
|
const {token, refreshToken} = account;
|
||||||
const email = account.email || null;
|
const email = account.email || null;
|
||||||
|
|
||||||
return (dispatch: Dispatch, getState: () => State): Promise<Account> =>
|
return (dispatch: Dispatch, getState: () => State): Promise<Account> => {
|
||||||
authentication.validateToken({token, refreshToken})
|
const accountId: number | null = typeof account.id === 'number' ? account.id : null;
|
||||||
|
const knownAccount: ?Account = accountId
|
||||||
|
? getState().accounts.available.find((item) => item.id === accountId)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (knownAccount) {
|
||||||
|
// this account is already available
|
||||||
|
// activate it before validation
|
||||||
|
dispatch(activate(knownAccount));
|
||||||
|
}
|
||||||
|
|
||||||
|
return authentication.validateToken({token, refreshToken})
|
||||||
.catch((resp = {}) => {
|
.catch((resp = {}) => {
|
||||||
// all the logic to get the valid token was failed,
|
// all the logic to get the valid token was failed,
|
||||||
// looks like we have some problems with token
|
// looks like we have some problems with token
|
||||||
@ -97,6 +108,7 @@ export function authenticate(account: Account | {
|
|||||||
return dispatch(setLocale(user.lang))
|
return dispatch(setLocale(user.lang))
|
||||||
.then(() => account);
|
.then(() => account);
|
||||||
});
|
});
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -219,9 +231,17 @@ export function revoke(account: Account) {
|
|||||||
|
|
||||||
if (accountToReplace) {
|
if (accountToReplace) {
|
||||||
return dispatch(authenticate(accountToReplace))
|
return dispatch(authenticate(accountToReplace))
|
||||||
.then(() => {
|
.finally(() => {
|
||||||
authentication.logout(account);
|
// we need to logout user, even in case, when we can
|
||||||
|
// not authenticate him with new account
|
||||||
|
authentication.logout(account)
|
||||||
|
.catch(() => {
|
||||||
|
// we don't care
|
||||||
|
});
|
||||||
dispatch(remove(account));
|
dispatch(remove(account));
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
// we don't care
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,6 +190,35 @@ describe('components/accounts/actions', () => {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('when one account available', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
getState.returns({
|
||||||
|
accounts: {
|
||||||
|
active: account.id,
|
||||||
|
available: [account]
|
||||||
|
},
|
||||||
|
auth: {
|
||||||
|
credentials: {},
|
||||||
|
},
|
||||||
|
user,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should activate account before auth api call', () => {
|
||||||
|
authentication.validateToken.returns(Promise.reject({ error: 'foo'}));
|
||||||
|
|
||||||
|
return expect(
|
||||||
|
authenticate(account)(dispatch, getState),
|
||||||
|
'to be rejected with',
|
||||||
|
{ error: 'foo'}
|
||||||
|
).then(() =>
|
||||||
|
expect(dispatch, 'to have a call satisfying', [
|
||||||
|
activate(account)
|
||||||
|
])
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#revoke()', () => {
|
describe('#revoke()', () => {
|
||||||
|
@ -1,16 +1,12 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import { AccountSwitcher } from 'components/accounts';
|
import { AccountSwitcher } from 'components/accounts';
|
||||||
|
|
||||||
import styles from './loggedInPanel.scss';
|
import styles from './loggedInPanel.scss';
|
||||||
|
|
||||||
import type { User } from 'components/user';
|
|
||||||
|
|
||||||
export default class LoggedInPanel extends Component<{
|
export default class LoggedInPanel extends Component<{
|
||||||
user: User
|
username: string
|
||||||
}, {
|
}, {
|
||||||
isAccountSwitcherActive: bool
|
isAccountSwitcherActive: bool
|
||||||
}> {
|
}> {
|
||||||
@ -38,7 +34,7 @@ export default class LoggedInPanel extends Component<{
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { user } = this.props;
|
const { username } = this.props;
|
||||||
const { isAccountSwitcherActive } = this.state;
|
const { isAccountSwitcherActive } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -48,7 +44,7 @@ export default class LoggedInPanel extends Component<{
|
|||||||
})}>
|
})}>
|
||||||
<button className={styles.activeAccountButton} onClick={this.onExpandAccountSwitcher}>
|
<button className={styles.activeAccountButton} onClick={this.onExpandAccountSwitcher}>
|
||||||
<span className={styles.userIcon} />
|
<span className={styles.userIcon} />
|
||||||
<span className={styles.userName}>{user.username}</span>
|
<span className={styles.userName}>{username}</span>
|
||||||
<span className={styles.expandIcon} />
|
<span className={styles.expandIcon} />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
@ -1,31 +1,26 @@
|
|||||||
import PropTypes from 'prop-types';
|
// @flow
|
||||||
|
import type { Account } from 'components/accounts/reducer';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { FormattedMessage as Message } from 'react-intl';
|
import { FormattedMessage as Message } from 'react-intl';
|
||||||
|
|
||||||
import buttons from 'components/ui/buttons.scss';
|
import buttons from 'components/ui/buttons.scss';
|
||||||
|
|
||||||
import messages from './Userbar.intl.json';
|
import messages from './Userbar.intl.json';
|
||||||
import styles from './userbar.scss';
|
import styles from './userbar.scss';
|
||||||
|
|
||||||
import { userShape } from 'components/user/User';
|
|
||||||
|
|
||||||
import LoggedInPanel from './LoggedInPanel';
|
import LoggedInPanel from './LoggedInPanel';
|
||||||
|
|
||||||
export default class Userbar extends Component {
|
export default class Userbar extends Component<{
|
||||||
|
account: ?Account,
|
||||||
|
guestAction: 'register' | 'login',
|
||||||
|
}> {
|
||||||
static displayName = 'Userbar';
|
static displayName = 'Userbar';
|
||||||
static propTypes = {
|
|
||||||
user: userShape,
|
|
||||||
guestAction: PropTypes.oneOf(['register', 'login'])
|
|
||||||
};
|
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
guestAction: 'register'
|
guestAction: 'register'
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { user } = this.props;
|
const { account } = this.props;
|
||||||
let { guestAction } = this.props;
|
let { guestAction } = this.props;
|
||||||
|
|
||||||
switch (guestAction) {
|
switch (guestAction) {
|
||||||
@ -48,12 +43,12 @@ export default class Userbar extends Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.userbar}>
|
<div className={styles.userbar}>
|
||||||
{user.isGuest
|
{account
|
||||||
? (
|
? (
|
||||||
guestAction
|
<LoggedInPanel username={account.username} />
|
||||||
)
|
)
|
||||||
: (
|
: (
|
||||||
<LoggedInPanel {...this.props} />
|
guestAction
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
// @flow
|
// @flow
|
||||||
|
import type { User } from 'components/user';
|
||||||
|
import type { Account } from 'components/accounts/reducer';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { resetAuth } from 'components/auth/actions';
|
import { resetAuth } from 'components/auth/actions';
|
||||||
@ -7,24 +9,23 @@ import { FormattedMessage as Message } from 'react-intl';
|
|||||||
import { Route, Link, Switch } from 'react-router-dom';
|
import { Route, Link, Switch } from 'react-router-dom';
|
||||||
import Helmet from 'react-helmet';
|
import Helmet from 'react-helmet';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import AuthPage from 'pages/auth/AuthPage';
|
import AuthPage from 'pages/auth/AuthPage';
|
||||||
import ProfilePage from 'pages/profile/ProfilePage';
|
import ProfilePage from 'pages/profile/ProfilePage';
|
||||||
import RulesPage from 'pages/rules/RulesPage';
|
import RulesPage from 'pages/rules/RulesPage';
|
||||||
import PageNotFound from 'pages/404/PageNotFound';
|
import PageNotFound from 'pages/404/PageNotFound';
|
||||||
|
|
||||||
import { ScrollIntoView } from 'components/ui/scroll';
|
import { ScrollIntoView } from 'components/ui/scroll';
|
||||||
import PrivateRoute from 'containers/PrivateRoute';
|
import PrivateRoute from 'containers/PrivateRoute';
|
||||||
import AuthFlowRoute from 'containers/AuthFlowRoute';
|
import AuthFlowRoute from 'containers/AuthFlowRoute';
|
||||||
import Userbar from 'components/userbar/Userbar';
|
import Userbar from 'components/userbar/Userbar';
|
||||||
import PopupStack from 'components/ui/popup/PopupStack';
|
import PopupStack from 'components/ui/popup/PopupStack';
|
||||||
import loader from 'services/loader';
|
import loader from 'services/loader';
|
||||||
import type { User } from 'components/user';
|
import { getActiveAccount } from 'components/accounts/reducer';
|
||||||
|
|
||||||
import styles from './root.scss';
|
import styles from './root.scss';
|
||||||
import messages from './RootPage.intl.json';
|
import messages from './RootPage.intl.json';
|
||||||
|
|
||||||
class RootPage extends Component<{
|
class RootPage extends Component<{
|
||||||
|
account: ?Account,
|
||||||
user: User,
|
user: User,
|
||||||
isPopupActive: bool,
|
isPopupActive: bool,
|
||||||
onLogoClick: Function,
|
onLogoClick: Function,
|
||||||
@ -46,7 +47,7 @@ class RootPage extends Component<{
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const props = this.props;
|
const props = this.props;
|
||||||
const {user, isPopupActive, onLogoClick} = this.props;
|
const {user, account, isPopupActive, onLogoClick} = this.props;
|
||||||
const isRegisterPage = props.location.pathname === '/register';
|
const isRegisterPage = props.location.pathname === '/register';
|
||||||
|
|
||||||
if (document && document.body) {
|
if (document && document.body) {
|
||||||
@ -70,7 +71,8 @@ class RootPage extends Component<{
|
|||||||
<Message {...messages.siteName} />
|
<Message {...messages.siteName} />
|
||||||
</Link>
|
</Link>
|
||||||
<div className={styles.userbar}>
|
<div className={styles.userbar}>
|
||||||
<Userbar {...props}
|
<Userbar
|
||||||
|
account={account}
|
||||||
guestAction={isRegisterPage ? 'login' : 'register'}
|
guestAction={isRegisterPage ? 'login' : 'register'}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -95,6 +97,7 @@ class RootPage extends Component<{
|
|||||||
|
|
||||||
export default withRouter(connect((state) => ({
|
export default withRouter(connect((state) => ({
|
||||||
user: state.user,
|
user: state.user,
|
||||||
|
account: getActiveAccount(state),
|
||||||
isPopupActive: state.popup.popups.length > 0
|
isPopupActive: state.popup.popups.length > 0
|
||||||
}), {
|
}), {
|
||||||
onLogoClick: resetAuth
|
onLogoClick: resetAuth
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
{
|
{
|
||||||
"account1": {
|
"account1": {
|
||||||
"username": "SleepWalker",
|
"username": "SleepWalker",
|
||||||
|
"email": "danilenkos@auroraglobal.com",
|
||||||
"login": "SleepWalker",
|
"login": "SleepWalker",
|
||||||
"password": "qwer1234"
|
"password": "qwer1234"
|
||||||
},
|
},
|
||||||
"account2": {
|
"account2": {
|
||||||
"username": "test",
|
"username": "test",
|
||||||
|
"email": "admin@udf.su",
|
||||||
"login": "test",
|
"login": "test",
|
||||||
"password": "qwer1234"
|
"password": "qwer1234"
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ describe('when user\'s token and refreshToken are invalid', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should allow select account', () => {
|
it('should allow select account', () => {
|
||||||
|
// TODO: need a way to get valid token for one of the accounts
|
||||||
cy.visit('/');
|
cy.visit('/');
|
||||||
|
|
||||||
cy.get('[data-e2e-go-back]').click();
|
cy.get('[data-e2e-go-back]').click();
|
||||||
@ -39,6 +40,16 @@ describe('when user\'s token and refreshToken are invalid', () => {
|
|||||||
cy.contains('account preferences');
|
cy.contains('account preferences');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should allow logout', () => {
|
||||||
|
cy.visit('/');
|
||||||
|
|
||||||
|
cy.get('[data-e2e-toolbar]').contains(account2.username).click();
|
||||||
|
cy.get('[data-e2e-toolbar]').contains('Log out').click();
|
||||||
|
|
||||||
|
cy.contains(account2.email).should('not.exist');
|
||||||
|
cy.get('[data-e2e-toolbar]').contains(account2.username).should('not.exist');
|
||||||
|
});
|
||||||
|
|
||||||
it('should allow enter new login from choose account', () => {
|
it('should allow enter new login from choose account', () => {
|
||||||
cy.visit('/');
|
cy.visit('/');
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user