Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Invalidate session after token expiration #195

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
6.9.3
36 changes: 36 additions & 0 deletions addon/authenticators/jwt.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ export default TokenAuthenticator.extend({
if (expiresAt > now) {
const wait = (expiresAt - now - this.refreshLeeway) * 1000;

this.scheduleAccessTokenExpiration(expiresAt);

if (wait > 0) {
if (this.refreshAccessTokens) {
this.scheduleAccessTokenRefresh(dataObject.get(this.tokenExpireName), refreshToken);
Expand Down Expand Up @@ -318,6 +320,23 @@ export default TokenAuthenticator.extend({
});
},

/**
Schedules session invalidation at the time token expires.

@method scheduleAccessTokenExpiration
@private
*/
scheduleAccessTokenExpiration(expiresAt) {
const now = this.getCurrentTime();
const wait = Math.max((expiresAt - now) * 1000, 0);

if (!Ember.isEmpty(expiresAt)) {
Ember.run.cancel(this._tokenExpirationTimeout);
delete this._tokenExpirationTimeout;
this._tokenExpirationTimeout = Ember.run.later(this, this.handleAccessTokenExpiration, wait);
}
},

/**
Cancels any outstanding automatic token refreshes and returns a resolving
promise.
Expand All @@ -328,6 +347,8 @@ export default TokenAuthenticator.extend({
invalidate() {
Ember.run.cancel(this._refreshTokenTimeout);
delete this._refreshTokenTimeout;
Ember.run.cancel(this._tokenExpirationTimeout);
delete this._tokenExpirationTimeout;
return new Ember.RSVP.resolve();
},

Expand Down Expand Up @@ -358,6 +379,7 @@ export default TokenAuthenticator.extend({
const tokenExpireData = {};

tokenExpireData[this.tokenExpireName] = expiresAt;
this.scheduleAccessTokenExpiration(expiresAt);

if (this.refreshAccessTokens) {
const refreshToken = Ember.get(response, this.refreshTokenPropertyName);
Expand Down Expand Up @@ -386,5 +408,19 @@ export default TokenAuthenticator.extend({
this.trigger('sessionDataInvalidated');
});
}
},

/**
Handles access token expiration. After token expiration the session will
be invalidated and the sessionInvalidated provided by ember-simple-auth
will be triggered.

@method handleAccessTokenExpiration
@private
*/
handleAccessTokenExpiration() {
return this.invalidate().then(() => {
this.trigger('sessionDataInvalidated');
});
}
});
3 changes: 3 additions & 0 deletions ember-cli-build.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ module.exports = function(defaults) {
// Add options here
});

app.import('bower_components/qunit/qunit/qunit.js');
app.import('bower_components/ember-qunit/ember-qunit.amd.js');

var extraAssets = new Funnel(app.bowerDirectory + '/sinon', {
srcDir: '/',
files: ['index.js'],
Expand Down
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@
"ember-cli-inject-live-reload": "^1.3.1",
"ember-cli-qunit": "^1.0.4",
"ember-cli-release": "0.2.8",
"ember-cli-sri": "^1.2.0",
"ember-cli-uglify": "^1.2.0",
"ember-data": "1.13.15",
"ember-disable-prototype-extensions": "^1.0.0",
Expand Down
152 changes: 128 additions & 24 deletions tests/unit/authenticators/jwt-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,8 +173,10 @@ test('#restore rejects when `refreshAccessTokens` is false and token is expired'
App.authenticator.restore(data).then(() => {
assert.ok(false);
}, () => {
// Check that Ember.run.later was not called.
assert.deepEqual(Ember.run.later.getCall(0), null);
// Check that refreshAccessToken was not scheduled
assert.equal(Ember.run.later.getCalls().some((call) => {
return call.args[1] === App.authenticator.refreshAccessToken;
}), false);
});
});
});
Expand Down Expand Up @@ -281,23 +283,25 @@ test('#restore resolves when `expiresAt` is greater than `now`', assert => {

Ember.run(() => {
App.authenticator.restore(data).then(() => {
// Check that Ember.run.later was not called.
assert.deepEqual(Ember.run.later.getCall(0), null);
// Check that refreshAccessToken was not scheduled
assert.equal(Ember.run.later.getCalls().some((call) => {
return call.args[1] === App.authenticator.refreshAccessToken;
}), false);
}).catch(() => {
assert.ok(false);
});
});
});

