Skip to content

Commit 018adbc

Browse files
author
Thom Seddon
committed
Add support for non-expiring tokens
New tokens will be considered to never expire if respective accessTokenLifetime or refreshTokenLifetime are null. Equally, if saveAccessToken/saveRefreshToken() methods return an expires value of null, the token will be considered valid. If a non-expiring token is being issued, the "expires_in" key will be omitted from the response
1 parent b3c2149 commit 018adbc

File tree

6 files changed

+130
-10
lines changed

6 files changed

+130
-10
lines changed

Readme.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,11 @@ Note: As no model was actually implemented here, delving any deeper, i.e. passin
6868
- Default: `false`
6969
- *number* **accessTokenLifetime**
7070
- Life of access tokens in seconds
71-
- Default: 3600
71+
- If `null`, tokens will considered to never expire
72+
- Default: `3600`
7273
- *number* **refreshTokenLifetime**
7374
- Life of refresh tokens in seconds
75+
- If `null`, tokens will considered to never expire
7476
- Default: `1209600`
7577
- *number* **authCodeLifetime**
7678
- Life of auth codes in seconds
@@ -99,6 +101,7 @@ Note: see https://github.com/nightworld/node-oauth2-server/tree/master/examples/
99101
- Must contain the following keys:
100102
- *date* **expires**
101103
- The date when it expires
104+
- `null` to indicate the token **never expires**
102105
- *string|number* **user_id**
103106
- The user_id (saved in req.user.id)
104107

@@ -171,6 +174,7 @@ Note: see https://github.com/nightworld/node-oauth2-server/tree/master/examples/
171174
- client_id associated with this token
172175
- *date* **expires**
173176
- The date when it expires
177+
- `null` to indicate the token **never expires**
174178
- *string|number* **user_id**
175179
- The user_id
176180

lib/authorise.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ authorise.validateAccessToken = function (token, req, next) {
6363
}
6464

