mirror of
https://github.com/elyby/accounts-frontend.git
synced 2024-12-25 22:50:04 +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 React, { Component } from 'react';
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
import classNames from 'classnames';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { FormattedMessage as Message } from 'react-intl';
|
||||
|
||||
import loader from 'services/loader';
|
||||
import { skins, SKIN_DARK, COLOR_WHITE } from 'components/ui';
|
||||
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 messages from './AccountSwitcher.intl.json';
|
||||
@ -22,7 +22,6 @@ export class AccountSwitcher extends Component {
|
||||
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.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),
|
||||
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
|
||||
@ -40,8 +39,7 @@ export class AccountSwitcher extends Component {
|
||||
|
||||
render() {
|
||||
const { accounts, skin, allowAdd, allowLogout, highlightActiveAccount } = this.props;
|
||||
// const activeAccount = accounts.active || this.props.user;
|
||||
const activeAccount = this.props.user;
|
||||
const activeAccount = getActiveAccount({ accounts });
|
||||
|
||||
let {available} = accounts;
|
||||
|
||||
@ -83,14 +81,14 @@ export class AccountSwitcher extends Component {
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
{available.map((account, id) => (
|
||||
{available.map((account, index) => (
|
||||
<div className={classNames(styles.item, styles.accountSwitchItem)}
|
||||
key={account.id}
|
||||
onClick={this.onSwitch(account)}
|
||||
>
|
||||
<div className={classNames(
|
||||
styles.accountIcon,
|
||||
styles[`accountIcon${id % 7 + (highlightActiveAccount ? 2 : 1)}`]
|
||||
styles[`accountIcon${index % 7 + (highlightActiveAccount ? 2 : 1)}`]
|
||||
)} />
|
||||
|
||||
{allowLogout ? (
|
||||
@ -156,12 +154,8 @@ export class AccountSwitcher extends Component {
|
||||
};
|
||||
}
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
import { authenticate, revoke } from 'components/accounts/actions';
|
||||
|
||||
export default connect(({accounts, user}) => ({
|
||||
export default connect(({accounts}) => ({
|
||||
accounts,
|
||||
user
|
||||
}), {
|
||||
switchAccount: authenticate,
|
||||
removeAccount: revoke
|
||||
|
@ -9,6 +9,7 @@ import { setAccountSwitcher } from 'components/auth/actions';
|
||||
import { getActiveAccount } from 'components/accounts/reducer';
|
||||
import logger from 'services/logger';
|
||||
|
||||
import type { Account, State as AccountsState } from './reducer';
|
||||
import {
|
||||
add,
|
||||
remove,
|
||||
@ -16,7 +17,6 @@ import {
|
||||
reset,
|
||||
updateToken
|
||||
} from './actions/pure-actions';
|
||||
import type { Account, State as AccountsState } from './reducer';
|
||||
|
||||
type Dispatch = (action: Object) => Promise<*>;
|
||||
|
||||
@ -45,8 +45,19 @@ export function authenticate(account: Account | {
|
||||
const {token, refreshToken} = account;
|
||||
const email = account.email || null;
|
||||
|
||||
return (dispatch: Dispatch, getState: () => State): Promise<Account> =>
|
||||
authentication.validateToken({token, refreshToken})
|
||||
return (dispatch: Dispatch, getState: () => State): Promise<Account> => {
|
||||
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 = {}) => {
|
||||
// all the logic to get the valid token was failed,
|
||||
// looks like we have some problems with token
|
||||
@ -97,6 +108,7 @@ export function authenticate(account: Account | {
|
||||
return dispatch(setLocale(user.lang))
|
||||
.then(() => account);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -219,9 +231,17 @@ export function revoke(account: Account) {
|
||||
|
||||
if (accountToReplace) {
|
||||
return dispatch(authenticate(accountToReplace))
|
||||
.then(() => {
|
||||
authentication.logout(account);
|
||||
.finally(() => {
|
||||
// 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));
|
||||
})
|
||||
.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()', () => {
|
||||
|
@ -1,16 +1,12 @@
|
||||
// @flow
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { AccountSwitcher } from 'components/accounts';
|
||||
|
||||
import styles from './loggedInPanel.scss';
|
||||
|
||||
import type { User } from 'components/user';
|
||||
|
||||
export default class LoggedInPanel extends Component<{
|
||||
user: User
|
||||
username: string
|
||||
}, {
|
||||
isAccountSwitcherActive: bool
|
||||
}> {
|
||||
@ -38,7 +34,7 @@ export default class LoggedInPanel extends Component<{
|
||||
}
|
||||
|
||||
render() {
|
||||
const { user } = this.props;
|
||||
const { username } = this.props;
|
||||
const { isAccountSwitcherActive } = this.state;
|
||||
|
||||
return (
|
||||
@ -48,7 +44,7 @@ export default class LoggedInPanel extends Component<{
|
||||
})}>
|
||||
<button className={styles.activeAccountButton} onClick={this.onExpandAccountSwitcher}>
|
||||
<span className={styles.userIcon} />
|
||||
<span className={styles.userName}>{user.username}</span>
|
||||
<span className={styles.userName}>{username}</span>
|
||||
<span className={styles.expandIcon} />
|
||||
</button>
|
||||
|
||||
|
@ -1,31 +1,26 @@
|
||||
import PropTypes from 'prop-types';
|
||||
// @flow
|
||||
import type { Account } from 'components/accounts/reducer';
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { Link } from 'react-router-dom';
|
||||
import { FormattedMessage as Message } from 'react-intl';
|
||||
|
||||
import buttons from 'components/ui/buttons.scss';
|
||||
|
||||
import messages from './Userbar.intl.json';
|
||||
import styles from './userbar.scss';
|
||||
|
||||
import { userShape } from 'components/user/User';
|
||||
|
||||
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 propTypes = {
|
||||
user: userShape,
|
||||
guestAction: PropTypes.oneOf(['register', 'login'])
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
guestAction: 'register'
|
||||
};
|
||||
|
||||
render() {
|
||||
const { user } = this.props;
|
||||
const { account } = this.props;
|
||||
let { guestAction } = this.props;
|
||||
|
||||
switch (guestAction) {
|
||||
@ -48,12 +43,12 @@ export default class Userbar extends Component {
|
||||
|
||||
return (
|
||||
<div className={styles.userbar}>
|
||||
{user.isGuest
|
||||
{account
|
||||
? (
|
||||
guestAction
|
||||
<LoggedInPanel username={account.username} />
|
||||
)
|
||||
: (
|
||||
<LoggedInPanel {...this.props} />
|
||||
guestAction
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
@ -1,4 +1,6 @@
|
||||
// @flow
|
||||
import type { User } from 'components/user';
|
||||
import type { Account } from 'components/accounts/reducer';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
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 Helmet from 'react-helmet';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import AuthPage from 'pages/auth/AuthPage';
|
||||
import ProfilePage from 'pages/profile/ProfilePage';
|
||||
import RulesPage from 'pages/rules/RulesPage';
|
||||
import PageNotFound from 'pages/404/PageNotFound';
|
||||
|
||||
import { ScrollIntoView } from 'components/ui/scroll';
|
||||
import PrivateRoute from 'containers/PrivateRoute';
|
||||
import AuthFlowRoute from 'containers/AuthFlowRoute';
|
||||
import Userbar from 'components/userbar/Userbar';
|
||||
import PopupStack from 'components/ui/popup/PopupStack';
|
||||
import loader from 'services/loader';
|
||||
import type { User } from 'components/user';
|
||||
import { getActiveAccount } from 'components/accounts/reducer';
|
||||
|
||||
import styles from './root.scss';
|
||||
import messages from './RootPage.intl.json';
|
||||
|
||||
class RootPage extends Component<{
|
||||
account: ?Account,
|
||||
user: User,
|
||||
isPopupActive: bool,
|
||||
onLogoClick: Function,
|
||||
@ -46,7 +47,7 @@ class RootPage extends Component<{
|
||||
|
||||
render() {
|
||||
const props = this.props;
|
||||
const {user, isPopupActive, onLogoClick} = this.props;
|
||||
const {user, account, isPopupActive, onLogoClick} = this.props;
|
||||
const isRegisterPage = props.location.pathname === '/register';
|
||||
|
||||
if (document && document.body) {
|
||||
@ -70,7 +71,8 @@ class RootPage extends Component<{
|
||||
<Message {...messages.siteName} />
|
||||
</Link>
|
||||
<div className={styles.userbar}>
|
||||
<Userbar {...props}
|
||||
<Userbar
|
||||
account={account}
|
||||
guestAction={isRegisterPage ? 'login' : 'register'}
|
||||
/>
|
||||
</div>
|
||||
@ -95,6 +97,7 @@ class RootPage extends Component<{
|
||||
|
||||
export default withRouter(connect((state) => ({
|
||||
user: state.user,
|
||||
account: getActiveAccount(state),
|
||||
isPopupActive: state.popup.popups.length > 0
|
||||
}), {
|
||||
onLogoClick: resetAuth
|
||||
|
@ -1,11 +1,13 @@
|
||||
{
|
||||
"account1": {
|
||||
"username": "SleepWalker",
|
||||
"email": "danilenkos@auroraglobal.com",
|
||||
"login": "SleepWalker",
|
||||
"password": "qwer1234"
|
||||
},
|
||||
"account2": {
|
||||
"username": "test",
|
||||
"email": "admin@udf.su",
|
||||
"login": "test",
|
||||
"password": "qwer1234"
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ describe('when user\'s token and refreshToken are invalid', () => {
|
||||
});
|
||||
|
||||
it('should allow select account', () => {
|
||||
// TODO: need a way to get valid token for one of the accounts
|
||||
cy.visit('/');
|
||||
|
||||
cy.get('[data-e2e-go-back]').click();
|
||||
@ -39,6 +40,16 @@ describe('when user\'s token and refreshToken are invalid', () => {
|
||||
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', () => {
|
||||
cy.visit('/');
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user