test('#restore schedules a token refresh when `refreshAccessTokens` is true.', assert => {
assert.expect(2);
assert.expect(1);

const jwt = JWT.create(),
currentTime = getConvertedTime(10000),
expiresAt = currentTime + getConvertedTime(3000);

sinon.stub(App.authenticator, 'getCurrentTime', () => { return currentTime; });
sinon.stub(App.authenticator, 'refreshAccessToken', () => { return null; });
let refreshAccessTokenStub = sinon.stub(App.authenticator, 'refreshAccessToken', () => { return null; });

let token = {};
token[jwt.identificationField] = '[email protected]';
Expand Down Expand Up @@ -327,10 +331,11 @@ test('#restore schedules a token refresh when `refreshAccessTokens` is true.', a

Ember.run(() => {
App.authenticator.restore(data).then(() => {
// Check that Ember.run.later ran.
var spyCall = Ember.run.later.getCall(0);
assert.deepEqual(spyCall.args[1], App.authenticator.refreshAccessToken);
assert.deepEqual(spyCall.args[2], refreshToken);
// Check that refreshAccessToken was scheduled
assert.equal(Ember.run.later.getCalls().some((call) => {
return call.args[1] === App.authenticator.refreshAccessToken &&
call.args[2] === refreshToken;
}), true);

App.authenticator.refreshAccessToken.restore();
});
Expand Down Expand Up @@ -361,9 +366,10 @@ test('#restore does not schedule a token refresh when `refreshAccessTokens` is f

Ember.run(() => {
App.authenticator.restore(data).then(() => {
// Check that Ember.run.later ran.
var spyCall = Ember.run.later.getCall(0);
assert.deepEqual(spyCall, null);
// Check that refreshAccessToken was not scheduled
assert.equal(Ember.run.later.getCalls().some((call) => {
return call.args[1] === App.authenticator.refreshAccessToken;
}), false);
});
});
});
Expand Down Expand Up @@ -420,7 +426,7 @@ test('#restore does not schedule a token refresh when `expiresAt` - `refreshLeew
});

test('#restore schedule access token refresh and refreshes it when time is appropriate', assert => {
assert.expect(1);
assert.expect(2);

const jwt = JWT.create();

Expand All @@ -434,9 +440,11 @@ test('#restore schedule access token refresh and refreshes it when time is appro
Ember.run.later.restore();

sinon.stub(Ember.run, 'later', (target, method, args) => {
refreshAccessToken = method;
refreshAccessTokenTarget = target;
refreshAccessTokenArgs = args;
if (method === App.authenticator.refreshAccessToken) {
refreshAccessToken = method;
refreshAccessTokenTarget = target;
refreshAccessTokenArgs = args;
}

return Math.random();
});
Expand Down Expand Up @@ -483,13 +491,70 @@ test('#restore schedule access token refresh and refreshes it when time is appro
'{ "token": "' + token + '", "refresh_token": "' + refreshToken + '" }'
]);

assert.ok(!!refreshAccessToken, "should schedule refreshAccessToken method");

Ember.run(() => {
refreshAccessToken.call(refreshAccessTokenTarget, refreshAccessTokenArgs).then(response => {
assert.deepEqual(response, { exp: expiresAt, token: token, 'refresh_token': refreshToken });
});
});
});

test('#restore schedule access token expiration and invalidates session when time is appropriate', assert => {
assert.expect(3);

const jwt = JWT.create();

let currentTime = getConvertedTime(10000);
const expiresAt = currentTime + getConvertedTime(3000);

let invalidateSession;
let invalidateSessionTarget;
let invalidateSessionArgs;

Ember.run.later.restore();

sinon.stub(Ember.run, 'later', (target, method, wait) => {
assert.equal(target, App.authenticator);
assert.equal(method, App.authenticator.handleAccessTokenExpiration);
assert.equal(wait, 3000000000);

return Math.random();
});

sinon.stub(App.authenticator, 'getCurrentTime', () => { return currentTime; });

let token = {};
token[jwt.identificationField] = '[email protected]';
token[jwt.tokenExpireName] = expiresAt;

token = createFakeToken(token);

const data = {};
data[jwt.tokenPropertyName] = token;
data[jwt.tokenExpireName] = expiresAt;

App.authenticator.refreshAccessTokens = false;

Ember.run(() => {
App.authenticator.restore(data).catch(() => {
// Check that Ember.run.cancel was called.
assert.deepEqual(Ember.run.cancel.getCall(1), true);
});
});

currentTime = getConvertedTime(1250);
App.authenticator.getCurrentTime.restore();
sinon.stub(App.authenticator, 'getCurrentTime', () => { return currentTime; });

// Ember.run(() => {
// invalidateSession.call(invalidateSessionTarget, invalidateSessionArgs).then(response => {
// console.log(response);
// assert.true();
// });
// });
});

test('#authenticate sends an ajax request to the token endpoint', assert => {
assert.expect(1);

Expand Down Expand Up @@ -542,7 +607,7 @@ test('#authenticate rejects with invalid credentials', assert => {
});

test('#authenticate schedules a token refresh when `refreshAccessTokens` is true', assert => {
assert.expect(2);
assert.expect(1);

const jwt = JWT.create(),
expiresAt = new Date().getTime() + 300000;
Expand All @@ -569,9 +634,11 @@ test('#authenticate schedules a token refresh when `refreshAccessTokens` is true

Ember.run(() => {
App.authenticator.authenticate(credentials).then(() => {
var spyCall = Ember.run.later.getCall(0);
assert.deepEqual(spyCall.args[1], App.authenticator.refreshAccessToken);
assert.deepEqual(spyCall.args[2], refreshToken);
// Check that refreshAccessToken was scheduled
assert.equal(Ember.run.later.getCalls().some((call) => {
return call.args[1] === App.authenticator.refreshAccessToken &&
call.args[2] === refreshToken;
}), true);
});
});
});
Expand Down Expand Up @@ -606,9 +673,46 @@ test('#authenticate does not schedule a token refresh when `refreshAccessTokens`

Ember.run(() => {
App.authenticator.authenticate(credentials).then(() => {
// Check that Ember.run.later ran.
var spyCall = Ember.run.later.getCall(0);
assert.deepEqual(spyCall, null);
// Check that refreshAccessToken was not scheduled
assert.equal(Ember.run.later.getCalls().some((call) => {
return call.args[1] === App.authenticator.refreshAccessToken;
}), false);
});
});
});

test('#authenticate schedules a token expiration', assert => {
assert.expect(1);

const jwt = JWT.create(),
expiresAt = new Date().getTime() + 300000;

let token = {};
token[jwt.identificationField] = '[email protected]';
token[jwt.tokenExpireName] = expiresAt;

token = createFakeToken(token);

let refreshToken = createFakeRefreshToken();

const credentials = {
identification: 'username',
password: 'password'
};

App.server.respondWith('POST', jwt.serverTokenEndpoint, [
201, {
'Content-Type': 'application/json'
},
'{ "token": "' + token + '", "refresh_token": "' + refreshToken + '" }'
]);

Ember.run(() => {
App.authenticator.authenticate(credentials).then(() => {
// Check that expireAccessToken was scheduled
assert.equal(Ember.run.later.getCalls().some((call) => {
return call.args[1] === App.authenticator.handleAccessTokenExpiration;
}), true);
});
});
});
Expand Down