6565
// Check it's valid
66-
if (!token.expires || token.expires < this.now) {
66+
if (token.expires !== null && (!token.expires || token.expires < this.now)) {
6767
return next(error('invalid_grant', 'The access token provided has expired.'));
6868
}
6969

lib/oauth2server.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,10 @@ function OAuth2Server (config) {
4343
this.debug = config.debug || false;
4444
this.passthroughErrors = config.passthroughErrors;
4545

46-
this.accessTokenLifetime = config.accessTokenLifetime || 3600;
47-
this.refreshTokenLifeTime = config.refreshTokenLifeTime || 1209600;
46+
this.accessTokenLifetime = config.accessTokenLifetime !== undefined ?
47+
config.accessTokenLifetime : 3600;
48+
this.refreshTokenLifetime = config.refreshTokenLifetime !== undefined ?
49+
config.refreshTokenLifetime : 1209600;
4850
this.authCodeLifetime = config.authCodeLifetime || 30;
4951
this.now = new Date();
5052

lib/token.js

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ token.grant = function (req, res, next) {
179179

180180
if (!refreshToken || refreshToken.client_id !== req.oauth.client.client_id) {
181181
return next(error('invalid_grant', 'Invalid refresh token'));
182-
} else if (refreshToken.expires < oauth.now) {
182+
} else if (refreshToken.expires !== null && refreshToken.expires < oauth.now) {
183183
return next(error('invalid_grant', 'Refresh token has expired'));
184184
}
185185

@@ -231,8 +231,11 @@ token.grantAccessToken = function (req, res, next) {
231231

232232
req.oauth.accessToken.refresh_token = refreshToken;
233233

234-
var expires = new Date(oauth.now);
235-
expires.setSeconds(expires.getSeconds() + oauth.refreshTokenLifeTime);
234+
var expires = null;
235+
if (oauth.refreshTokenLifetime !== null) {
236+
expires = new Date(oauth.now);
237+
expires.setSeconds(expires.getSeconds() + oauth.refreshTokenLifetime);
238+
}
236239

237240
oauth.model.saveRefreshToken(req.oauth.accessToken.refresh_token,
238241
req.oauth.client.client_id, req.user.id, expires, function (err) {
@@ -262,8 +265,11 @@ token.grantAccessToken = function (req, res, next) {
262265

263266
req.oauth.accessToken = { access_token: accessToken };
264267

265-
var expires = new Date(oauth.now);
266-
expires.setSeconds(expires.getSeconds() + oauth.accessTokenLifetime);
268+
var expires = null;
269+
if (oauth.accessTokenLifetime !== null) {
270+
expires = new Date(oauth.now);
271+
expires.setSeconds(expires.getSeconds() + oauth.accessTokenLifetime);
272+
}
267273

268274
oauth.model.saveAccessToken(req.oauth.accessToken.access_token, req.oauth.client.client_id,
269275
req.user.id, expires, function (err) {
@@ -289,7 +295,9 @@ token.grantAccessToken = function (req, res, next) {
289295
token.issueToken = function (req, res, next) {
290296
// Prepare for output
291297
req.oauth.accessToken.token_type = 'bearer';
292-
req.oauth.accessToken.expires_in = this.accessTokenLifetime;
298+
if (this.accessTokenLifetime !== null) {
299+
req.oauth.accessToken.expires_in = this.accessTokenLifetime;
300+
}
293301

294302
// That's it!
295303
res.set({ 'Cache-Control': 'no-cache', 'Pragma': 'no-cache' });

test/authorizeRequest.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,24 @@ describe('OAuth2Server.authorizeRequest()', function() {
169169
.get('/?access_token=thom')
170170
.expect(/nightworld/, 200, done);
171171
});
172+
173+
it('should passthrough with valid, non-expiring token (token = null)', function (done) {
174+
var app = bootstrap({
175+
model: {
176+
getAccessToken: function (token, callback) {
177+
callback(false, { expires: null });
178+
}
179+
}
180+
});
181+
182+
app.get('/', function (req, res) {
183+
res.send('nightworld');
184+
});
185+
186+
request(app)
187+
.get('/?access_token=thom')
188+
.expect(/nightworld/, 200, done);
189+
});
172190
});
173191

174192
it('should expose the user_id', function (done) {

test/token.js

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,48 @@ describe('OAuth2Server.token()', function() {
403403
.expect(/"access_token": "(.*)",\n\s+"refresh_token": "(.*)"/i, 400, done);
404404

405405
});
406+
407+
it('should allow valid request with non-expiring token (token= null)', function (done) {
408+
var app = bootstrap({
409+
model: {
410+
getClient: function (id, secret, callback) {
411+
callback(false, { client_id: 'thom' });
412+
},
413+
grantTypeAllowed: function (id, secret, callback) {
414+
callback(false, true);
415+
},
416+
getRefreshToken: function (refreshToken, callback) {
417+
callback(false, {
418+
client_id: 'thom',
419+
expires: null,
420+
user_id: '123'
421+
});
422+
},
423+
saveAccessToken: function (accessToken, clientId, userId, expires, cb) {
424+
cb();
425+
},
426+
saveRefreshToken: function (refreshToken, clientId, userId, expires, cb) {
427+
cb();
428+
},
429+
expireRefreshToken: function (refreshToken, callback) {
430+
callback();
431+
}
432+
},
433+
grants: ['password', 'refresh_token']
434+
});
435+
436+
request(app)
437+
.post('/oauth/token')
438+
.set('Content-Type', 'application/x-www-form-urlencoded')
439+
.send({
440+
grant_type: 'refresh_token',
441+
client_id: 'thom',
442+
client_secret: 'nightworld',
443+
refresh_token: 'abc123'
444+
})
445+
.expect(/"access_token": "(.*)",\n\s+"refresh_token": "(.*)"/i, 400, done);
446+
447+
});
406448
});
407449

408450
describe('custom', function () {
@@ -762,6 +804,52 @@ describe('OAuth2Server.token()', function() {
762804
});
763805

764806
});
807+
808+
it('should exclude expires_in if accessTokenLifetime = null', function (done) {
809+
var app = bootstrap({
810+
model: {
811+
getClient: function (id, secret, callback) {
812+
callback(false, { client_id: id });
813+
},
814+
grantTypeAllowed: function (id, secret, callback) {
815+
callback(false, true);
816+
},
817+
getUser: function (uname, pword, callback) {
818+
callback(false, { id: 1 });
819+
},
820+
saveAccessToken: function (accessToken, clientId, userId, expires, callback) {
821+
should.strictEqual(null, expires);
822+
callback();
823+
},
824+
saveRefreshToken: function (refreshToken, clientId, userId, expires, callback) {
825+
should.strictEqual(null, expires);
826+
callback();
827+
}
828+
},
829+
grants: ['password', 'refresh_token'],
830+
accessTokenLifetime: null,
831+
refreshTokenLifetime: null
832+
});
833+
834+
request(app)
835+
.post('/oauth/token')
836+
.set('Content-Type', 'application/x-www-form-urlencoded')
837+
.send(validBody)
838+
.expect(200)
839+
.end(function (err, res) {
840+
if (err) return done(err);
841+
842+
res.body.should.have.keys(['access_token', 'refresh_token', 'token_type']);
843+
res.body.access_token.should.be.a('string');
844+
res.body.access_token.should.have.length(40);
845+
res.body.refresh_token.should.be.a('string');
846+
res.body.refresh_token.should.have.length(40);
847+
res.body.token_type.should.equal('bearer');
848+
849+
done();
850+
});
851+
852+
});
765853
});
766854

767855
});

0 commit comments

Comments
 (0)