2016-08-10 00:47:49 +05:30
|
|
|
import expect from 'unexpected';
|
2017-01-06 11:04:39 +05:30
|
|
|
import sinon from 'sinon';
|
2016-08-10 00:47:49 +05:30
|
|
|
|
|
|
|
import refreshTokenMiddleware from 'components/user/middlewares/refreshTokenMiddleware';
|
2017-12-31 00:34:31 +05:30
|
|
|
import { browserHistory } from 'services/history';
|
2016-08-10 00:47:49 +05:30
|
|
|
import authentication from 'services/api/authentication';
|
2017-02-26 17:10:07 +05:30
|
|
|
import { InternalServerError } from 'services/request';
|
2016-11-05 15:41:41 +05:30
|
|
|
import { updateToken } from 'components/accounts/actions';
|
2016-08-10 00:47:49 +05:30
|
|
|
|
|
|
|
const refreshToken = 'foo';
|
|
|
|
const expiredToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE0NzA3NjE0NDMsImV4cCI6MTQ3MDc2MTQ0MywiaWF0IjoxNDcwNzYxNDQzLCJqdGkiOiJpZDEyMzQ1NiJ9.gWdnzfQQvarGpkbldUvB8qdJZSVkvdNtCbhbbl2yJW8';
|
|
|
|
// valid till 2100 year
|
|
|
|
const validToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE0NzA3NjE5NzcsImV4cCI6NDEwMjQ0NDgwMCwiaWF0IjoxNDcwNzYxOTc3LCJqdGkiOiJpZDEyMzQ1NiJ9.M4KY4QgHOUzhpAZjWoHJbGsEJPR-RBsJ1c1BKyxvAoU';
|
|
|
|
|
|
|
|
describe('refreshTokenMiddleware', () => {
|
|
|
|
let middleware;
|
|
|
|
let getState;
|
|
|
|
let dispatch;
|
|
|
|
|
2017-12-31 00:34:31 +05:30
|
|
|
const email = 'test@email.com';
|
|
|
|
|
2016-08-10 00:47:49 +05:30
|
|
|
beforeEach(() => {
|
|
|
|
sinon.stub(authentication, 'requestToken').named('authentication.requestToken');
|
2016-11-15 11:25:15 +05:30
|
|
|
sinon.stub(authentication, 'logout').named('authentication.logout');
|
2017-12-31 00:34:31 +05:30
|
|
|
sinon.stub(browserHistory, 'push');
|
2016-08-10 00:47:49 +05:30
|
|
|
|
|
|
|
getState = sinon.stub().named('store.getState');
|
2016-11-05 15:41:41 +05:30
|
|
|
dispatch = sinon.spy((arg) =>
|
|
|
|
typeof arg === 'function' ? arg(dispatch, getState) : arg
|
|
|
|
).named('store.dispatch');
|
2016-08-10 00:47:49 +05:30
|
|
|
|
|
|
|
middleware = refreshTokenMiddleware({getState, dispatch});
|
|
|
|
});
|
|
|
|
|
|
|
|
afterEach(() => {
|
|
|
|
authentication.requestToken.restore();
|
2016-11-15 11:25:15 +05:30
|
|
|
authentication.logout.restore();
|
2017-12-31 00:34:31 +05:30
|
|
|
browserHistory.push.restore();
|
2016-08-10 00:47:49 +05:30
|
|
|
});
|
|
|
|
|
2017-12-31 00:34:31 +05:30
|
|
|
function assertRelogin() {
|
|
|
|
expect(dispatch, 'to have a call satisfying', [
|
|
|
|
{
|
|
|
|
type: 'auth:setCredentials',
|
2018-02-18 23:39:32 +05:30
|
|
|
payload: {
|
|
|
|
login: email,
|
|
|
|
returnUrl: expect.it('to be a string'),
|
|
|
|
},
|
2017-12-31 00:34:31 +05:30
|
|
|
}
|
|
|
|
]);
|
|
|
|
|
|
|
|
expect(browserHistory.push, 'to have a call satisfying', [
|
|
|
|
'/login'
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
2016-11-05 15:41:41 +05:30
|
|
|
it('must be till 2100 to test with validToken', () =>
|
|
|
|
expect(new Date().getFullYear(), 'to be less than', 2100)
|
|
|
|
);
|
|
|
|
|
2016-08-10 00:47:49 +05:30
|
|
|
describe('#before', () => {
|
2016-10-30 17:42:49 +05:30
|
|
|
describe('when token expired', () => {
|
|
|
|
beforeEach(() => {
|
2016-11-15 11:25:15 +05:30
|
|
|
const account = {
|
2017-12-31 00:34:31 +05:30
|
|
|
email,
|
2016-11-15 11:25:15 +05:30
|
|
|
token: expiredToken,
|
|
|
|
refreshToken
|
|
|
|
};
|
2016-10-30 17:42:49 +05:30
|
|
|
getState.returns({
|
2016-11-05 15:41:41 +05:30
|
|
|
accounts: {
|
2018-11-10 20:23:04 +05:30
|
|
|
active: account.id,
|
2016-11-15 11:25:15 +05:30
|
|
|
available: [account]
|
2016-11-05 15:41:41 +05:30
|
|
|
},
|
2018-02-18 23:39:32 +05:30
|
|
|
auth: {
|
|
|
|
credentials: {},
|
|
|
|
},
|
|
|
|
user: {},
|
2016-10-30 17:42:49 +05:30
|
|
|
});
|
2016-08-10 00:47:49 +05:30
|
|
|
});
|
|
|
|
|
2016-10-30 17:42:49 +05:30
|
|
|
it('should request new token', () => {
|
|
|
|
const data = {
|
|
|
|
url: 'foo',
|
|
|
|
options: {
|
|
|
|
headers: {}
|
|
|
|
}
|
|
|
|
};
|
2016-08-10 00:47:49 +05:30
|
|
|
|
2016-10-30 17:42:49 +05:30
|
|
|
authentication.requestToken.returns(Promise.resolve({token: validToken}));
|
|
|
|
|
|
|
|
return middleware.before(data).then((resp) => {
|
|
|
|
expect(resp, 'to satisfy', data);
|
|
|
|
|
|
|
|
expect(authentication.requestToken, 'to have a call satisfying', [
|
|
|
|
refreshToken
|
|
|
|
]);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should not apply to refresh-token request', () => {
|
2016-11-13 02:01:44 +05:30
|
|
|
const data = {url: '/refresh-token', options: {}};
|
2016-10-30 17:42:49 +05:30
|
|
|
const resp = middleware.before(data);
|
2016-08-10 00:47:49 +05:30
|
|
|
|
|
|
|
|
2017-01-06 11:04:39 +05:30
|
|
|
return expect(resp, 'to be fulfilled with', data)
|
|
|
|
.then(() =>
|
|
|
|
expect(authentication.requestToken, 'was not called')
|
|
|
|
);
|
2016-08-10 00:47:49 +05:30
|
|
|
});
|
|
|
|
|
2016-11-08 12:00:53 +05:30
|
|
|
it('should not auto refresh token if options.token specified', () => {
|
2016-10-30 17:42:49 +05:30
|
|
|
const data = {
|
|
|
|
url: 'foo',
|
2016-11-08 12:00:53 +05:30
|
|
|
options: {token: 'foo'}
|
2016-10-30 17:42:49 +05:30
|
|
|
};
|
|
|
|
middleware.before(data);
|
|
|
|
|
|
|
|
expect(authentication.requestToken, 'was not called');
|
2016-08-10 00:47:49 +05:30
|
|
|
});
|
|
|
|
|
2016-11-05 15:41:41 +05:30
|
|
|
it('should update user with new token', () => {
|
|
|
|
const data = {
|
|
|
|
url: 'foo',
|
|
|
|
options: {
|
|
|
|
headers: {}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
authentication.requestToken.returns(Promise.resolve({token: validToken}));
|
|
|
|
|
|
|
|
return middleware.before(data).then(() =>
|
|
|
|
expect(dispatch, 'to have a call satisfying', [
|
|
|
|
updateToken(validToken)
|
|
|
|
])
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
2017-12-31 00:34:31 +05:30
|
|
|
it('should relogin if token can not be parsed', () => {
|
2016-11-15 11:25:15 +05:30
|
|
|
const account = {
|
2017-12-31 00:34:31 +05:30
|
|
|
email,
|
2016-11-15 11:25:15 +05:30
|
|
|
token: 'realy bad token',
|
|
|
|
refreshToken
|
|
|
|
};
|
2016-11-05 15:41:41 +05:30
|
|
|
getState.returns({
|
|
|
|
accounts: {
|
2018-11-10 20:23:04 +05:30
|
|
|
active: account.id,
|
2016-11-15 11:25:15 +05:30
|
|
|
available: [account]
|
2016-11-05 15:41:41 +05:30
|
|
|
},
|
2018-02-18 23:39:32 +05:30
|
|
|
auth: {
|
|
|
|
credentials: {},
|
|
|
|
},
|
|
|
|
user: {},
|
2016-11-05 15:41:41 +05:30
|
|
|
});
|
|
|
|
|
|
|
|
const req = {url: 'foo', options: {}};
|
|
|
|
|
2017-12-31 00:34:31 +05:30
|
|
|
return expect(middleware.before(req), 'to be rejected with', {
|
|
|
|
message: 'Invalid token'
|
|
|
|
})
|
|
|
|
.then(() => {
|
|
|
|
expect(authentication.requestToken, 'was not called');
|
2016-08-10 00:47:49 +05:30
|
|
|
|
2017-12-31 00:34:31 +05:30
|
|
|
assertRelogin();
|
|
|
|
});
|
2016-11-05 15:41:41 +05:30
|
|
|
});
|
|
|
|
|
2017-12-31 00:34:31 +05:30
|
|
|
it('should relogin if token request failed', () => {
|
2016-10-30 17:42:49 +05:30
|
|
|
authentication.requestToken.returns(Promise.reject());
|
2016-08-10 00:47:49 +05:30
|
|
|
|
2017-12-31 00:34:31 +05:30
|
|
|
return expect(middleware.before({url: 'foo', options: {}}), 'to be rejected')
|
|
|
|
.then(() =>
|
|
|
|
assertRelogin()
|
|
|
|
);
|
2016-11-05 15:41:41 +05:30
|
|
|
});
|
2017-02-26 15:28:33 +05:30
|
|
|
|
|
|
|
it('should not logout if request failed with 5xx', () => {
|
2017-02-26 17:10:07 +05:30
|
|
|
const resp = new InternalServerError(null, {status: 500});
|
2017-02-26 15:28:33 +05:30
|
|
|
|
|
|
|
authentication.requestToken.returns(Promise.reject(resp));
|
|
|
|
|
|
|
|
return expect(middleware.before({url: 'foo', options: {}}), 'to be rejected with', resp).then(() =>
|
|
|
|
expect(dispatch, 'to have no calls satisfying', [
|
|
|
|
{payload: {isGuest: true}}
|
|
|
|
])
|
|
|
|
);
|
|
|
|
});
|
2016-11-05 15:41:41 +05:30
|
|
|
});
|
|
|
|
|
2016-10-30 17:42:49 +05:30
|
|
|
it('should not be applied if no token', () => {
|
2016-08-10 00:47:49 +05:30
|
|
|
getState.returns({
|
2016-11-05 15:41:41 +05:30
|
|
|
accounts: {
|
2017-12-31 00:34:31 +05:30
|
|
|
active: null,
|
|
|
|
available: [],
|
2016-11-05 15:41:41 +05:30
|
|
|
},
|
2016-08-10 00:47:49 +05:30
|
|
|
user: {}
|
|
|
|
});
|
|
|
|
|
2017-12-31 00:34:31 +05:30
|
|
|
const data = {
|
|
|
|
url: 'foo',
|
|
|
|
options: {},
|
|
|
|
};
|
2016-08-10 00:47:49 +05:30
|
|
|
const resp = middleware.before(data);
|
|
|
|
|
2017-01-06 11:04:39 +05:30
|
|
|
return expect(resp, 'to be fulfilled with', data)
|
|
|
|
.then(() =>
|
|
|
|
expect(authentication.requestToken, 'was not called')
|
|
|
|
);
|
2016-08-10 00:47:49 +05:30
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('#catch', () => {
|
2016-10-30 17:42:49 +05:30
|
|
|
const expiredResponse = {
|
|
|
|
name: 'Unauthorized',
|
|
|
|
message: 'Token expired',
|
|
|
|
code: 0,
|
|
|
|
status: 401,
|
|
|
|
type: 'yii\\web\\UnauthorizedHttpException'
|
|
|
|
};
|
|
|
|
|
|
|
|
const badTokenReponse = {
|
|
|
|
name: 'Unauthorized',
|
2016-11-05 15:41:41 +05:30
|
|
|
message: 'You are requesting with an invalid credential.',
|
|
|
|
code: 0,
|
|
|
|
status: 401,
|
|
|
|
type: 'yii\\web\\UnauthorizedHttpException'
|
|
|
|
};
|
|
|
|
|
|
|
|
const incorrectTokenReponse = {
|
|
|
|
name: 'Unauthorized',
|
|
|
|
message: 'Incorrect token',
|
2016-10-30 17:42:49 +05:30
|
|
|
code: 0,
|
|
|
|
status: 401,
|
|
|
|
type: 'yii\\web\\UnauthorizedHttpException'
|
|
|
|
};
|
|
|
|
|
|
|
|
let restart;
|
|
|
|
|
|
|
|
beforeEach(() => {
|
2016-08-10 00:47:49 +05:30
|
|
|
getState.returns({
|
2016-11-05 15:41:41 +05:30
|
|
|
accounts: {
|
2017-12-31 00:34:31 +05:30
|
|
|
active: 1,
|
|
|
|
available: [{
|
|
|
|
id: 1,
|
|
|
|
email,
|
|
|
|
token: 'old token',
|
|
|
|
refreshToken,
|
|
|
|
}]
|
2016-11-05 15:41:41 +05:30
|
|
|
},
|
|
|
|
user: {}
|
2016-08-10 00:47:49 +05:30
|
|
|
});
|
|
|
|
|
2016-10-30 17:42:49 +05:30
|
|
|
restart = sinon.stub().named('restart');
|
2016-08-10 00:47:49 +05:30
|
|
|
|
|
|
|
authentication.requestToken.returns(Promise.resolve({token: validToken}));
|
2016-10-30 17:42:49 +05:30
|
|
|
});
|
2016-08-10 00:47:49 +05:30
|
|
|
|
2017-12-31 00:34:31 +05:30
|
|
|
function assertNewTokenRequest() {
|
|
|
|
expect(authentication.requestToken, 'to have a call satisfying', [
|
|
|
|
refreshToken
|
|
|
|
]);
|
|
|
|
expect(restart, 'was called');
|
|
|
|
expect(dispatch, 'was called');
|
|
|
|
}
|
|
|
|
|
2016-10-30 17:42:49 +05:30
|
|
|
it('should request new token if expired', () =>
|
2017-12-31 00:34:31 +05:30
|
|
|
expect(
|
|
|
|
middleware.catch(expiredResponse, {options: {}}, restart),
|
|
|
|
'to be fulfilled'
|
|
|
|
).then(assertNewTokenRequest)
|
2016-10-30 17:42:49 +05:30
|
|
|
);
|
|
|
|
|
2017-12-31 00:34:31 +05:30
|
|
|
it('should request new token if invalid credential', () =>
|
2016-11-05 15:41:41 +05:30
|
|
|
expect(
|
|
|
|
middleware.catch(badTokenReponse, {options: {}}, restart),
|
2017-12-31 00:34:31 +05:30
|
|
|
'to be fulfilled'
|
|
|
|
).then(assertNewTokenRequest)
|
2016-11-05 15:41:41 +05:30
|
|
|
);
|
|
|
|
|
2017-12-31 00:34:31 +05:30
|
|
|
it('should request new token if token is incorrect', () =>
|
2016-11-05 15:41:41 +05:30
|
|
|
expect(
|
|
|
|
middleware.catch(incorrectTokenReponse, {options: {}}, restart),
|
2017-12-31 00:34:31 +05:30
|
|
|
'to be fulfilled'
|
|
|
|
).then(assertNewTokenRequest)
|
2016-11-05 15:41:41 +05:30
|
|
|
);
|
2016-08-10 00:47:49 +05:30
|
|
|
|
2017-12-31 00:34:31 +05:30
|
|
|
it('should relogin if no refreshToken', () => {
|
|
|
|
getState.returns({
|
|
|
|
accounts: {
|
|
|
|
active: 1,
|
|
|
|
available: [{
|
|
|
|
id: 1,
|
|
|
|
email,
|
|
|
|
refreshToken: null,
|
|
|
|
}]
|
|
|
|
},
|
2018-02-18 23:39:32 +05:30
|
|
|
auth: {
|
|
|
|
credentials: {},
|
|
|
|
},
|
|
|
|
user: {},
|
2017-12-31 00:34:31 +05:30
|
|
|
});
|
|
|
|
|
|
|
|
return expect(
|
|
|
|
middleware.catch(incorrectTokenReponse, {options: {}}, restart),
|
|
|
|
'to be rejected'
|
|
|
|
).then(() => {
|
|
|
|
assertRelogin();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2016-11-08 12:00:53 +05:30
|
|
|
it('should pass the request through if options.token specified', () => {
|
2016-10-30 17:42:49 +05:30
|
|
|
const promise = middleware.catch(expiredResponse, {
|
|
|
|
options: {
|
2016-11-08 12:00:53 +05:30
|
|
|
token: 'foo'
|
2016-10-30 17:42:49 +05:30
|
|
|
}
|
|
|
|
}, restart);
|
|
|
|
|
|
|
|
return expect(promise, 'to be rejected with', expiredResponse).then(() => {
|
|
|
|
expect(restart, 'was not called');
|
|
|
|
expect(authentication.requestToken, 'was not called');
|
|
|
|
});
|
|
|
|
});
|
2016-08-10 00:47:49 +05:30
|
|
|
|
|
|
|
it('should pass the rest of failed requests through', () => {
|
|
|
|
const resp = {};
|
|
|
|
|
2016-10-30 17:42:49 +05:30
|
|
|
const promise = middleware.catch(resp, {
|
|
|
|
options: {}
|
|
|
|
}, restart);
|
2016-08-10 00:47:49 +05:30
|
|
|
|
2016-10-30 17:42:49 +05:30
|
|
|
return expect(promise, 'to be rejected with', resp).then(() => {
|
|
|
|
expect(restart, 'was not called');
|
|
|
|
expect(authentication.requestToken, 'was not called');
|
2016-08-10 00:47:49 +05:30
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|