diff --git a/lib/index.js b/lib/index.js index f9c5ae63..7730f9b1 100755 --- a/lib/index.js +++ b/lib/index.js @@ -55,6 +55,7 @@ internals.schema = Joi.object({ ttl: Joi.number(), domain: Joi.string().allow(null), providerParams: Joi.object(), + runtimeQuerySchema: Joi.object().default({}), scope: Joi.array().items(Joi.string()).when('provider.protocol', { is: 'oauth2', otherwise: Joi.forbidden() }), name: Joi.string().required(), config: Joi.object(), diff --git a/lib/oauth.js b/lib/oauth.js index a715838a..78f5fe35 100755 --- a/lib/oauth.js +++ b/lib/oauth.js @@ -7,6 +7,7 @@ var Cryptiles = require('cryptiles'); var Crypto = require('crypto'); var Hoek = require('hoek'); var Wreck = require('wreck'); +var Joi = require('joi'); // Declare internals @@ -147,27 +148,34 @@ exports.v2 = function (settings) { if (!request.query.code) { var nonce = Cryptiles.randomString(22); query = Hoek.clone(settings.providerParams) || {}; - protocol = request.connection.info.protocol; - if (settings.forceHttps) { - protocol = 'https'; - } - query.client_id = settings.clientId; - query.response_type = 'code'; - query.redirect_uri = internals.location(request, protocol, settings.location); - query.state = nonce; - var scope = settings.scope || settings.provider.scope; - if (scope) { - query.scope = scope.join(settings.provider.scopeSeparator || ' '); - } + // Validate runtime query params + return Joi.validate(request.query, settings.runtimeQuerySchema, function (err, value) { - state = { - nonce: nonce, - query: request.query - }; + if (err) { + return reply(Boom.internal('Invalid runtime query parameters', err)); + } + + Hoek.merge(query, request.query); + protocol = settings.forceHttps ? 'https' : request.connection.info.protocol; + query.client_id = settings.clientId; + query.response_type = 'code'; + query.redirect_uri = internals.location(request, protocol, settings.location); + query.state = nonce; - reply.state(cookie, state); - return reply.redirect(settings.provider.auth + '?' + internals.queryString(query)); + var scope = settings.scope || settings.provider.scope; + if (scope) { + query.scope = scope.join(settings.provider.scopeSeparator || ' '); + } + + state = { + nonce: nonce, + query: request.query + }; + + reply.state(cookie, state); + return reply.redirect(settings.provider.auth + '?' + internals.queryString(query)); + }); } // Authorization callback @@ -459,9 +467,9 @@ internals.Client.prototype.signature = function (method, baseUri, params, oauth, normalized.sort(function (a, b) { return (a[0] < b[0] ? -1 - : (a[0] > b[0] ? 1 - : (a[1] < b[1] ? -1 - : (a[1] > b[1] ? 1 : 0)))); + : (a[0] > b[0] ? 1 + : (a[1] < b[1] ? -1 + : (a[1] > b[1] ? 1 : 0)))); }); var normalizedParam = ''; @@ -472,8 +480,8 @@ internals.Client.prototype.signature = function (method, baseUri, params, oauth, // String Construction (3.4.1.1) var baseString = internals.encode(method.toUpperCase()) + '&' + - internals.encode(baseUri) + '&' + - internals.encode(normalizedParam); + internals.encode(baseUri) + '&' + + internals.encode(normalizedParam); // HMAC-SHA1 (3.4.2) diff --git a/lib/providers/vk.js b/lib/providers/vk.js index 10ce4160..7189735f 100755 --- a/lib/providers/vk.js +++ b/lib/providers/vk.js @@ -16,8 +16,8 @@ exports = module.exports = function (options) { profile: function (credentials, params, get, callback) { var query = { - uids: params.user_id, - access_token: params.access_token + uids: params.user_id, + access_token: params.access_token }; get('https://api.vk.com/method/users.get', query, function (data) { diff --git a/test/oauth.js b/test/oauth.js index b0e08022..9545829e 100755 --- a/test/oauth.js +++ b/test/oauth.js @@ -6,6 +6,7 @@ var Code = require('code'); var Hapi = require('hapi'); var Hoek = require('hoek'); var Lab = require('lab'); +var Joi = require('joi'); var Mock = require('./mock'); var OAuth = require('../lib/oauth'); @@ -847,6 +848,50 @@ describe('Bell', function () { }); }); }); + + it('authenticates an endpoint with runtime query parameters', function (done) { + + var mock = new Mock.V2(); + mock.start(function (provider) { + + var server = new Hapi.Server(); + server.connection({ host: 'localhost', port: 80 }); + server.register(Bell, function (err) { + + expect(err).to.not.exist(); + + server.auth.strategy('custom', 'bell', { + password: 'password', + isSecure: false, + clientId: 'test', + clientSecret: 'secret', + provider: provider, + providerParams: { special: true }, + runtimeQuerySchema: { + runtime: Joi.number() + } + }); + + server.route({ + method: '*', + path: '/login', + config: { + auth: 'custom', + handler: function (request, reply) { + + reply(request.auth.credentials); + } + } + }); + + server.inject('/login?runtime=5', function (res) { + + expect(res.headers.location).to.contain(mock.uri + '/auth?special=true&runtime=5&client_id=test&response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A80%2Flogin&state='); + mock.stop(done); + }); + }); + }); + }); }); describe('#v2', function () { @@ -1360,7 +1405,6 @@ describe('Bell', function () { expect(res.statusCode).to.equal(500); done(); - }); }); }); @@ -1479,6 +1523,50 @@ describe('Bell', function () { }); }); }); + + it('errors on invalid runtime query parameters validation', function (done) { + + var mock = new Mock.V2(); + mock.start(function (provider) { + + var server = new Hapi.Server(); + server.connection({ host: 'localhost', port: 80 }); + server.register(Bell, function (err) { + + expect(err).to.not.exist(); + + server.auth.strategy('custom', 'bell', { + password: 'password', + isSecure: false, + clientId: 'test', + clientSecret: 'secret', + provider: provider, + providerParams: { special: true }, + runtimeQuerySchema: { + runtime: Joi.number() + } + }); + + server.route({ + method: '*', + path: '/login', + config: { + auth: 'custom', + handler: function (request, reply) { + + reply(request.auth.credentials); + } + } + }); + + server.inject('/login?notallowed=b', function (res) { + + expect(res.statusCode).to.equal(500); + mock.stop(done); + }); + }); + }); + }); }); describe('Client', function () { diff --git a/test/providers.js b/test/providers.js index 8bfadd4b..dc32af68 100755 --- a/test/providers.js +++ b/test/providers.js @@ -364,8 +364,8 @@ describe('Bell', function () { }, emails: [ { - 'type': 'account', - 'value': 'steve@example.com' + 'type': 'account', + 'value': 'steve@example.com' } ], raw: profile @@ -415,8 +415,8 @@ describe('Bell', function () { }, emails: [ { - 'type': 'account', - 'value': 'steve@example.com' + 'type': 'account', + 'value': 'steve@example.com' } ], raw: profile