mirror of
https://github.com/elyby/accounts-frontend.git
synced 2024-12-02 19:50:44 +05:30
#48: call authentication.logout for each revoked account
This commit is contained in:
parent
9e7d5b8338
commit
5142d65b39
@ -59,7 +59,10 @@ export function revoke(account) {
|
|||||||
|
|
||||||
if (accountToReplace) {
|
if (accountToReplace) {
|
||||||
return dispatch(authenticate(accountToReplace))
|
return dispatch(authenticate(accountToReplace))
|
||||||
.then(() => dispatch(remove(account)));
|
.then(() => {
|
||||||
|
authentication.logout(account);
|
||||||
|
dispatch(remove(account));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return dispatch(logout());
|
return dispatch(logout());
|
||||||
@ -111,8 +114,19 @@ export function activate(account) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function logoutAll() {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
const {accounts: {available}} = getState();
|
||||||
|
|
||||||
|
available.forEach((account) => authentication.logout(account));
|
||||||
|
|
||||||
|
dispatch(reset());
|
||||||
|
};
|
||||||
|
}
|
||||||
export const RESET = 'accounts:reset';
|
export const RESET = 'accounts:reset';
|
||||||
/**
|
/**
|
||||||
|
* @api private
|
||||||
|
*
|
||||||
* @return {object} - action definition
|
* @return {object} - action definition
|
||||||
*/
|
*/
|
||||||
export function reset() {
|
export function reset() {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { routeActions } from 'react-router-redux';
|
import { routeActions } from 'react-router-redux';
|
||||||
|
|
||||||
import accounts from 'services/api/accounts';
|
import accounts from 'services/api/accounts';
|
||||||
import { reset as resetAccounts } from 'components/accounts/actions';
|
import { logoutAll } from 'components/accounts/actions';
|
||||||
import authentication from 'services/api/authentication';
|
import authentication from 'services/api/authentication';
|
||||||
import { setLocale } from 'components/i18n/actions';
|
import { setLocale } from 'components/i18n/actions';
|
||||||
|
|
||||||
@ -54,24 +54,16 @@ export function setUser(payload) {
|
|||||||
|
|
||||||
export function logout() {
|
export function logout() {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
if (getState().user.token) {
|
dispatch(setUser({
|
||||||
authentication.logout();
|
lang: getState().user.lang,
|
||||||
}
|
isGuest: true
|
||||||
|
}));
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
dispatch(logoutAll());
|
||||||
setTimeout(() => { // a tiny timeout to allow logout before user's token will be removed
|
|
||||||
dispatch(setUser({
|
|
||||||
lang: getState().user.lang,
|
|
||||||
isGuest: true
|
|
||||||
}));
|
|
||||||
|
|
||||||
dispatch(resetAccounts());
|
dispatch(routeActions.push('/login'));
|
||||||
|
|
||||||
dispatch(routeActions.push('/login'));
|
return Promise.resolve();
|
||||||
|
|
||||||
resolve();
|
|
||||||
}, 0);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,8 +13,17 @@ const authentication = {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
logout() {
|
/**
|
||||||
return request.post('/api/authentication/logout');
|
* @param {object} options
|
||||||
|
* @param {object} [options.token] - an optional token to overwrite headers
|
||||||
|
* in middleware and disable token auto-refresh
|
||||||
|
*
|
||||||
|
* @return {Promise}
|
||||||
|
*/
|
||||||
|
logout(options = {}) {
|
||||||
|
return request.post('/api/authentication/logout', {}, {
|
||||||
|
token: options.token
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
forgotPassword({
|
forgotPassword({
|
||||||
|
@ -8,7 +8,8 @@ import {
|
|||||||
add, ADD,
|
add, ADD,
|
||||||
activate, ACTIVATE,
|
activate, ACTIVATE,
|
||||||
remove,
|
remove,
|
||||||
reset
|
reset,
|
||||||
|
logoutAll
|
||||||
} from 'components/accounts/actions';
|
} from 'components/accounts/actions';
|
||||||
import { SET_LOCALE } from 'components/i18n/actions';
|
import { SET_LOCALE } from 'components/i18n/actions';
|
||||||
|
|
||||||
@ -116,58 +117,129 @@ describe('components/accounts/actions', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('#revoke()', () => {
|
describe('#revoke()', () => {
|
||||||
it('should switch next account if available', () => {
|
beforeEach(() => {
|
||||||
|
sinon.stub(authentication, 'logout').named('authentication.logout');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
authentication.logout.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when one account available', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
getState.returns({
|
||||||
|
accounts: {
|
||||||
|
active: account,
|
||||||
|
available: [account]
|
||||||
|
},
|
||||||
|
user
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should dispatch reset action', () =>
|
||||||
|
revoke(account)(dispatch, getState).then(() =>
|
||||||
|
expect(dispatch, 'to have a call satisfying', [
|
||||||
|
reset()
|
||||||
|
])
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
it('should call logout api method in background', () =>
|
||||||
|
revoke(account)(dispatch, getState).then(() =>
|
||||||
|
expect(authentication.logout, 'to have a call satisfying', [
|
||||||
|
account
|
||||||
|
])
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
it('should update user state', () =>
|
||||||
|
revoke(account)(dispatch, getState).then(() =>
|
||||||
|
expect(dispatch, 'to have a call satisfying', [
|
||||||
|
{payload: {isGuest: true}}
|
||||||
|
// updateUser({isGuest: true})
|
||||||
|
])
|
||||||
|
// expect(dispatch, 'to have calls satisfying', [
|
||||||
|
// [remove(account)],
|
||||||
|
// [expect.it('to be a function')]
|
||||||
|
// // [logout()] // TODO: this is not a plain action. How should we simplify its testing?
|
||||||
|
// ])
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when multiple accounts available', () => {
|
||||||
const account2 = {...account, id: 2};
|
const account2 = {...account, id: 2};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
getState.returns({
|
||||||
|
accounts: {
|
||||||
|
active: account2,
|
||||||
|
available: [account, account2]
|
||||||
|
},
|
||||||
|
user
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should switch to the next account', () =>
|
||||||
|
revoke(account2)(dispatch, getState).then(() =>
|
||||||
|
expect(dispatch, 'to have a call satisfying', [
|
||||||
|
activate(account)
|
||||||
|
])
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
it('should remove current account', () =>
|
||||||
|
revoke(account2)(dispatch, getState).then(() =>
|
||||||
|
expect(dispatch, 'to have a call satisfying', [
|
||||||
|
remove(account2)
|
||||||
|
])
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
it('should call logout api method in background', () =>
|
||||||
|
revoke(account2)(dispatch, getState).then(() =>
|
||||||
|
expect(authentication.logout, 'to have a call satisfying', [
|
||||||
|
account2
|
||||||
|
])
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#logoutAll()', () => {
|
||||||
|
const account2 = {...account, id: 2};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
getState.returns({
|
getState.returns({
|
||||||
accounts: {
|
accounts: {
|
||||||
active: account2,
|
active: account2,
|
||||||
available: [account]
|
available: [account, account2]
|
||||||
},
|
},
|
||||||
user
|
user
|
||||||
});
|
});
|
||||||
|
|
||||||
return revoke(account2)(dispatch, getState).then(() => {
|
sinon.stub(authentication, 'logout').named('authentication.logout');
|
||||||
expect(dispatch, 'to have a call satisfying', [
|
|
||||||
remove(account2)
|
|
||||||
]);
|
|
||||||
expect(dispatch, 'to have a call satisfying', [
|
|
||||||
activate(account)
|
|
||||||
]);
|
|
||||||
expect(dispatch, 'to have a call satisfying', [
|
|
||||||
updateUser({...user, isGuest: false})
|
|
||||||
]);
|
|
||||||
// expect(dispatch, 'to have calls satisfying', [
|
|
||||||
// [remove(account2)],
|
|
||||||
// [expect.it('to be a function')]
|
|
||||||
// // [authenticate(account2)] // TODO: this is not a plain action. How should we simplify its testing?
|
|
||||||
// ])
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should logout if no other accounts available', () => {
|
afterEach(() => {
|
||||||
getState.returns({
|
authentication.logout.restore();
|
||||||
accounts: {
|
});
|
||||||
active: account,
|
|
||||||
available: []
|
|
||||||
},
|
|
||||||
user
|
|
||||||
});
|
|
||||||
|
|
||||||
revoke(account)(dispatch, getState).then(() => {
|
it('should call logout api method for each account', () => {
|
||||||
expect(dispatch, 'to have a call satisfying', [
|
logoutAll()(dispatch, getState);
|
||||||
{payload: {isGuest: true}}
|
|
||||||
// updateUser({isGuest: true})
|
expect(authentication.logout, 'to have calls satisfying', [
|
||||||
]);
|
[account],
|
||||||
expect(dispatch, 'to have a call satisfying', [
|
[account2]
|
||||||
reset()
|
]);
|
||||||
]);
|
});
|
||||||
// expect(dispatch, 'to have calls satisfying', [
|
|
||||||
// [remove(account)],
|
it('should dispatch reset', () => {
|
||||||
// [expect.it('to be a function')]
|
logoutAll()(dispatch, getState);
|
||||||
// // [logout()] // TODO: this is not a plain action. How should we simplify its testing?
|
|
||||||
// ])
|
expect(dispatch, 'to have a call satisfying', [
|
||||||
});
|
reset()
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -42,11 +42,16 @@ describe('components/user/actions', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('user with jwt', () => {
|
describe('user with jwt', () => {
|
||||||
|
const token = 'iLoveRockNRoll';
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
getState.returns({
|
getState.returns({
|
||||||
user: {
|
user: {
|
||||||
token: 'iLoveRockNRoll',
|
|
||||||
lang: 'foo'
|
lang: 'foo'
|
||||||
|
},
|
||||||
|
accounts: {
|
||||||
|
active: {token},
|
||||||
|
available: [{token}]
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -65,7 +70,7 @@ describe('components/user/actions', () => {
|
|||||||
|
|
||||||
return callThunk(logout).then(() => {
|
return callThunk(logout).then(() => {
|
||||||
expect(request.post, 'to have a call satisfying', [
|
expect(request.post, 'to have a call satisfying', [
|
||||||
'/api/authentication/logout'
|
'/api/authentication/logout', {}, {}
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -75,11 +80,17 @@ describe('components/user/actions', () => {
|
|||||||
testRedirectedToLogin();
|
testRedirectedToLogin();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('user without jwt', () => { // (a guest with partially filled user's state)
|
describe('user without jwt', () => {
|
||||||
|
// (a guest with partially filled user's state)
|
||||||
|
// DEPRECATED
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
getState.returns({
|
getState.returns({
|
||||||
user: {
|
user: {
|
||||||
lang: 'foo'
|
lang: 'foo'
|
||||||
|
},
|
||||||
|
accounts: {
|
||||||
|
active: null,
|
||||||
|
available: []
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -17,6 +17,7 @@ describe('refreshTokenMiddleware', () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sinon.stub(authentication, 'requestToken').named('authentication.requestToken');
|
sinon.stub(authentication, 'requestToken').named('authentication.requestToken');
|
||||||
|
sinon.stub(authentication, 'logout').named('authentication.logout');
|
||||||
|
|
||||||
getState = sinon.stub().named('store.getState');
|
getState = sinon.stub().named('store.getState');
|
||||||
dispatch = sinon.spy((arg) =>
|
dispatch = sinon.spy((arg) =>
|
||||||
@ -28,6 +29,7 @@ describe('refreshTokenMiddleware', () => {
|
|||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
authentication.requestToken.restore();
|
authentication.requestToken.restore();
|
||||||
|
authentication.logout.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('must be till 2100 to test with validToken', () =>
|
it('must be till 2100 to test with validToken', () =>
|
||||||
@ -37,12 +39,14 @@ describe('refreshTokenMiddleware', () => {
|
|||||||
describe('#before', () => {
|
describe('#before', () => {
|
||||||
describe('when token expired', () => {
|
describe('when token expired', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
const account = {
|
||||||
|
token: expiredToken,
|
||||||
|
refreshToken
|
||||||
|
};
|
||||||
getState.returns({
|
getState.returns({
|
||||||
accounts: {
|
accounts: {
|
||||||
active: {
|
active: account,
|
||||||
token: expiredToken,
|
available: [account]
|
||||||
refreshToken
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
user: {}
|
user: {}
|
||||||
});
|
});
|
||||||
@ -104,12 +108,14 @@ describe('refreshTokenMiddleware', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should if token can not be parsed', () => {
|
it('should if token can not be parsed', () => {
|
||||||
|
const account = {
|
||||||
|
token: 'realy bad token',
|
||||||
|
refreshToken
|
||||||
|
};
|
||||||
getState.returns({
|
getState.returns({
|
||||||
accounts: {
|
accounts: {
|
||||||
active: {
|
active: account,
|
||||||
token: 'realy bad token',
|
available: [account]
|
||||||
refreshToken
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
user: {}
|
user: {}
|
||||||
});
|
});
|
||||||
@ -140,7 +146,8 @@ describe('refreshTokenMiddleware', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
getState.returns({
|
getState.returns({
|
||||||
accounts: {
|
accounts: {
|
||||||
active: null
|
active: null,
|
||||||
|
available: []
|
||||||
},
|
},
|
||||||
user: {
|
user: {
|
||||||
token: expiredToken,
|
token: expiredToken,
|
||||||
@ -216,7 +223,8 @@ describe('refreshTokenMiddleware', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
getState.returns({
|
getState.returns({
|
||||||
accounts: {
|
accounts: {
|
||||||
active: {refreshToken}
|
active: {refreshToken},
|
||||||
|
available: [{refreshToken}]
|
||||||
},
|
},
|
||||||
user: {}
|
user: {}
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import expect from 'unexpected';
|
import expect from 'unexpected';
|
||||||
|
|
||||||
|
import request from 'services/request';
|
||||||
import authentication from 'services/api/authentication';
|
import authentication from 'services/api/authentication';
|
||||||
import accounts from 'services/api/accounts';
|
import accounts from 'services/api/accounts';
|
||||||
|
|
||||||
@ -88,4 +89,38 @@ describe('authentication api', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('#logout', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
sinon.stub(request, 'post').named('request.post');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
request.post.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should request logout api', () => {
|
||||||
|
authentication.logout();
|
||||||
|
|
||||||
|
expect(request.post, 'to have a call satisfying', [
|
||||||
|
'/api/authentication/logout', {}, {}
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns a promise', () => {
|
||||||
|
request.post.returns(Promise.resolve());
|
||||||
|
|
||||||
|
return expect(authentication.logout(), 'to be fulfilled');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('overrides token if provided', () => {
|
||||||
|
const token = 'foo';
|
||||||
|
|
||||||
|
authentication.logout({token});
|
||||||
|
|
||||||
|
expect(request.post, 'to have a call satisfying', [
|
||||||
|
'/api/authentication/logout', {}, {token}
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user