From c33a2cd97b44220261c3f51e7b764081cb7d80ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20L=C3=BCthy?= Date: Wed, 14 Feb 2018 14:51:22 +0100 Subject: [PATCH] resolve merge conflict --- lib/grant-types/implicit-grant-type.js | 87 ++++++++ lib/handlers/authorize-handler.js | 106 +++------ lib/response-types/code-response-type.js | 130 ++++++++++- lib/response-types/token-response-type.js | 85 +++++++- lib/server.js | 1 + .../handlers/authorize-handler_test.js | 181 ++------------- .../response-types/code-response-type_test.js | 206 +++++++++++++++++- test/integration/server_test.js | 1 - test/unit/handlers/authorize-handler_test.js | 22 -- 9 files changed, 536 insertions(+), 283 deletions(-) create mode 100644 lib/grant-types/implicit-grant-type.js diff --git a/lib/grant-types/implicit-grant-type.js b/lib/grant-types/implicit-grant-type.js new file mode 100644 index 000000000..97461ebf3 --- /dev/null +++ b/lib/grant-types/implicit-grant-type.js @@ -0,0 +1,87 @@ +'use strict'; + +/** + * Module dependencies. + */ + +var AbstractGrantType = require('./abstract-grant-type'); +var InvalidArgumentError = require('../errors/invalid-argument-error'); +var Promise = require('bluebird'); +var util = require('util'); + +/** + * Constructor. + */ + +function ImplicitGrantType(options) { + options = options || {}; + + if (!options.model) { + throw new InvalidArgumentError('Missing parameter: `model`'); + } + + if (!options.model.saveToken) { + throw new InvalidArgumentError('Invalid argument: model does not implement `saveToken()`'); + } + + if (!options.user) { + throw new InvalidArgumentError('Missing parameter: `user`'); + } + + this.scope = options.scope; + this.user = options.user; + + AbstractGrantType.call(this, options); +} + +/** + * Inherit prototype. + */ + +util.inherits(ImplicitGrantType, AbstractGrantType); + +/** + * Handle implicit token grant. + */ + +ImplicitGrantType.prototype.handle = function(request, client) { + if (!request) { + throw new InvalidArgumentError('Missing parameter: `request`'); + } + + if (!client) { + throw new InvalidArgumentError('Missing parameter: `client`'); + } + + return this.saveToken(this.user, client, this.scope); +}; + +/** + * Save token. + */ + +ImplicitGrantType.prototype.saveToken = function(user, client, scope) { + var fns = [ + this.validateScope(user, client, scope), + this.generateAccessToken(), + this.getAccessTokenExpiresAt() + ]; + + return Promise.all(fns) + .bind(this) + .spread(function(scope, accessToken, accessTokenExpiresAt) { + var token = { + accessToken: accessToken, + accessTokenExpiresAt: accessTokenExpiresAt, + scope: scope + }; + + return this.model.saveToken(token, client, user); + }); +}; + +/** + * Export constructor. + */ + +module.exports = ImplicitGrantType; diff --git a/lib/handlers/authorize-handler.js b/lib/handlers/authorize-handler.js index 86d9bd313..c7b395ead 100644 --- a/lib/handlers/authorize-handler.js +++ b/lib/handlers/authorize-handler.js @@ -20,7 +20,6 @@ var Response = require('../response'); var ServerError = require('../errors/server-error'); var UnauthorizedClientError = require('../errors/unauthorized-client-error'); var is = require('../validator/is'); -var tokenUtil = require('../utils/token-util'); var url = require('url'); /** @@ -43,10 +42,6 @@ function AuthorizeHandler(options) { throw new InvalidArgumentError('Invalid argument: authenticateHandler does not implement `handle()`'); } - if (!options.authorizationCodeLifetime) { - throw new InvalidArgumentError('Missing parameter: `authorizationCodeLifetime`'); - } - if (!options.model) { throw new InvalidArgumentError('Missing parameter: `model`'); } @@ -55,13 +50,9 @@ function AuthorizeHandler(options) { throw new InvalidArgumentError('Invalid argument: model does not implement `getClient()`'); } - if (!options.model.saveAuthorizationCode) { - throw new InvalidArgumentError('Invalid argument: model does not implement `saveAuthorizationCode()`'); - } - + this.options = options; this.allowEmptyState = options.allowEmptyState; this.authenticateHandler = options.authenticateHandler || new AuthenticateHandler(options); - this.authorizationCodeLifetime = options.authorizationCodeLifetime; this.model = options.model; } @@ -86,20 +77,20 @@ AuthorizeHandler.prototype.handle = function(request, response) { this.model.request = request; var fns = [ - this.getAuthorizationCodeLifetime(), this.getClient(request), this.getUser(request, response) ]; return Promise.all(fns) .bind(this) - .spread(function(expiresAt, client, user) { - var uri = this.getRedirectUri(request, client); + .spread(function(client, user) { var scope; var state; - var ResponseType; + var responseType = this.getResponseType(request, client); + var uri = this.getRedirectUri(request, client); - return Promise.bind(this) + return Promise + .bind(this) .then(function() { var requestedScope = this.getScope(request); @@ -107,58 +98,31 @@ AuthorizeHandler.prototype.handle = function(request, response) { }) .then(function(validScope) { scope = validScope; - - return this.generateAuthorizationCode(client, user, scope); - }) - .then(function(authorizationCode) { state = this.getState(request); - ResponseType = this.getResponseType(request); - return this.saveAuthorizationCode(authorizationCode, expiresAt, scope, client, uri, user); + return responseType.handle(request, client, user, uri, scope); }) - .then(function(code) { - var responseType = new ResponseType(code.authorizationCode); + .then(function(codeOrAccessToken) { var redirectUri = this.buildSuccessRedirectUri(uri, responseType); - this.updateResponse(response, redirectUri, state); + this.updateResponse(response, redirectUri, responseType, state); - return code; + return codeOrAccessToken; }) .catch(function(e) { if (!(e instanceof OAuthError)) { e = new ServerError(e); } - var redirectUri = this.buildErrorRedirectUri(uri, e); - this.updateResponse(response, redirectUri, state); + var redirectUri = this.buildErrorRedirectUri(uri, responseType, e); + + this.updateResponse(response, redirectUri, responseType, state); throw e; }); }); }; -/** - * Generate authorization code. - */ - -AuthorizeHandler.prototype.generateAuthorizationCode = function(client, user, scope) { - if (this.model.generateAuthorizationCode) { - return promisify(this.model.generateAuthorizationCode, 3).call(this.model, client, user, scope); - } - return tokenUtil.generateRandomToken(); -}; - -/** - * Get authorization code lifetime. - */ - -AuthorizeHandler.prototype.getAuthorizationCodeLifetime = function() { - var expires = new Date(); - - expires.setSeconds(expires.getSeconds() + this.authorizationCodeLifetime); - return expires; -}; - /** * Get the client from the model. */ @@ -279,25 +243,12 @@ AuthorizeHandler.prototype.getRedirectUri = function(request, client) { return request.body.redirect_uri || request.query.redirect_uri || client.redirectUris[0]; }; -/** - * Save authorization code. - */ - -AuthorizeHandler.prototype.saveAuthorizationCode = function(authorizationCode, expiresAt, scope, client, redirectUri, user) { - var code = { - authorizationCode: authorizationCode, - expiresAt: expiresAt, - redirectUri: redirectUri, - scope: scope - }; - return promisify(this.model.saveAuthorizationCode, 3).call(this.model, code, client, user); -}; /** * Get response type. */ -AuthorizeHandler.prototype.getResponseType = function(request) { +AuthorizeHandler.prototype.getResponseType = function(request, client) { var responseType = request.body.response_type || request.query.response_type; if (!responseType) { @@ -308,7 +259,17 @@ AuthorizeHandler.prototype.getResponseType = function(request) { throw new UnsupportedResponseTypeError('Unsupported response type: `response_type` is not supported'); } - return responseTypes[responseType]; + if (!_.contains(['code', 'token'], responseType)) { + throw new InvalidRequestError('Invalid parameter: `response_type`'); + } + + if (!_.contains(client.grants, 'implicit') && responseType === 'token') { + throw new UnauthorizedClientError('Unauthorized client: `grant_type` is invalid'); + } + + var Type = responseTypes[responseType]; + + return new Type(this.options); }; /** @@ -316,22 +277,21 @@ AuthorizeHandler.prototype.getResponseType = function(request) { */ AuthorizeHandler.prototype.buildSuccessRedirectUri = function(redirectUri, responseType) { - return responseType.buildRedirectUri(redirectUri); + var uri = url.parse(redirectUri); + return responseType.buildRedirectUri(uri); }; /** * Build an error response that redirects the user-agent to the client-provided url. */ -AuthorizeHandler.prototype.buildErrorRedirectUri = function(redirectUri, error) { +AuthorizeHandler.prototype.buildErrorRedirectUri = function(redirectUri, responseType, error) { var uri = url.parse(redirectUri); - uri.query = { - error: error.name - }; + uri = responseType.setRedirectUriParam(uri, 'error', error.name); if (error.message) { - uri.query.error_description = error.message; + uri = responseType.setRedirectUriParam(uri, 'error_description', error.message); } return uri; @@ -341,11 +301,9 @@ AuthorizeHandler.prototype.buildErrorRedirectUri = function(redirectUri, error) * Update response with the redirect uri and the state parameter, if available. */ -AuthorizeHandler.prototype.updateResponse = function(response, redirectUri, state) { - redirectUri.query = redirectUri.query || {}; - +AuthorizeHandler.prototype.updateResponse = function(response, redirectUri, responseType, state) { if (state) { - redirectUri.query.state = state; + redirectUri = responseType.setRedirectUriParam(redirectUri, 'state', state); } response.redirect(url.format(redirectUri)); diff --git a/lib/response-types/code-response-type.js b/lib/response-types/code-response-type.js index 6eaf23a89..0a9cc8ae2 100644 --- a/lib/response-types/code-response-type.js +++ b/lib/response-types/code-response-type.js @@ -5,20 +5,118 @@ */ var InvalidArgumentError = require('../errors/invalid-argument-error'); -var url = require('url'); +var tokenUtil = require('../utils/token-util'); +var Promise = require('bluebird'); /** * Constructor. */ -function CodeResponseType(code) { - if (!code) { - throw new InvalidArgumentError('Missing parameter: `code`'); +function CodeResponseType(options) { + options = options || {}; + + if (!options.authorizationCodeLifetime) { + throw new InvalidArgumentError('Missing parameter: `authorizationCodeLifetime`'); + } + + if (!options.model) { + throw new InvalidArgumentError('Missing parameter: `model`'); } - this.code = code; + if (!options.model.saveAuthorizationCode) { + throw new InvalidArgumentError('Invalid argument: model does not implement `saveAuthorizationCode()`'); + } + + this.code = null; + this.authorizationCodeLifetime = options.authorizationCodeLifetime; + this.model = options.model; } +/** + * Handle code response type. + */ + +CodeResponseType.prototype.handle = function(request, client, user, uri, scope) { + if (!request) { + throw new InvalidArgumentError('Missing parameter: `request`'); + } + + if (!client) { + throw new InvalidArgumentError('Missing parameter: `client`'); + } + + if (!user) { + throw new InvalidArgumentError('Missing parameter: `user`'); + } + + if (!uri) { + throw new InvalidArgumentError('Missing parameter: `uri`'); + } + + var fns = [ + this.generateAuthorizationCode(), + this.getAuthorizationCodeExpiresAt(client) + ]; + + return Promise.all(fns) + .bind(this) + .spread(function(authorizationCode, expiresAt) { + return this.saveAuthorizationCode(authorizationCode, expiresAt, scope, client, uri, user); + }) + .then(function(code) { + this.code = code.authorizationCode; + return code; + }); +}; + +/** + * Get authorization code expiration date. + */ + +CodeResponseType.prototype.getAuthorizationCodeExpiresAt = function(client) { + var expires = new Date(); + var authorizationCodeLifetime = this.getAuthorizationCodeLifetime(client); + + expires.setSeconds(expires.getSeconds() + authorizationCodeLifetime); + + return expires; +}; + +/** + * Get authorization code lifetime. + */ + +CodeResponseType.prototype.getAuthorizationCodeLifetime = function(client) { + return client.authorizationCodeLifetime || this.authorizationCodeLifetime; +}; + +/** + * Save authorization code. + */ + +CodeResponseType.prototype.saveAuthorizationCode = function(authorizationCode, expiresAt, scope, client, redirectUri, user) { + var code = { + authorizationCode: authorizationCode, + expiresAt: expiresAt, + redirectUri: redirectUri, + scope: scope + }; + + return Promise.try(this.model.saveAuthorizationCode, [code, client, user]); +}; + +/** + * Generate authorization code. + */ + +CodeResponseType.prototype.generateAuthorizationCode = function() { + if (this.model.generateAuthorizationCode) { + return Promise.try(this.model.generateAuthorizationCode); + } + + return tokenUtil.generateRandomToken(); +}; + /** * Build redirect uri. */ @@ -27,13 +125,27 @@ CodeResponseType.prototype.buildRedirectUri = function(redirectUri) { if (!redirectUri) { throw new InvalidArgumentError('Missing parameter: `redirectUri`'); } + redirectUri.search = null; + return this.setRedirectUriParam(redirectUri, 'code', this.code); +}; - var uri = url.parse(redirectUri, true); +/** + * Set redirect uri parameter. + */ + +CodeResponseType.prototype.setRedirectUriParam = function(redirectUri, key, value) { + if (!redirectUri) { + throw new InvalidArgumentError('Missing parameter: `redirectUri`'); + } + + if (!key) { + throw new InvalidArgumentError('Missing parameter: `key`'); + } - uri.query.code = this.code; - uri.search = null; + redirectUri.query = redirectUri.query || {}; + redirectUri.query[key] = value; - return uri; + return redirectUri; }; /** diff --git a/lib/response-types/token-response-type.js b/lib/response-types/token-response-type.js index 2637f64cd..d9b5e1e1a 100644 --- a/lib/response-types/token-response-type.js +++ b/lib/response-types/token-response-type.js @@ -4,16 +4,95 @@ * Module dependencies. */ -var ServerError = require('../errors/server-error'); +var InvalidArgumentError = require('../errors/invalid-argument-error'); +var ImplicitGrantType = require('../grant-types/implicit-grant-type'); +var Promise = require('bluebird'); /** * Constructor. */ -function TokenResponseType() { - throw new ServerError('Not implemented.'); +function TokenResponseType(options) { + options = options || {}; + + if (!options.accessTokenLifetime) { + throw new InvalidArgumentError('Missing parameter: `accessTokenLifetime`'); + } + + this.options = options; + this.accessToken = null; + this.accessTokenLifetime = options.accessTokenLifetime; } +/** + * Handle token response type. + */ + +TokenResponseType.prototype.handle = function(request, client, user, uri, scope) { + if (!request) { + throw new InvalidArgumentError('Missing parameter: `request`'); + } + + if (!client) { + throw new InvalidArgumentError('Missing parameter: `client`'); + } + + var accessTokenLifetime = this.getAccessTokenLifetime(client); + + var options = { + user: user, + scope: scope, + model: this.options.model, + accessTokenLifetime: accessTokenLifetime + }; + + var grantType = new ImplicitGrantType(options); + + return Promise.bind(this) + .then(function() { + return grantType.handle(request, client); + }) + .then(function(token) { + this.accessToken = token.accessToken; + return token; + }); +}; + +/** + * Get access token lifetime. + */ + +TokenResponseType.prototype.getAccessTokenLifetime = function(client) { + return client.accessTokenLifetime || this.accessTokenLifetime; +}; + +/** + * Build redirect uri. + */ + +TokenResponseType.prototype.buildRedirectUri = function(redirectUri) { + return this.setRedirectUriParam(redirectUri, 'access_token', this.accessToken); +}; + +/** + * Set redirect uri parameter. + */ + +TokenResponseType.prototype.setRedirectUriParam = function(redirectUri, key, value) { + if (!redirectUri) { + throw new InvalidArgumentError('Missing parameter: `redirectUri`'); + } + + if (!key) { + throw new InvalidArgumentError('Missing parameter: `key`'); + } + + redirectUri.hash = redirectUri.hash || ''; + redirectUri.hash += (redirectUri.hash ? '&' : '') + key + '=' + encodeURIComponent(value); + + return redirectUri; +}; + /** * Export constructor. */ diff --git a/lib/server.js b/lib/server.js index fba9ccf81..570bd2b62 100644 --- a/lib/server.js +++ b/lib/server.js @@ -51,6 +51,7 @@ OAuth2Server.prototype.authenticate = function(request, response, options, callb OAuth2Server.prototype.authorize = function(request, response, options, callback) { options = _.assign({ allowEmptyState: false, + accessTokenLifetime: 60 * 60, // 1 hour. authorizationCodeLifetime: 5 * 60 // 5 minutes. }, this.options, options); diff --git a/test/integration/handlers/authorize-handler_test.js b/test/integration/handlers/authorize-handler_test.js index 1095c02b5..4ecbd1679 100644 --- a/test/integration/handlers/authorize-handler_test.js +++ b/test/integration/handlers/authorize-handler_test.js @@ -27,17 +27,6 @@ var url = require('url'); describe('AuthorizeHandler integration', function() { describe('constructor()', function() { - it('should throw an error if `options.authorizationCodeLifetime` is missing', function() { - try { - new AuthorizeHandler(); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `authorizationCodeLifetime`'); - } - }); - it('should throw an error if `options.model` is missing', function() { try { new AuthorizeHandler({ authorizationCodeLifetime: 120 }); @@ -60,17 +49,6 @@ describe('AuthorizeHandler integration', function() { } }); - it('should throw an error if the model does not implement `saveAuthorizationCode()`', function() { - try { - new AuthorizeHandler({ authorizationCodeLifetime: 120, model: { getClient: function() {} } }); - - should.fail(); - } catch (e) { - e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Invalid argument: model does not implement `saveAuthorizationCode()`'); - } - }); - it('should throw an error if the model does not implement `getAccessToken()`', function() { var model = { getClient: function() {}, @@ -87,17 +65,6 @@ describe('AuthorizeHandler integration', function() { } }); - it('should set the `authorizationCodeLifetime`', function() { - var model = { - getAccessToken: function() {}, - getClient: function() {}, - saveAuthorizationCode: function() {} - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - - handler.authorizationCodeLifetime.should.equal(120); - }); - it('should set the `authenticateHandler`', function() { var model = { getAccessToken: function() {}, @@ -108,17 +75,6 @@ describe('AuthorizeHandler integration', function() { handler.authenticateHandler.should.be.an.instanceOf(AuthenticateHandler); }); - - it('should set the `model`', function() { - var model = { - getAccessToken: function() {}, - getClient: function() {}, - saveAuthorizationCode: function() {} - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - - handler.model.should.equal(model); - }); }); describe('handle()', function() { @@ -577,64 +533,6 @@ describe('AuthorizeHandler integration', function() { }); }); - describe('generateAuthorizationCode()', function() { - it('should return an auth code', function() { - var model = { - getAccessToken: function() {}, - getClient: function() {}, - saveAuthorizationCode: function() {} - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - - return handler.generateAuthorizationCode() - .then(function(data) { - data.should.be.a.sha1; - }) - .catch(should.fail); - }); - - it('should support promises', function() { - var model = { - generateAuthorizationCode: function() { - return Promise.resolve({}); - }, - getAccessToken: function() {}, - getClient: function() {}, - saveAuthorizationCode: function() {} - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - - handler.generateAuthorizationCode().should.be.an.instanceOf(Promise); - }); - - it('should support non-promises', function() { - var model = { - generateAuthorizationCode: function() { - return {}; - }, - getAccessToken: function() {}, - getClient: function() {}, - saveAuthorizationCode: function() {} - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - - handler.generateAuthorizationCode().should.be.an.instanceOf(Promise); - }); - }); - - describe('getAuthorizationCodeLifetime()', function() { - it('should return a date', function() { - var model = { - getAccessToken: function() {}, - getClient: function() {}, - saveAuthorizationCode: function() {} - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - - handler.getAuthorizationCodeLifetime().should.be.an.instanceOf(Date); - }); - }); - describe('getClient()', function() { it('should throw an error if `client_id` is missing', function() { var model = { @@ -1024,65 +922,6 @@ describe('AuthorizeHandler integration', function() { }); }); - describe('saveAuthorizationCode()', function() { - it('should return an auth code', function() { - var authorizationCode = {}; - var model = { - getAccessToken: function() {}, - getClient: function() {}, - saveAuthorizationCode: function() { - return authorizationCode; - } - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - - return handler.saveAuthorizationCode('foo', 'bar', 'biz', 'baz') - .then(function(data) { - data.should.equal(authorizationCode); - }) - .catch(should.fail); - }); - - it('should support promises when calling `model.saveAuthorizationCode()`', function() { - var model = { - getAccessToken: function() {}, - getClient: function() {}, - saveAuthorizationCode: function() { - return Promise.resolve({}); - } - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - - handler.saveAuthorizationCode('foo', 'bar', 'biz', 'baz').should.be.an.instanceOf(Promise); - }); - - it('should support non-promises when calling `model.saveAuthorizationCode()`', function() { - var model = { - getAccessToken: function() {}, - getClient: function() {}, - saveAuthorizationCode: function() { - return {}; - } - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - - handler.saveAuthorizationCode('foo', 'bar', 'biz', 'baz').should.be.an.instanceOf(Promise); - }); - - it('should support callbacks when calling `model.saveAuthorizationCode()`', function() { - var model = { - getAccessToken: function() {}, - getClient: function() {}, - saveAuthorizationCode: function(code, client, user, callback) { - return callback(null, true); - } - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - - handler.saveAuthorizationCode('foo', 'bar', 'biz', 'baz').should.be.an.instanceOf(Promise); - }); - }); - describe('getResponseType()', function() { it('should throw an error if `response_type` is missing', function() { var model = { @@ -1161,7 +1000,10 @@ describe('AuthorizeHandler integration', function() { saveAuthorizationCode: function() {} }; var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var responseType = new CodeResponseType(12345); + var responseType = new CodeResponseType({ authorizationCodeLifetime: 120, model: { + saveAuthorizationCode: function() {} + }}); + responseType.code = 12345; var redirectUri = handler.buildSuccessRedirectUri('http://example.com/cb', responseType); url.format(redirectUri).should.equal('http://example.com/cb?code=12345'); @@ -1177,7 +1019,10 @@ describe('AuthorizeHandler integration', function() { saveAuthorizationCode: function() {} }; var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var redirectUri = handler.buildErrorRedirectUri('http://example.com/cb', error); + var responseType = new CodeResponseType({ authorizationCodeLifetime: 120, model: { + saveAuthorizationCode: function() {} + }}); + var redirectUri = handler.buildErrorRedirectUri('http://example.com/cb', responseType, error); url.format(redirectUri).should.equal('http://example.com/cb?error=invalid_client&error_description=foo%20bar'); }); @@ -1190,7 +1035,10 @@ describe('AuthorizeHandler integration', function() { saveAuthorizationCode: function() {} }; var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - var redirectUri = handler.buildErrorRedirectUri('http://example.com/cb', error); + var responseType = new CodeResponseType({ authorizationCodeLifetime: 120, model: { + saveAuthorizationCode: function() {} + }}); + var redirectUri = handler.buildErrorRedirectUri('http://example.com/cb', responseType, error); url.format(redirectUri).should.equal('http://example.com/cb?error=invalid_client&error_description=Bad%20Request'); }); @@ -1206,8 +1054,11 @@ describe('AuthorizeHandler integration', function() { var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); var response = new Response({ body: {}, headers: {} }); var uri = url.parse('http://example.com/cb'); + var responseType = new CodeResponseType({ authorizationCodeLifetime: 120, model: { + saveAuthorizationCode: function() {} + }}); - handler.updateResponse(response, uri, 'foobar'); + handler.updateResponse(response, uri, responseType, 'foobar'); response.get('location').should.equal('http://example.com/cb?state=foobar'); }); diff --git a/test/integration/response-types/code-response-type_test.js b/test/integration/response-types/code-response-type_test.js index 5461b62c4..93284b61d 100644 --- a/test/integration/response-types/code-response-type_test.js +++ b/test/integration/response-types/code-response-type_test.js @@ -6,7 +6,9 @@ var CodeResponseType = require('../../../lib/response-types/code-response-type'); var InvalidArgumentError = require('../../../lib/errors/invalid-argument-error'); +var Promise = require('bluebird'); var should = require('should'); +var sinon = require('sinon'); var url = require('url'); /** @@ -15,27 +17,53 @@ var url = require('url'); describe('CodeResponseType integration', function() { describe('constructor()', function() { - it('should throw an error if `code` is missing', function() { + it('should throw an error if `options.authorizationCodeLifetime` is missing', function() { try { new CodeResponseType(); should.fail(); } catch (e) { e.should.be.an.instanceOf(InvalidArgumentError); - e.message.should.equal('Missing parameter: `code`'); + e.message.should.equal('Missing parameter: `authorizationCodeLifetime`'); } }); it('should set the `code`', function() { - var responseType = new CodeResponseType('foo'); + var model = { + saveAuthorizationCode: function() {} + }; + var responseType = new CodeResponseType({ authorizationCodeLifetime: 120, model: model }); - responseType.code.should.equal('foo'); + responseType.authorizationCodeLifetime.should.equal(120); }); }); + it('should throw an error if the model does not implement `saveAuthorizationCode()`', function() { + try { + new CodeResponseType({ authorizationCodeLifetime: 120, model: { } }); + + should.fail(); + } catch (e) { + e.should.be.an.instanceOf(InvalidArgumentError); + e.message.should.equal('Invalid argument: model does not implement `saveAuthorizationCode()`'); + } + }); + + it('should set the `authorizationCodeLifetime`', function() { + var model = { + saveAuthorizationCode: function() {} + }; + var handler = new CodeResponseType({ authorizationCodeLifetime: 120, model: model }); + + handler.authorizationCodeLifetime.should.equal(120); + }); + describe('buildRedirectUri()', function() { it('should throw an error if the `redirectUri` is missing', function() { - var responseType = new CodeResponseType('foo'); + var model = { + saveAuthorizationCode: function() {} + }; + var responseType = new CodeResponseType({ authorizationCodeLifetime: 120, model: model }); try { responseType.buildRedirectUri(); @@ -48,17 +76,177 @@ describe('CodeResponseType integration', function() { }); it('should return the new redirect uri and set the `code` and `state` in the query', function() { - var responseType = new CodeResponseType('foo'); - var redirectUri = responseType.buildRedirectUri('http://example.com/cb'); + var model = { + saveAuthorizationCode: function() {} + }; + var responseType = new CodeResponseType({ authorizationCodeLifetime: 120, model: model }); + responseType.code = 'foo'; + var redirectUri = responseType.buildRedirectUri(url.parse('http://example.com/cb')); url.format(redirectUri).should.equal('http://example.com/cb?code=foo'); }); it('should return the new redirect uri and append the `code` and `state` in the query', function() { - var responseType = new CodeResponseType('foo'); - var redirectUri = responseType.buildRedirectUri('http://example.com/cb?foo=bar'); + var model = { + saveAuthorizationCode: function() {} + }; + var responseType = new CodeResponseType({ authorizationCodeLifetime: 120, model: model }); + responseType.code = 'foo'; + var redirectUri = responseType.buildRedirectUri(url.parse('http://example.com/cb?foo=bar', true)); url.format(redirectUri).should.equal('http://example.com/cb?foo=bar&code=foo'); }); }); + + it('should set the `model`', function() { + var model = { + saveAuthorizationCode: function() {} + }; + var handler = new CodeResponseType({ authorizationCodeLifetime: 120, model: model }); + + handler.model.should.equal(model); + }); + + describe('generateAuthorizationCode()', function() { + it('should return an auth code', function() { + var model = { + getAccessToken: function() {}, + getClient: function() {}, + saveAuthorizationCode: function() {} + }; + var handler = new CodeResponseType({ authorizationCodeLifetime: 120, model: model }); + + return handler.generateAuthorizationCode() + .then(function(data) { + data.should.be.a.sha1; + }) + .catch(should.fail); + }); + + it('should support promises', function() { + var model = { + generateAuthorizationCode: function() { + return Promise.resolve({}); + }, + getAccessToken: function() {}, + getClient: function() {}, + saveAuthorizationCode: function() {} + }; + var handler = new CodeResponseType({ authorizationCodeLifetime: 120, model: model }); + + handler.generateAuthorizationCode().should.be.an.instanceOf(Promise); + }); + + it('should support non-promises', function() { + var model = { + generateAuthorizationCode: function() { + return {}; + }, + getAccessToken: function() {}, + getClient: function() {}, + saveAuthorizationCode: function() {} + }; + var handler = new CodeResponseType({ authorizationCodeLifetime: 120, model: model }); + + handler.generateAuthorizationCode().should.be.an.instanceOf(Promise); + }); + }); + + describe('getAuthorizationCodeExpiresAt()', function() { + it('should return a date', function() { + var model = { + getAccessToken: function() {}, + getClient: function() {}, + saveAuthorizationCode: function() {} + }; + var handler = new CodeResponseType({ authorizationCodeLifetime: 120, model: model }); + + handler.getAuthorizationCodeExpiresAt({}).should.be.an.instanceOf(Date); + }); + }); + + describe('saveAuthorizationCode()', function() { + it('should return an auth code', function() { + var authorizationCode = {}; + var model = { + getAccessToken: function() {}, + getClient: function() {}, + saveAuthorizationCode: function() { + return authorizationCode; + } + }; + var handler = new CodeResponseType({ authorizationCodeLifetime: 120, model: model }); + + return handler.saveAuthorizationCode('foo', 'bar', 'biz', 'baz') + .then(function(data) { + data.should.equal(authorizationCode); + }) + .catch(should.fail); + }); + + it('should support promises when calling `model.saveAuthorizationCode()`', function() { + var model = { + getAccessToken: function() {}, + getClient: function() {}, + saveAuthorizationCode: function() { + return Promise.resolve({}); + } + }; + var handler = new CodeResponseType({ authorizationCodeLifetime: 120, model: model }); + + handler.saveAuthorizationCode('foo', 'bar', 'biz', 'baz').should.be.an.instanceOf(Promise); + }); + + it('should support non-promises when calling `model.saveAuthorizationCode()`', function() { + var model = { + getAccessToken: function() {}, + getClient: function() {}, + saveAuthorizationCode: function() { + return {}; + } + }; + var handler = new CodeResponseType({ authorizationCodeLifetime: 120, model: model }); + + handler.saveAuthorizationCode('foo', 'bar', 'biz', 'baz').should.be.an.instanceOf(Promise); + }); + }); + + describe('saveAuthorizationCode()', function() { + it('should call `model.saveAuthorizationCode()`', function() { + var model = { + getAccessToken: function() {}, + getClient: function() {}, + saveAuthorizationCode: sinon.stub().returns({}) + }; + var handler = new CodeResponseType({ authorizationCodeLifetime: 120, model: model }); + + return handler.saveAuthorizationCode('foo', 'bar', 'qux', 'biz', 'baz', 'boz') + .then(function() { + model.saveAuthorizationCode.callCount.should.equal(1); + model.saveAuthorizationCode.firstCall.args.should.have.length(3); + model.saveAuthorizationCode.firstCall.args[0].should.eql({ authorizationCode: 'foo', expiresAt: 'bar', redirectUri: 'baz', scope: 'qux' }); + model.saveAuthorizationCode.firstCall.args[1].should.equal('biz'); + model.saveAuthorizationCode.firstCall.args[2].should.equal('boz'); + }) + .catch(should.fail); + }); + }); + + describe('generateAuthorizationCode()', function() { + it('should call `model.generateAuthorizationCode()`', function() { + var model = { + generateAuthorizationCode: sinon.stub().returns({}), + getAccessToken: function() {}, + getClient: function() {}, + saveAuthorizationCode: function() {} + }; + var handler = new CodeResponseType({ authorizationCodeLifetime: 120, model: model }); + + return handler.generateAuthorizationCode() + .then(function() { + model.generateAuthorizationCode.callCount.should.equal(1); + }) + .catch(should.fail); + }); + }); }); diff --git a/test/integration/server_test.js b/test/integration/server_test.js index 2d3aa7845..84761fb21 100644 --- a/test/integration/server_test.js +++ b/test/integration/server_test.js @@ -116,7 +116,6 @@ describe('Server integration', function() { return server.authorize(request, response) .then(function() { this.allowEmptyState.should.be.false; - this.authorizationCodeLifetime.should.equal(300); }) .catch(should.fail); }); diff --git a/test/unit/handlers/authorize-handler_test.js b/test/unit/handlers/authorize-handler_test.js index 1f8ee0521..781d060d9 100644 --- a/test/unit/handlers/authorize-handler_test.js +++ b/test/unit/handlers/authorize-handler_test.js @@ -111,26 +111,4 @@ describe('AuthorizeHandler', function() { .catch(should.fail); }); }); - - describe('saveAuthorizationCode()', function() { - it('should call `model.saveAuthorizationCode()`', function() { - var model = { - getAccessToken: function() {}, - getClient: function() {}, - saveAuthorizationCode: sinon.stub().returns({}) - }; - var handler = new AuthorizeHandler({ authorizationCodeLifetime: 120, model: model }); - - return handler.saveAuthorizationCode('foo', 'bar', 'qux', 'biz', 'baz', 'boz') - .then(function() { - model.saveAuthorizationCode.callCount.should.equal(1); - model.saveAuthorizationCode.firstCall.args.should.have.length(3); - model.saveAuthorizationCode.firstCall.args[0].should.eql({ authorizationCode: 'foo', expiresAt: 'bar', redirectUri: 'baz', scope: 'qux' }); - model.saveAuthorizationCode.firstCall.args[1].should.equal('biz'); - model.saveAuthorizationCode.firstCall.args[2].should.equal('boz'); - model.saveAuthorizationCode.firstCall.thisValue.should.equal(model); - }) - .catch(should.fail); - }); - }); });