From abc35a26c75647af352528df44d8e0d09708b094 Mon Sep 17 00:00:00 2001 From: Eduardo Diaz Date: Wed, 9 Aug 2017 15:57:33 +0200 Subject: [PATCH] exchanges: add scope type validation --- lib/exchange/clientCredentials.js | 16 +- lib/exchange/password.js | 16 +- lib/exchange/refreshToken.js | 22 +-- test/exchange/clientCredentials.test.js | 173 ++++++++++++--------- test/exchange/password.test.js | 187 +++++++++++++---------- test/exchange/refreshToken.test.js | 192 ++++++++++++++---------- 6 files changed, 353 insertions(+), 253 deletions(-) diff --git a/lib/exchange/clientCredentials.js b/lib/exchange/clientCredentials.js index f44305e7..316a950d 100644 --- a/lib/exchange/clientCredentials.js +++ b/lib/exchange/clientCredentials.js @@ -60,7 +60,7 @@ module.exports = function(options, issue) { options = undefined; } options = options || {}; - + if (!issue) { throw new TypeError('oauth2orize.clientCredentials exchange requires an issue callback'); } var userProperty = options.userProperty || 'user'; @@ -77,13 +77,17 @@ module.exports = function(options, issue) { return function client_credentials(req, res, next) { if (!req.body) { return next(new Error('OAuth2orize requires body parsing. Did you forget app.use(express.bodyParser())?')); } - + // The 'user' property of `req` holds the authenticated user. In the case // of the token endpoint, the property will contain the OAuth 2.0 client. var client = req[userProperty] , scope = req.body.scope; - + if (scope) { + if (typeof scope !== 'string') { + return next(new TokenError('Invalid parameter: scope must be a string', 'invalid_scope')); + } + for (var i = 0, len = separators.length; i < len; i++) { var separated = scope.split(separators[i]); // only separate on the first matching separator. this allows for a sort @@ -95,7 +99,7 @@ module.exports = function(options, issue) { } if (!Array.isArray(scope)) { scope = [ scope ]; } } - + function issued(err, accessToken, refreshToken, params) { if (err) { return next(err); } if (!accessToken) { return next(new TokenError('Invalid client credentials', 'invalid_grant')); } @@ -109,14 +113,14 @@ module.exports = function(options, issue) { if (refreshToken) { tok.refresh_token = refreshToken; } if (params) { utils.merge(tok, params); } tok.token_type = tok.token_type || 'Bearer'; - + var json = JSON.stringify(tok); res.setHeader('Content-Type', 'application/json'); res.setHeader('Cache-Control', 'no-store'); res.setHeader('Pragma', 'no-cache'); res.end(json); } - + try { var arity = issue.length; if (arity == 5) { diff --git a/lib/exchange/password.js b/lib/exchange/password.js index aae08c34..3f0060ca 100644 --- a/lib/exchange/password.js +++ b/lib/exchange/password.js @@ -78,18 +78,22 @@ module.exports = function(options, issue) { return function password(req, res, next) { if (!req.body) { return next(new Error('OAuth2orize requires body parsing. Did you forget app.use(express.bodyParser())?')); } - + // The 'user' property of `req` holds the authenticated user. In the case // of the token endpoint, the property will contain the OAuth 2.0 client. var client = req[userProperty] , username = req.body.username , passwd = req.body.password , scope = req.body.scope; - + if (!username) { return next(new TokenError('Missing required parameter: username', 'invalid_request')); } if (!passwd) { return next(new TokenError('Missing required parameter: password', 'invalid_request')); } - + if (scope) { + if (typeof scope !== 'string') { + return next(new TokenError('Invalid parameter: scope must be a string', 'invalid_scope')); + } + for (var i = 0, len = separators.length; i < len; i++) { var separated = scope.split(separators[i]); // only separate on the first matching separator. this allows for a sort @@ -101,7 +105,7 @@ module.exports = function(options, issue) { } if (!Array.isArray(scope)) { scope = [ scope ]; } } - + function issued(err, accessToken, refreshToken, params) { if (err) { return next(err); } if (!accessToken) { return next(new TokenError('Invalid resource owner credentials', 'invalid_grant')); } @@ -109,13 +113,13 @@ module.exports = function(options, issue) { params = refreshToken; refreshToken = null; } - + var tok = {}; tok.access_token = accessToken; if (refreshToken) { tok.refresh_token = refreshToken; } if (params) { utils.merge(tok, params); } tok.token_type = tok.token_type || 'Bearer'; - + var json = JSON.stringify(tok); res.setHeader('Content-Type', 'application/json'); res.setHeader('Cache-Control', 'no-store'); diff --git a/lib/exchange/refreshToken.js b/lib/exchange/refreshToken.js index c1abf658..ee06dcbe 100644 --- a/lib/exchange/refreshToken.js +++ b/lib/exchange/refreshToken.js @@ -58,9 +58,9 @@ module.exports = function(options, issue) { options = undefined; } options = options || {}; - + if (!issue) { throw new TypeError('oauth2orize.refreshToken exchange requires an issue callback'); } - + var userProperty = options.userProperty || 'user'; // For maximum flexibility, multiple scope spearators can optionally be @@ -75,16 +75,20 @@ module.exports = function(options, issue) { return function refresh_token(req, res, next) { if (!req.body) { return next(new Error('OAuth2orize requires body parsing. Did you forget app.use(express.bodyParser())?')); } - + // The 'user' property of `req` holds the authenticated user. In the case // of the token endpoint, the property will contain the OAuth 2.0 client. var client = req[userProperty] , refreshToken = req.body.refresh_token , scope = req.body.scope; - + if (!refreshToken) { return next(new TokenError('Missing required parameter: refresh_token', 'invalid_request')); } - + if (scope) { + if (typeof scope !== 'string') { + return next(new TokenError('Invalid parameter: scope must be a string', 'invalid_scope')); + } + for (var i = 0, len = separators.length; i < len; i++) { var separated = scope.split(separators[i]); // only separate on the first matching separator. this allows for a sort @@ -96,7 +100,7 @@ module.exports = function(options, issue) { } if (!Array.isArray(scope)) { scope = [ scope ]; } } - + function issued(err, accessToken, refreshToken, params) { if (err) { return next(err); } if (!accessToken) { return next(new TokenError('Invalid refresh token', 'invalid_grant')); } @@ -104,20 +108,20 @@ module.exports = function(options, issue) { params = refreshToken; refreshToken = null; } - + var tok = {}; tok.access_token = accessToken; if (refreshToken) { tok.refresh_token = refreshToken; } if (params) { utils.merge(tok, params); } tok.token_type = tok.token_type || 'Bearer'; - + var json = JSON.stringify(tok); res.setHeader('Content-Type', 'application/json'); res.setHeader('Cache-Control', 'no-store'); res.setHeader('Pragma', 'no-cache'); res.end(json); } - + try { var arity = issue.length; if (arity == 6) { diff --git a/test/exchange/clientCredentials.test.js b/test/exchange/clientCredentials.test.js index 5d188e1f..4d9a7ee1 100644 --- a/test/exchange/clientCredentials.test.js +++ b/test/exchange/clientCredentials.test.js @@ -3,27 +3,27 @@ var chai = require('chai') describe('exchange.clientCredentials', function() { - + it('should be named client_credentials', function() { expect(clientCredentials(function(){}).name).to.equal('client_credentials'); }); - + it('should throw if constructed without a issue callback', function() { expect(function() { clientCredentials(); }).to.throw(TypeError, 'oauth2orize.clientCredentials exchange requires an issue callback'); }); - + describe('issuing an access token', function() { var response, err; before(function(done) { function issue(client, done) { if (client.id !== 'c123') { return done(new Error('incorrect client argument')); } - + return done(null, 's3cr1t') } - + chai.connect.use(clientCredentials(issue)) .req(function(req) { req.user = { id: 'c123', name: 'Example' }; @@ -35,28 +35,28 @@ describe('exchange.clientCredentials', function() { }) .dispatch(); }); - + it('should respond with headers', function() { expect(response.getHeader('Content-Type')).to.equal('application/json'); expect(response.getHeader('Cache-Control')).to.equal('no-store'); expect(response.getHeader('Pragma')).to.equal('no-cache'); }); - + it('should respond with body', function() { expect(response.body).to.equal('{"access_token":"s3cr1t","token_type":"Bearer"}'); }); }); - + describe('issuing an access token and refresh token', function() { var response, err; before(function(done) { function issue(client, done) { if (client.id !== 'c223') { return done(new Error('incorrect client argument')); } - + return done(null, 's3cr1t', 'getANotehr') } - + chai.connect.use(clientCredentials(issue)) .req(function(req) { req.user = { id: 'c223', name: 'Example' }; @@ -68,28 +68,28 @@ describe('exchange.clientCredentials', function() { }) .dispatch(); }); - + it('should respond with headers', function() { expect(response.getHeader('Content-Type')).to.equal('application/json'); expect(response.getHeader('Cache-Control')).to.equal('no-store'); expect(response.getHeader('Pragma')).to.equal('no-cache'); }); - + it('should respond with body', function() { expect(response.body).to.equal('{"access_token":"s3cr1t","refresh_token":"getANotehr","token_type":"Bearer"}'); }); }); - + describe('issuing an access token and params', function() { var response, err; before(function(done) { function issue(client, done) { if (client.id !== 'c523') { return done(new Error('incorrect client argument')); } - + return done(null, 's3cr1t', { 'expires_in': 3600 }) } - + chai.connect.use(clientCredentials(issue)) .req(function(req) { req.user = { id: 'c523', name: 'Example' }; @@ -101,28 +101,28 @@ describe('exchange.clientCredentials', function() { }) .dispatch(); }); - + it('should respond with headers', function() { expect(response.getHeader('Content-Type')).to.equal('application/json'); expect(response.getHeader('Cache-Control')).to.equal('no-store'); expect(response.getHeader('Pragma')).to.equal('no-cache'); }); - + it('should respond with body', function() { expect(response.body).to.equal('{"access_token":"s3cr1t","expires_in":3600,"token_type":"Bearer"}'); }); }); - + describe('issuing an access token, null refresh token, and params', function() { var response, err; before(function(done) { function issue(client, done) { if (client.id !== 'c323') { return done(new Error('incorrect client argument')); } - + return done(null, 's3cr1t', null, { 'expires_in': 3600 }) } - + chai.connect.use(clientCredentials(issue)) .req(function(req) { req.user = { id: 'c323', name: 'Example' }; @@ -134,28 +134,28 @@ describe('exchange.clientCredentials', function() { }) .dispatch(); }); - + it('should respond with headers', function() { expect(response.getHeader('Content-Type')).to.equal('application/json'); expect(response.getHeader('Cache-Control')).to.equal('no-store'); expect(response.getHeader('Pragma')).to.equal('no-cache'); }); - + it('should respond with body', function() { expect(response.body).to.equal('{"access_token":"s3cr1t","expires_in":3600,"token_type":"Bearer"}'); }); }); - + describe('issuing an access token, refresh token, and params with token_type', function() { var response, err; before(function(done) { function issue(client, done) { if (client.id !== 'c423') { return done(new Error('incorrect client argument')); } - + return done(null, 's3cr1t', 'blahblag', { 'token_type': 'foo', 'expires_in': 3600 }) } - + chai.connect.use(clientCredentials(issue)) .req(function(req) { req.user = { id: 'c423', name: 'Example' }; @@ -167,27 +167,27 @@ describe('exchange.clientCredentials', function() { }) .dispatch(); }); - + it('should respond with headers', function() { expect(response.getHeader('Content-Type')).to.equal('application/json'); expect(response.getHeader('Cache-Control')).to.equal('no-store'); expect(response.getHeader('Pragma')).to.equal('no-cache'); }); - + it('should respond with body', function() { expect(response.body).to.equal('{"access_token":"s3cr1t","refresh_token":"blahblag","token_type":"foo","expires_in":3600}'); }); }); - + describe('issuing an access token based on scope', function() { function issue(client, scope, done) { if (client.id !== 'c123') { return done(new Error('incorrect client argument')); } if (scope.length !== 1) { return done(new Error('incorrect scope argument')); } if (scope[0] !== 'read') { return done(new Error('incorrect scope argument')); } - + return done(null, 's3cr1t') } - + var response, err; before(function(done) { @@ -202,28 +202,28 @@ describe('exchange.clientCredentials', function() { }) .dispatch(); }); - + it('should respond with headers', function() { expect(response.getHeader('Content-Type')).to.equal('application/json'); expect(response.getHeader('Cache-Control')).to.equal('no-store'); expect(response.getHeader('Pragma')).to.equal('no-cache'); }); - + it('should respond with body', function() { expect(response.body).to.equal('{"access_token":"s3cr1t","token_type":"Bearer"}'); }); }); - + describe('issuing an access token based on array of scopes', function() { function issue(client, scope, done) { if (client.id !== 'c123') { return done(new Error('incorrect client argument')); } if (scope.length !== 2) { return done(new Error('incorrect scope argument')); } if (scope[0] !== 'read') { return done(new Error('incorrect scope argument')); } if (scope[1] !== 'write') { return done(new Error('incorrect scope argument')); } - + return done(null, 's3cr1t') } - + var response, err; before(function(done) { @@ -238,28 +238,28 @@ describe('exchange.clientCredentials', function() { }) .dispatch(); }); - + it('should respond with headers', function() { expect(response.getHeader('Content-Type')).to.equal('application/json'); expect(response.getHeader('Cache-Control')).to.equal('no-store'); expect(response.getHeader('Pragma')).to.equal('no-cache'); }); - + it('should respond with body', function() { expect(response.body).to.equal('{"access_token":"s3cr1t","token_type":"Bearer"}'); }); }); - + describe('issuing an access token based on scope and body', function() { function issue(client, scope, body, done) { if (client.id !== 'c123') { return done(new Error('incorrect client argument')); } if (scope.length !== 1) { return done(new Error('incorrect scope argument')); } if (scope[0] !== 'read') { return done(new Error('incorrect scope argument')); } if (body.audience !== 'https://www.example.com/') { return done(new Error('incorrect body argument')); } - + return done(null, 's3cr1t') } - + var response, err; before(function(done) { @@ -274,18 +274,18 @@ describe('exchange.clientCredentials', function() { }) .dispatch(); }); - + it('should respond with headers', function() { expect(response.getHeader('Content-Type')).to.equal('application/json'); expect(response.getHeader('Cache-Control')).to.equal('no-store'); expect(response.getHeader('Pragma')).to.equal('no-cache'); }); - + it('should respond with body', function() { expect(response.body).to.equal('{"access_token":"s3cr1t","token_type":"Bearer"}'); }); }); - + describe('issuing an access token based on authInfo', function() { var response, err; @@ -323,7 +323,7 @@ describe('exchange.clientCredentials', function() { expect(response.body).to.equal('{"access_token":"s3cr1t","token_type":"Bearer"}'); }); }); - + describe('not issuing an access token', function() { var response, err; @@ -331,7 +331,7 @@ describe('exchange.clientCredentials', function() { function issue(client, done) { return done(null, false) } - + chai.connect.use(clientCredentials(issue)) .req(function(req) { req.user = { id: 'cUN', name: 'Example' }; @@ -343,7 +343,7 @@ describe('exchange.clientCredentials', function() { }) .dispatch(); }); - + it('should error', function() { expect(err).to.be.an.instanceOf(Error); expect(err.constructor.name).to.equal('TokenError'); @@ -352,7 +352,7 @@ describe('exchange.clientCredentials', function() { expect(err.status).to.equal(403); }); }); - + describe('encountering an error while issuing an access token', function() { var response, err; @@ -360,7 +360,7 @@ describe('exchange.clientCredentials', function() { function issue(client, done) { return done(new Error('something is wrong')); } - + chai.connect.use(clientCredentials(issue)) .req(function(req) { req.user = { id: 'cXXX', name: 'Example' }; @@ -372,13 +372,13 @@ describe('exchange.clientCredentials', function() { }) .dispatch(); }); - + it('should error', function() { expect(err).to.be.an.instanceOf(Error); expect(err.message).to.equal('something is wrong'); }); }); - + describe('encountering an exception while issuing an access token', function() { var response, err; @@ -386,7 +386,7 @@ describe('exchange.clientCredentials', function() { function issue(client, done) { throw new Error('something was thrown') } - + chai.connect.use(clientCredentials(issue)) .req(function(req) { req.user = { id: 'cTHROW', name: 'Example' }; @@ -398,13 +398,13 @@ describe('exchange.clientCredentials', function() { }) .dispatch(); }); - + it('should error', function() { expect(err).to.be.an.instanceOf(Error); expect(err.message).to.equal('something was thrown'); }); }); - + describe('handling a request without a body', function() { var response, err; @@ -412,7 +412,7 @@ describe('exchange.clientCredentials', function() { function issue(client, done) { return done(null, '.ignore') } - + chai.connect.use(clientCredentials(issue)) .req(function(req) { req.user = { id: 'c123', name: 'Example' }; @@ -423,23 +423,52 @@ describe('exchange.clientCredentials', function() { }) .dispatch(); }); - + it('should error', function() { expect(err).to.be.an.instanceOf(Error); expect(err.message).to.equal('OAuth2orize requires body parsing. Did you forget app.use(express.bodyParser())?'); }); }); - + + describe('handling a request where scope format is not string', function () { + var response, err; + + before(function (done) { + function issue(client, done) { + return done(null, '.ignore') + } + + chai.connect.use(clientCredentials(issue)) + .req(function (req) { + req.user = { id: 'c123', name: 'Example' }; + req.body = { scope: ['read', 'write'] }; + }) + .next(function (e) { + err = e; + done(); + }) + .dispatch(); + }); + + it('should error', function () { + expect(err).to.be.an.instanceOf(Error); + expect(err.name).to.equal('TokenError'); + expect(err.message).to.equal('Invalid parameter: scope must be a string'); + expect(err.code).to.equal('invalid_scope'); + expect(err.status).to.equal(400); + }); + }); + describe('with scope separator option', function() { function issue(client, scope, done) { if (client.id !== 'c123') { return done(new Error('incorrect client argument')); } if (scope.length !== 2) { return done(new Error('incorrect scope argument')); } if (scope[0] !== 'read') { return done(new Error('incorrect scope argument')); } if (scope[1] !== 'write') { return done(new Error('incorrect scope argument')); } - + return done(null, 's3cr1t') } - + describe('issuing an access token based on scope', function() { var response, err; @@ -455,29 +484,29 @@ describe('exchange.clientCredentials', function() { }) .dispatch(); }); - + it('should respond with headers', function() { expect(response.getHeader('Content-Type')).to.equal('application/json'); expect(response.getHeader('Cache-Control')).to.equal('no-store'); expect(response.getHeader('Pragma')).to.equal('no-cache'); }); - + it('should respond with body', function() { expect(response.body).to.equal('{"access_token":"s3cr1t","token_type":"Bearer"}'); }); }); }); - + describe('with multiple scope separator option', function() { function issue(client, scope, done) { if (client.id !== 'c123') { return done(new Error('incorrect client argument')); } if (scope.length !== 2) { return done(new Error('incorrect scope argument')); } if (scope[0] !== 'read') { return done(new Error('incorrect scope argument')); } if (scope[1] !== 'write') { return done(new Error('incorrect scope argument')); } - + return done(null, 's3cr1t') } - + describe('issuing an access token based on scope separated by space', function() { var response, err; @@ -493,18 +522,18 @@ describe('exchange.clientCredentials', function() { }) .dispatch(); }); - + it('should respond with headers', function() { expect(response.getHeader('Content-Type')).to.equal('application/json'); expect(response.getHeader('Cache-Control')).to.equal('no-store'); expect(response.getHeader('Pragma')).to.equal('no-cache'); }); - + it('should respond with body', function() { expect(response.body).to.equal('{"access_token":"s3cr1t","token_type":"Bearer"}'); }); }); - + describe('issuing an access token based on scope separated by comma', function() { var response, err; @@ -520,13 +549,13 @@ describe('exchange.clientCredentials', function() { }) .dispatch(); }); - + it('should respond with headers', function() { expect(response.getHeader('Content-Type')).to.equal('application/json'); expect(response.getHeader('Cache-Control')).to.equal('no-store'); expect(response.getHeader('Pragma')).to.equal('no-cache'); }); - + it('should respond with body', function() { expect(response.body).to.equal('{"access_token":"s3cr1t","token_type":"Bearer"}'); }); @@ -539,10 +568,10 @@ describe('exchange.clientCredentials', function() { before(function(done) { function issue(client, done) { if (client.id !== 'c123') { return done(new Error('incorrect client argument')); } - + return done(null, 's3cr1t') } - + chai.connect.use(clientCredentials({ userProperty: 'client' }, issue)) .req(function(req) { req.client = { id: 'c123', name: 'Example' }; @@ -554,13 +583,13 @@ describe('exchange.clientCredentials', function() { }) .dispatch(); }); - + it('should respond with headers', function() { expect(response.getHeader('Content-Type')).to.equal('application/json'); expect(response.getHeader('Cache-Control')).to.equal('no-store'); expect(response.getHeader('Pragma')).to.equal('no-cache'); }); - + it('should respond with body', function() { expect(response.body).to.equal('{"access_token":"s3cr1t","token_type":"Bearer"}'); }); diff --git a/test/exchange/password.test.js b/test/exchange/password.test.js index 08f507db..2915a1fc 100644 --- a/test/exchange/password.test.js +++ b/test/exchange/password.test.js @@ -3,17 +3,17 @@ var chai = require('chai') describe('exchange.password', function() { - + it('should be named password', function() { expect(password(function(){}).name).to.equal('password'); }); - + it('should throw if constructed without a issue callback', function() { expect(function() { password(); }).to.throw(TypeError, 'oauth2orize.password exchange requires an issue callback'); }); - + describe('issuing an access token', function() { var response, err; @@ -22,10 +22,10 @@ describe('exchange.password', function() { if (client.id !== 'c123') { return done(new Error('incorrect client argument')); } if (username !== 'bob') { return done(new Error('incorrect username argument')); } if (passwd !== 'shh') { return done(new Error('incorrect passwd argument')); } - + return done(null, 's3cr1t') } - + chai.connect.use(password(issue)) .req(function(req) { req.user = { id: 'c123', name: 'Example' }; @@ -37,18 +37,18 @@ describe('exchange.password', function() { }) .dispatch(); }); - + it('should respond with headers', function() { expect(response.getHeader('Content-Type')).to.equal('application/json'); expect(response.getHeader('Cache-Control')).to.equal('no-store'); expect(response.getHeader('Pragma')).to.equal('no-cache'); }); - + it('should respond with body', function() { expect(response.body).to.equal('{"access_token":"s3cr1t","token_type":"Bearer"}'); }); }); - + describe('issuing an access token and refresh token', function() { var response, err; @@ -57,10 +57,10 @@ describe('exchange.password', function() { if (client.id !== 'c223') { return done(new Error('incorrect client argument')); } if (username !== 'bob') { return done(new Error('incorrect username argument')); } if (passwd !== 'shh') { return done(new Error('incorrect passwd argument')); } - + return done(null, 's3cr1t', 'getANotehr') } - + chai.connect.use(password(issue)) .req(function(req) { req.user = { id: 'c223', name: 'Example' }; @@ -72,18 +72,18 @@ describe('exchange.password', function() { }) .dispatch(); }); - + it('should respond with headers', function() { expect(response.getHeader('Content-Type')).to.equal('application/json'); expect(response.getHeader('Cache-Control')).to.equal('no-store'); expect(response.getHeader('Pragma')).to.equal('no-cache'); }); - + it('should respond with body', function() { expect(response.body).to.equal('{"access_token":"s3cr1t","refresh_token":"getANotehr","token_type":"Bearer"}'); }); }); - + describe('issuing an access token and params', function() { var response, err; @@ -92,10 +92,10 @@ describe('exchange.password', function() { if (client.id !== 'c523') { return done(new Error('incorrect client argument')); } if (username !== 'bob') { return done(new Error('incorrect username argument')); } if (passwd !== 'shh') { return done(new Error('incorrect passwd argument')); } - + return done(null, 's3cr1t', { 'expires_in': 3600 }) } - + chai.connect.use(password(issue)) .req(function(req) { req.user = { id: 'c523', name: 'Example' }; @@ -107,18 +107,18 @@ describe('exchange.password', function() { }) .dispatch(); }); - + it('should respond with headers', function() { expect(response.getHeader('Content-Type')).to.equal('application/json'); expect(response.getHeader('Cache-Control')).to.equal('no-store'); expect(response.getHeader('Pragma')).to.equal('no-cache'); }); - + it('should respond with body', function() { expect(response.body).to.equal('{"access_token":"s3cr1t","expires_in":3600,"token_type":"Bearer"}'); }); }); - + describe('issuing an access token, null refresh token, and params', function() { var response, err; @@ -127,10 +127,10 @@ describe('exchange.password', function() { if (client.id !== 'c323') { return done(new Error('incorrect client argument')); } if (username !== 'bob') { return done(new Error('incorrect username argument')); } if (passwd !== 'shh') { return done(new Error('incorrect passwd argument')); } - + return done(null, 's3cr1t', null, { 'expires_in': 3600 }) } - + chai.connect.use(password(issue)) .req(function(req) { req.user = { id: 'c323', name: 'Example' }; @@ -142,18 +142,18 @@ describe('exchange.password', function() { }) .dispatch(); }); - + it('should respond with headers', function() { expect(response.getHeader('Content-Type')).to.equal('application/json'); expect(response.getHeader('Cache-Control')).to.equal('no-store'); expect(response.getHeader('Pragma')).to.equal('no-cache'); }); - + it('should respond with body', function() { expect(response.body).to.equal('{"access_token":"s3cr1t","expires_in":3600,"token_type":"Bearer"}'); }); }); - + describe('issuing an access token, refresh token, and params with token_type', function() { var response, err; @@ -162,10 +162,10 @@ describe('exchange.password', function() { if (client.id !== 'c423') { return done(new Error('incorrect client argument')); } if (username !== 'bob') { return done(new Error('incorrect username argument')); } if (passwd !== 'shh') { return done(new Error('incorrect passwd argument')); } - + return done(null, 's3cr1t', 'blahblag', { 'token_type': 'foo', 'expires_in': 3600 }) } - + chai.connect.use(password(issue)) .req(function(req) { req.user = { id: 'c423', name: 'Example' }; @@ -177,18 +177,18 @@ describe('exchange.password', function() { }) .dispatch(); }); - + it('should respond with headers', function() { expect(response.getHeader('Content-Type')).to.equal('application/json'); expect(response.getHeader('Cache-Control')).to.equal('no-store'); expect(response.getHeader('Pragma')).to.equal('no-cache'); }); - + it('should respond with body', function() { expect(response.body).to.equal('{"access_token":"s3cr1t","refresh_token":"blahblag","token_type":"foo","expires_in":3600}'); }); }); - + describe('issuing an access token based on scope', function() { function issue(client, username, passwd, scope, done) { if (client.id !== 'c123') { return done(new Error('incorrect client argument')); } @@ -196,10 +196,10 @@ describe('exchange.password', function() { if (passwd !== 'shh') { return done(new Error('incorrect passwd argument')); } if (scope.length !== 1) { return done(new Error('incorrect scope argument')); } if (scope[0] !== 'read') { return done(new Error('incorrect scope argument')); } - + return done(null, 's3cr1t') } - + var response, err; before(function(done) { @@ -214,18 +214,18 @@ describe('exchange.password', function() { }) .dispatch(); }); - + it('should respond with headers', function() { expect(response.getHeader('Content-Type')).to.equal('application/json'); expect(response.getHeader('Cache-Control')).to.equal('no-store'); expect(response.getHeader('Pragma')).to.equal('no-cache'); }); - + it('should respond with body', function() { expect(response.body).to.equal('{"access_token":"s3cr1t","token_type":"Bearer"}'); }); }); - + describe('issuing an access token based on array of scopes', function() { function issue(client, username, passwd, scope, done) { if (client.id !== 'c123') { return done(new Error('incorrect client argument')); } @@ -234,10 +234,10 @@ describe('exchange.password', function() { if (scope.length !== 2) { return done(new Error('incorrect scope argument')); } if (scope[0] !== 'read') { return done(new Error('incorrect scope argument')); } if (scope[1] !== 'write') { return done(new Error('incorrect scope argument')); } - + return done(null, 's3cr1t') } - + var response, err; before(function(done) { @@ -252,18 +252,18 @@ describe('exchange.password', function() { }) .dispatch(); }); - + it('should respond with headers', function() { expect(response.getHeader('Content-Type')).to.equal('application/json'); expect(response.getHeader('Cache-Control')).to.equal('no-store'); expect(response.getHeader('Pragma')).to.equal('no-cache'); }); - + it('should respond with body', function() { expect(response.body).to.equal('{"access_token":"s3cr1t","token_type":"Bearer"}'); }); }); - + describe('issuing an access token based on scope and body', function() { function issue(client, username, passwd, scope, body, done) { if (client.id !== 'c123') { return done(new Error('incorrect client argument')); } @@ -272,10 +272,10 @@ describe('exchange.password', function() { if (scope.length !== 1) { return done(new Error('incorrect scope argument')); } if (scope[0] !== 'read') { return done(new Error('incorrect scope argument')); } if (body.audience !== 'https://www.example.com/') { return done(new Error('incorrect body argument')); } - + return done(null, 's3cr1t') } - + var response, err; before(function(done) { @@ -290,18 +290,18 @@ describe('exchange.password', function() { }) .dispatch(); }); - + it('should respond with headers', function() { expect(response.getHeader('Content-Type')).to.equal('application/json'); expect(response.getHeader('Cache-Control')).to.equal('no-store'); expect(response.getHeader('Pragma')).to.equal('no-cache'); }); - + it('should respond with body', function() { expect(response.body).to.equal('{"access_token":"s3cr1t","token_type":"Bearer"}'); }); }); - + describe('issuing an access token based on authInfo', function() { function issue(client, username, passwd, scope, body, authInfo, done) { if (client.id !== 'c123') { return done(new Error('incorrect client argument')); } @@ -341,7 +341,7 @@ describe('exchange.password', function() { expect(response.body).to.equal('{"access_token":"s3cr1t","token_type":"Bearer"}'); }); }); - + describe('not issuing an access token', function() { var response, err; @@ -349,7 +349,7 @@ describe('exchange.password', function() { function issue(client, username, passwd, done) { return done(null, false) } - + chai.connect.use(password(issue)) .req(function(req) { req.user = { id: 'cUN', name: 'Example' }; @@ -361,7 +361,7 @@ describe('exchange.password', function() { }) .dispatch(); }); - + it('should error', function() { expect(err).to.be.an.instanceOf(Error); expect(err.constructor.name).to.equal('TokenError'); @@ -370,7 +370,7 @@ describe('exchange.password', function() { expect(err.status).to.equal(403); }); }); - + describe('handling a request without username parameter', function() { var response, err; @@ -378,7 +378,7 @@ describe('exchange.password', function() { function issue(client, username, passwd, done) { return done(null, '.ignore') } - + chai.connect.use(password(issue)) .req(function(req) { req.user = { id: 'c123', name: 'Example' }; @@ -390,7 +390,7 @@ describe('exchange.password', function() { }) .dispatch(); }); - + it('should error', function() { expect(err).to.be.an.instanceOf(Error); expect(err.constructor.name).to.equal('TokenError'); @@ -399,7 +399,7 @@ describe('exchange.password', function() { expect(err.status).to.equal(400); }); }); - + describe('handling a request without password parameter', function() { var response, err; @@ -407,7 +407,7 @@ describe('exchange.password', function() { function issue(client, username, passwd, done) { return done(null, '.ignore') } - + chai.connect.use(password(issue)) .req(function(req) { req.user = { id: 'c123', name: 'Example' }; @@ -419,7 +419,7 @@ describe('exchange.password', function() { }) .dispatch(); }); - + it('should error', function() { expect(err).to.be.an.instanceOf(Error); expect(err.constructor.name).to.equal('TokenError'); @@ -428,7 +428,7 @@ describe('exchange.password', function() { expect(err.status).to.equal(400); }); }); - + describe('encountering an error while issuing an access token', function() { var response, err; @@ -436,7 +436,7 @@ describe('exchange.password', function() { function issue(client, username, passwd, done) { return done(new Error('something is wrong')); } - + chai.connect.use(password(issue)) .req(function(req) { req.user = { id: 'cXXX', name: 'Example' }; @@ -448,13 +448,13 @@ describe('exchange.password', function() { }) .dispatch(); }); - + it('should error', function() { expect(err).to.be.an.instanceOf(Error); expect(err.message).to.equal('something is wrong'); }); }); - + describe('encountering an exception while issuing an access token', function() { var response, err; @@ -462,7 +462,7 @@ describe('exchange.password', function() { function issue(client, username, passwd, done) { throw new Error('something was thrown') } - + chai.connect.use(password(issue)) .req(function(req) { req.user = { id: 'cTHROW', name: 'Example' }; @@ -474,13 +474,13 @@ describe('exchange.password', function() { }) .dispatch(); }); - + it('should error', function() { expect(err).to.be.an.instanceOf(Error); expect(err.message).to.equal('something was thrown'); }); }); - + describe('handling a request without a body', function() { var response, err; @@ -488,7 +488,7 @@ describe('exchange.password', function() { function issue(client, username, passwd, done) { return done(new Error('something is wrong')); } - + chai.connect.use(password(issue)) .req(function(req) { req.user = { id: 'c123', name: 'Example' }; @@ -499,13 +499,42 @@ describe('exchange.password', function() { }) .dispatch(); }); - + it('should error', function() { expect(err).to.be.an.instanceOf(Error); expect(err.message).to.equal('OAuth2orize requires body parsing. Did you forget app.use(express.bodyParser())?'); }); }); - + + describe('handling a request where scope format is not string', function () { + var response, err; + + before(function (done) { + function issue(client, username, passwd, done) { + return done(new Error('something is wrong')); + } + + chai.connect.use(password(issue)) + .req(function (req) { + req.user = { id: 'c123', name: 'Example' }; + req.body = { username: 'bob', password: 'shh', scope: ['read', 'write'] }; + }) + .next(function (e) { + err = e; + done(); + }) + .dispatch(); + }); + + it('should error', function () { + expect(err).to.be.an.instanceOf(Error); + expect(err.name).to.equal('TokenError'); + expect(err.message).to.equal('Invalid parameter: scope must be a string'); + expect(err.code).to.equal('invalid_scope'); + expect(err.status).to.equal(400); + }); + }); + describe('with scope separator option', function() { describe('issuing an access token based on array of scopes', function() { function issue(client, username, passwd, scope, done) { @@ -515,10 +544,10 @@ describe('exchange.password', function() { if (scope.length !== 2) { return done(new Error('incorrect scope argument')); } if (scope[0] !== 'read') { return done(new Error('incorrect scope argument')); } if (scope[1] !== 'write') { return done(new Error('incorrect scope argument')); } - + return done(null, 's3cr1t') } - + var response, err; before(function(done) { @@ -533,19 +562,19 @@ describe('exchange.password', function() { }) .dispatch(); }); - + it('should respond with headers', function() { expect(response.getHeader('Content-Type')).to.equal('application/json'); expect(response.getHeader('Cache-Control')).to.equal('no-store'); expect(response.getHeader('Pragma')).to.equal('no-cache'); }); - + it('should respond with body', function() { expect(response.body).to.equal('{"access_token":"s3cr1t","token_type":"Bearer"}'); }); }); }); - + describe('with multiple scope separator option', function() { function issue(client, username, passwd, scope, done) { if (client.id !== 'c123') { return done(new Error('incorrect client argument')); } @@ -554,10 +583,10 @@ describe('exchange.password', function() { if (scope.length !== 2) { return done(new Error('incorrect scope argument')); } if (scope[0] !== 'read') { return done(new Error('incorrect scope argument')); } if (scope[1] !== 'write') { return done(new Error('incorrect scope argument')); } - + return done(null, 's3cr1t') } - + describe('issuing an access token based on scope separated by space', function() { var response, err; @@ -573,18 +602,18 @@ describe('exchange.password', function() { }) .dispatch(); }); - + it('should respond with headers', function() { expect(response.getHeader('Content-Type')).to.equal('application/json'); expect(response.getHeader('Cache-Control')).to.equal('no-store'); expect(response.getHeader('Pragma')).to.equal('no-cache'); }); - + it('should respond with body', function() { expect(response.body).to.equal('{"access_token":"s3cr1t","token_type":"Bearer"}'); }); }); - + describe('issuing an access token based on scope separated by comma', function() { var response, err; @@ -600,19 +629,19 @@ describe('exchange.password', function() { }) .dispatch(); }); - + it('should respond with headers', function() { expect(response.getHeader('Content-Type')).to.equal('application/json'); expect(response.getHeader('Cache-Control')).to.equal('no-store'); expect(response.getHeader('Pragma')).to.equal('no-cache'); }); - + it('should respond with body', function() { expect(response.body).to.equal('{"access_token":"s3cr1t","token_type":"Bearer"}'); }); }); }); - + describe('with user property option issuing an access token', function() { var response, err; @@ -621,10 +650,10 @@ describe('exchange.password', function() { if (client.id !== 'c123') { return done(new Error('incorrect client argument')); } if (username !== 'bob') { return done(new Error('incorrect username argument')); } if (passwd !== 'shh') { return done(new Error('incorrect passwd argument')); } - + return done(null, 's3cr1t') } - + chai.connect.use(password({ userProperty: 'client' }, issue)) .req(function(req) { req.client = { id: 'c123', name: 'Example' }; @@ -636,13 +665,13 @@ describe('exchange.password', function() { }) .dispatch(); }); - + it('should respond with headers', function() { expect(response.getHeader('Content-Type')).to.equal('application/json'); expect(response.getHeader('Cache-Control')).to.equal('no-store'); expect(response.getHeader('Pragma')).to.equal('no-cache'); }); - + it('should respond with body', function() { expect(response.body).to.equal('{"access_token":"s3cr1t","token_type":"Bearer"}'); }); diff --git a/test/exchange/refreshToken.test.js b/test/exchange/refreshToken.test.js index 52f27ad1..306d860c 100644 --- a/test/exchange/refreshToken.test.js +++ b/test/exchange/refreshToken.test.js @@ -3,17 +3,17 @@ var chai = require('chai') describe('exchange.refreshToken', function() { - + it('should be named refresh_token', function() { expect(refreshToken(function(){}).name).to.equal('refresh_token'); }); - + it('should throw if constructed without a issue callback', function() { expect(function() { refreshToken(); }).to.throw(TypeError, 'oauth2orize.refreshToken exchange requires an issue callback'); }); - + describe('issuing an access token', function() { var response, err; @@ -21,10 +21,10 @@ describe('exchange.refreshToken', function() { function issue(client, refreshToken, done) { if (client.id !== 'c123') { return done(new Error('incorrect client argument')); } if (refreshToken !== 'refreshing') { return done(new Error('incorrect refreshToken argument')); } - + return done(null, 's3cr1t') } - + chai.connect.use(refreshToken(issue)) .req(function(req) { req.user = { id: 'c123', name: 'Example' }; @@ -36,18 +36,18 @@ describe('exchange.refreshToken', function() { }) .dispatch(); }); - + it('should respond with headers', function() { expect(response.getHeader('Content-Type')).to.equal('application/json'); expect(response.getHeader('Cache-Control')).to.equal('no-store'); expect(response.getHeader('Pragma')).to.equal('no-cache'); }); - + it('should respond with body', function() { expect(response.body).to.equal('{"access_token":"s3cr1t","token_type":"Bearer"}'); }); }); - + describe('issuing an access token and refresh token', function() { var response, err; @@ -55,10 +55,10 @@ describe('exchange.refreshToken', function() { function issue(client, refreshToken, done) { if (client.id !== 'c223') { return done(new Error('incorrect client argument')); } if (refreshToken !== 'refreshing') { return done(new Error('incorrect refreshToken argument')); } - + return done(null, 's3cr1t', 'getANotehr') } - + chai.connect.use(refreshToken(issue)) .req(function(req) { req.user = { id: 'c223', name: 'Example' }; @@ -70,18 +70,18 @@ describe('exchange.refreshToken', function() { }) .dispatch(); }); - + it('should respond with headers', function() { expect(response.getHeader('Content-Type')).to.equal('application/json'); expect(response.getHeader('Cache-Control')).to.equal('no-store'); expect(response.getHeader('Pragma')).to.equal('no-cache'); }); - + it('should respond with body', function() { expect(response.body).to.equal('{"access_token":"s3cr1t","refresh_token":"getANotehr","token_type":"Bearer"}'); }); }); - + describe('issuing an access token and params', function() { var response, err; @@ -89,10 +89,10 @@ describe('exchange.refreshToken', function() { function issue(client, refreshToken, done) { if (client.id !== 'c523') { return done(new Error('incorrect client argument')); } if (refreshToken !== 'refreshing') { return done(new Error('incorrect refreshToken argument')); } - + return done(null, 's3cr1t', { 'expires_in': 3600 }) } - + chai.connect.use(refreshToken(issue)) .req(function(req) { req.user = { id: 'c523', name: 'Example' }; @@ -104,18 +104,18 @@ describe('exchange.refreshToken', function() { }) .dispatch(); }); - + it('should respond with headers', function() { expect(response.getHeader('Content-Type')).to.equal('application/json'); expect(response.getHeader('Cache-Control')).to.equal('no-store'); expect(response.getHeader('Pragma')).to.equal('no-cache'); }); - + it('should respond with body', function() { expect(response.body).to.equal('{"access_token":"s3cr1t","expires_in":3600,"token_type":"Bearer"}'); }); }); - + describe('issuing an access token, null refresh token, and params', function() { var response, err; @@ -123,10 +123,10 @@ describe('exchange.refreshToken', function() { function issue(client, refreshToken, done) { if (client.id !== 'c323') { return done(new Error('incorrect client argument')); } if (refreshToken !== 'refreshing') { return done(new Error('incorrect refreshToken argument')); } - + return done(null, 's3cr1t', null, { 'expires_in': 3600 }) } - + chai.connect.use(refreshToken(issue)) .req(function(req) { req.user = { id: 'c323', name: 'Example' }; @@ -138,18 +138,18 @@ describe('exchange.refreshToken', function() { }) .dispatch(); }); - + it('should respond with headers', function() { expect(response.getHeader('Content-Type')).to.equal('application/json'); expect(response.getHeader('Cache-Control')).to.equal('no-store'); expect(response.getHeader('Pragma')).to.equal('no-cache'); }); - + it('should respond with body', function() { expect(response.body).to.equal('{"access_token":"s3cr1t","expires_in":3600,"token_type":"Bearer"}'); }); }); - + describe('issuing an access token, refresh token, and params with token_type', function() { var response, err; @@ -157,10 +157,10 @@ describe('exchange.refreshToken', function() { function issue(client, refreshToken, done) { if (client.id !== 'c423') { return done(new Error('incorrect client argument')); } if (refreshToken !== 'refreshing') { return done(new Error('incorrect refreshToken argument')); } - + return done(null, 's3cr1t', 'blahblag', { 'token_type': 'foo', 'expires_in': 3600 }) } - + chai.connect.use(refreshToken(issue)) .req(function(req) { req.user = { id: 'c423', name: 'Example' }; @@ -172,28 +172,28 @@ describe('exchange.refreshToken', function() { }) .dispatch(); }); - + it('should respond with headers', function() { expect(response.getHeader('Content-Type')).to.equal('application/json'); expect(response.getHeader('Cache-Control')).to.equal('no-store'); expect(response.getHeader('Pragma')).to.equal('no-cache'); }); - + it('should respond with body', function() { expect(response.body).to.equal('{"access_token":"s3cr1t","refresh_token":"blahblag","token_type":"foo","expires_in":3600}'); }); }); - + describe('issuing an access token based on scope', function() { function issue(client, refreshToken, scope, done) { if (client.id !== 'c123') { return done(new Error('incorrect client argument')); } if (refreshToken !== 'refreshing') { return done(new Error('incorrect refreshToken argument')); } if (scope.length !== 1) { return done(new Error('incorrect scope argument')); } if (scope[0] !== 'read') { return done(new Error('incorrect scope argument')); } - + return done(null, 's3cr1t') } - + var response, err; before(function(done) { @@ -208,18 +208,18 @@ describe('exchange.refreshToken', function() { }) .dispatch(); }); - + it('should respond with headers', function() { expect(response.getHeader('Content-Type')).to.equal('application/json'); expect(response.getHeader('Cache-Control')).to.equal('no-store'); expect(response.getHeader('Pragma')).to.equal('no-cache'); }); - + it('should respond with body', function() { expect(response.body).to.equal('{"access_token":"s3cr1t","token_type":"Bearer"}'); }); }); - + describe('issuing an access token based on array of scopes', function() { function issue(client, refreshToken, scope, done) { if (client.id !== 'c123') { return done(new Error('incorrect client argument')); } @@ -227,10 +227,10 @@ describe('exchange.refreshToken', function() { if (scope.length !== 2) { return done(new Error('incorrect scope argument')); } if (scope[0] !== 'read') { return done(new Error('incorrect scope argument')); } if (scope[1] !== 'write') { return done(new Error('incorrect scope argument')); } - + return done(null, 's3cr1t') } - + var response, err; before(function(done) { @@ -245,18 +245,18 @@ describe('exchange.refreshToken', function() { }) .dispatch(); }); - + it('should respond with headers', function() { expect(response.getHeader('Content-Type')).to.equal('application/json'); expect(response.getHeader('Cache-Control')).to.equal('no-store'); expect(response.getHeader('Pragma')).to.equal('no-cache'); }); - + it('should respond with body', function() { expect(response.body).to.equal('{"access_token":"s3cr1t","token_type":"Bearer"}'); }); }); - + describe('issuing an access token based on scope and body', function() { function issue(client, refreshToken, scope, body, done) { if (client.id !== 'c123') { return done(new Error('incorrect client argument')); } @@ -264,10 +264,10 @@ describe('exchange.refreshToken', function() { if (scope.length !== 1) { return done(new Error('incorrect scope argument')); } if (scope[0] !== 'read') { return done(new Error('incorrect scope argument')); } if (body.audience !== 'https://www.example.com/') { return done(new Error('incorrect body argument')); } - + return done(null, 's3cr1t') } - + var response, err; before(function(done) { @@ -282,18 +282,18 @@ describe('exchange.refreshToken', function() { }) .dispatch(); }); - + it('should respond with headers', function() { expect(response.getHeader('Content-Type')).to.equal('application/json'); expect(response.getHeader('Cache-Control')).to.equal('no-store'); expect(response.getHeader('Pragma')).to.equal('no-cache'); }); - + it('should respond with body', function() { expect(response.body).to.equal('{"access_token":"s3cr1t","token_type":"Bearer"}'); }); }); - + describe('issuing an access token based on authInfo', function() { function issue(client, refreshToken, scope, body, authInfo, done) { if (client.id !== 'c123') { return done(new Error('incorrect client argument')); } @@ -302,10 +302,10 @@ describe('exchange.refreshToken', function() { if (scope[0] !== 'read') { return done(new Error('incorrect scope argument')); } if (body.audience !== 'https://www.example.com/') { return done(new Error('incorrect body argument')); } if (authInfo.ip !== '127.0.0.1') { return done(new Error('incorrect authInfo argument')); } - + return done(null, 's3cr1t') } - + var response, err; before(function(done) { @@ -321,18 +321,18 @@ describe('exchange.refreshToken', function() { }) .dispatch(); }); - + it('should respond with headers', function() { expect(response.getHeader('Content-Type')).to.equal('application/json'); expect(response.getHeader('Cache-Control')).to.equal('no-store'); expect(response.getHeader('Pragma')).to.equal('no-cache'); }); - + it('should respond with body', function() { expect(response.body).to.equal('{"access_token":"s3cr1t","token_type":"Bearer"}'); }); }); - + describe('not issuing an access token', function() { var response, err; @@ -340,7 +340,7 @@ describe('exchange.refreshToken', function() { function issue(client, refreshToken, done) { return done(null, false) } - + chai.connect.use(refreshToken(issue)) .req(function(req) { req.user = { id: 'cUN', name: 'Example' }; @@ -352,7 +352,7 @@ describe('exchange.refreshToken', function() { }) .dispatch(); }); - + it('should error', function() { expect(err).to.be.an.instanceOf(Error); expect(err.constructor.name).to.equal('TokenError'); @@ -361,7 +361,7 @@ describe('exchange.refreshToken', function() { expect(err.status).to.equal(403); }); }); - + describe('handling a request without refresh token parameter', function() { var response, err; @@ -369,7 +369,7 @@ describe('exchange.refreshToken', function() { function issue(client, refreshToken, done) { return done(null, '.ignore') } - + chai.connect.use(refreshToken(issue)) .req(function(req) { req.user = { id: 'c123', name: 'Example' }; @@ -381,7 +381,7 @@ describe('exchange.refreshToken', function() { }) .dispatch(); }); - + it('should error', function() { expect(err).to.be.an.instanceOf(Error); expect(err.constructor.name).to.equal('TokenError'); @@ -390,7 +390,7 @@ describe('exchange.refreshToken', function() { expect(err.status).to.equal(400); }); }); - + describe('encountering an error while issuing an access token', function() { var response, err; @@ -398,7 +398,7 @@ describe('exchange.refreshToken', function() { function issue(client, refreshToken, done) { return done(new Error('something is wrong')); } - + chai.connect.use(refreshToken(issue)) .req(function(req) { req.user = { id: 'cXXX', name: 'Example' }; @@ -410,13 +410,13 @@ describe('exchange.refreshToken', function() { }) .dispatch(); }); - + it('should error', function() { expect(err).to.be.an.instanceOf(Error); expect(err.message).to.equal('something is wrong'); }); }); - + describe('encountering an exception while issuing an access token', function() { var response, err; @@ -424,7 +424,7 @@ describe('exchange.refreshToken', function() { function issue(client, refreshToken, done) { throw new Error('something was thrown') } - + chai.connect.use(refreshToken(issue)) .req(function(req) { req.user = { id: 'cTHROW', name: 'Example' }; @@ -436,13 +436,13 @@ describe('exchange.refreshToken', function() { }) .dispatch(); }); - + it('should error', function() { expect(err).to.be.an.instanceOf(Error); expect(err.message).to.equal('something was thrown'); }); }); - + describe('handling a request without a body', function() { var response, err; @@ -450,7 +450,7 @@ describe('exchange.refreshToken', function() { function issue(client, refreshToken, done) { return done(null, '.ignore') } - + chai.connect.use(refreshToken(issue)) .req(function(req) { req.user = { id: 'c123', name: 'Example' }; @@ -461,13 +461,43 @@ describe('exchange.refreshToken', function() { }) .dispatch(); }); - + it('should error', function() { expect(err).to.be.an.instanceOf(Error); expect(err.message).to.equal('OAuth2orize requires body parsing. Did you forget app.use(express.bodyParser())?'); }); }); - + + describe('handling a request where scope format is not string', function () { + var response, err; + + before(function (done) { + function issue(client, refreshToken, done) { + return done(null, '.ignore') + } + + chai.connect.use(refreshToken(issue)) + .req(function (req) { + req.user = { id: 'c123', name: 'Example' }; + req.body = { refresh_token: 'refreshing', scope: ['read', 'write'] }; + }) + .next(function (e) { + err = e; + done(); + }) + .dispatch(); + }); + + it('should error', function () { + expect(err).to.be.an.instanceOf(Error); + expect(err.name).to.equal('TokenError'); + expect(err.message).to.equal('Invalid parameter: scope must be a string'); + expect(err.code).to.equal('invalid_scope'); + expect(err.status).to.equal(400); + }); + }); + + describe('with scope separator option', function() { describe('issuing an access token based on array of scopes', function() { function issue(client, refreshToken, scope, done) { @@ -476,10 +506,10 @@ describe('exchange.refreshToken', function() { if (scope.length !== 2) { return done(new Error('incorrect scope argument')); } if (scope[0] !== 'read') { return done(new Error('incorrect scope argument')); } if (scope[1] !== 'write') { return done(new Error('incorrect scope argument')); } - + return done(null, 's3cr1t') } - + var response, err; before(function(done) { @@ -494,19 +524,19 @@ describe('exchange.refreshToken', function() { }) .dispatch(); }); - + it('should respond with headers', function() { expect(response.getHeader('Content-Type')).to.equal('application/json'); expect(response.getHeader('Cache-Control')).to.equal('no-store'); expect(response.getHeader('Pragma')).to.equal('no-cache'); }); - + it('should respond with body', function() { expect(response.body).to.equal('{"access_token":"s3cr1t","token_type":"Bearer"}'); }); }); }); - + describe('with multiple scope separator option', function() { function issue(client, refreshToken, scope, done) { if (client.id !== 'c123') { return done(new Error('incorrect client argument')); } @@ -514,10 +544,10 @@ describe('exchange.refreshToken', function() { if (scope.length !== 2) { return done(new Error('incorrect scope argument')); } if (scope[0] !== 'read') { return done(new Error('incorrect scope argument')); } if (scope[1] !== 'write') { return done(new Error('incorrect scope argument')); } - + return done(null, 's3cr1t') } - + describe('issuing an access token based on scope separated by space', function() { var response, err; @@ -533,18 +563,18 @@ describe('exchange.refreshToken', function() { }) .dispatch(); }); - + it('should respond with headers', function() { expect(response.getHeader('Content-Type')).to.equal('application/json'); expect(response.getHeader('Cache-Control')).to.equal('no-store'); expect(response.getHeader('Pragma')).to.equal('no-cache'); }); - + it('should respond with body', function() { expect(response.body).to.equal('{"access_token":"s3cr1t","token_type":"Bearer"}'); }); }); - + describe('issuing an access token based on scope separated by comma', function() { var response, err; @@ -560,19 +590,19 @@ describe('exchange.refreshToken', function() { }) .dispatch(); }); - + it('should respond with headers', function() { expect(response.getHeader('Content-Type')).to.equal('application/json'); expect(response.getHeader('Cache-Control')).to.equal('no-store'); expect(response.getHeader('Pragma')).to.equal('no-cache'); }); - + it('should respond with body', function() { expect(response.body).to.equal('{"access_token":"s3cr1t","token_type":"Bearer"}'); }); }); }); - + describe('with user property option issuing an access token', function() { var response, err; @@ -580,10 +610,10 @@ describe('exchange.refreshToken', function() { function issue(client, refreshToken, done) { if (client.id !== 'c123') { return done(new Error('incorrect client argument')); } if (refreshToken !== 'refreshing') { return done(new Error('incorrect refreshToken argument')); } - + return done(null, 's3cr1t') } - + chai.connect.use(refreshToken({ userProperty: 'client' }, issue)) .req(function(req) { req.client = { id: 'c123', name: 'Example' }; @@ -595,16 +625,16 @@ describe('exchange.refreshToken', function() { }) .dispatch(); }); - + it('should respond with headers', function() { expect(response.getHeader('Content-Type')).to.equal('application/json'); expect(response.getHeader('Cache-Control')).to.equal('no-store'); expect(response.getHeader('Pragma')).to.equal('no-cache'); }); - + it('should respond with body', function() { expect(response.body).to.equal('{"access_token":"s3cr1t","token_type":"Bearer"}'); }); }); - + });