From 8304a70ab44e894c9de3f5497d20072c7682c231 Mon Sep 17 00:00:00 2001 From: Loay Date: Tue, 26 Jul 2016 11:37:45 -0400 Subject: [PATCH] Add globalization --- .gitignore | 4 + intl/en/messages.json | 67 +++++++++++++ lib/exchange/authorizationCode.js | 24 ++--- lib/exchange/clientCredentials.js | 22 +++-- lib/exchange/jwt.js | 10 +- lib/exchange/password.js | 26 ++--- lib/exchange/refreshToken.js | 28 +++--- lib/grant/code.js | 44 +++++---- lib/grant/token.js | 48 ++++----- lib/index.js | 8 +- lib/mac-token.js | 4 - lib/middleware/authorization.js | 40 ++++---- lib/middleware/decision.js | 32 +++--- lib/middleware/revoke.js | 20 ++-- lib/middleware/token.js | 14 +-- lib/middleware/transactionLoader.js | 30 +++--- lib/models/index.js | 1 - lib/oauth2-helper.js | 21 ++-- lib/oauth2-loopback.js | 31 +++--- lib/resource-server.js | 12 +-- lib/scope.js | 7 +- lib/server.js | 54 +++++----- lib/strategy/jwt-bearer/strategy.js | 8 +- lib/strategy/mac/strategy.js | 10 +- package.json | 1 + test/middleware/authorization.test.js | 136 +++++++++++++------------- 26 files changed, 403 insertions(+), 299 deletions(-) create mode 100644 intl/en/messages.json diff --git a/.gitignore b/.gitignore index 907aa01..ba12771 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,7 @@ npm-debug.log # Mac OS X .DS_Store + +!intl/ +intl/* +!intl/en/ diff --git a/intl/en/messages.json b/intl/en/messages.json new file mode 100644 index 0000000..4d65291 --- /dev/null +++ b/intl/en/messages.json @@ -0,0 +1,67 @@ +{ + "102a3213ff01fc1b03faa5cbe720e6c0": "Invalid authorization code", + "12df3715215d39fdb1c5c19775091f31": "{{OAuth2orize}} requires body parsing. Did you forget {{app.use(express.bodyParser())}}?", + "37086a106da98d5322d134d7905d1817": "Missing required parameter: {{code}}", + "cdee717bf439af4c84eabb54b82c13af": "{{oauth2orize.authorizationCode}} exchange requires an issue callback", + "aaee2c79ab90ccc63b9d46795e19620a": "{{oauth2orize.clientCredentials}} exchange requires an issue callback", + "ae02475e560f64afdcf6d02bc80dcb0e": "Invalid client credentials", + "06f6133efc10bf1f9a2c6b5161c66fba": "Request body not parsed. Use {{bodyParser}} middleware.", + "76d075ea0325d32667fad67c446e469b": "invalid {{JWT}}", + "a198c4335c42739230d7d8afacdc109d": "{{OAuth 2.0 jwtBearer}} exchange middleware requires an {{issue}} function.", + "fcdd5cf7956698312e899ddb708abbaf": "missing assertion parameter", + "47db77dd8ab3aa3c3ec1e6c955c0cc01": "Missing required parameter: {{username}}", + "cbd12265f4ab78b30a0977e37aefcf37": "Missing required parameter: {{password}}", + "d837f7c0309bf4816cbbb22b57473733": "{{oauth2orize.password}} exchange requires an {{issue}} callback", + "ff7883d90a5fe850e026885759165406": "Invalid resource owner credentials", + "6d3b5c2b2c7c20cd8907a0b090f0e89c": "Invalid refresh token", + "740f3fb2bd1358d65943e735224d23e5": "Missing required parameter: {{refresh_token}}", + "ec9ddf242c3da3b5cf44def6398706ec": "{{oauth2orize.refreshToken}} exchange requires an {{issue}} callback", + "3bffb3bbef4312d6bcbabd3240e79ef2": "{{oauth2orize.code}} grant requires an {{issue}} callback", + "6e0122e493bd949f408c1421fc757b95": "Unable to issue redirect for {{OAuth 2.0}} transaction", + "d36b1a9f8719a56a091ed6c89d3cd1fa": "Request denied by authorization server", + "f50708df489d7811db692c0b601483f8": "Missing required parameter: {{client_id}}", + "bcb1c003530e6fb715ad605ea7fe83d9": "{{oauth2orize.token}} grant requires an {{issue}} callback", + "83459aaa8e2d81c7fad2b899ad0cbf57": "The {{OAuth2}} component was not configured for this application.", + "083123c0a745a514ce06fa527c151a46": "{{oauth2orize.authorization}} middleware requires a {{server}} argument", + "092f3dd9c9bde61eed97f67459b46a7e": "Unsupported response type: {0}", + "24ff616d05fe6b8dca8859d9dafa729e": "Invalid client", + "2caa676b18561101c92e69d70820b1bf": "{{OAuth2orize}} requires {{session}} support. Did you forget {{app.use(express.session(...))}}?", + "43a5b5bf0c7dd0bf5fec2601fe347245": "Invalid request: {{redirect_uri}} is missing", + "8782860f8d60f2bdb6f67d623dd7e51f": "{{oauth2orize.authorization}} middleware requires a {{validate}} function", + "ec30fea412580b611d7a274ee1163e5c": "Missing required parameter: {{response_type}}", + "14b06036ad206e2b038840c24e677b4d": "{{OAuth2orize}} requires transaction support. Did you forget {{oauth2orize.transactionLoader(...)}}?", + "7175ea69be895be338ca9f9168268dab": "{{oauth2orize.decision}} middleware requires a {{server}} argument", + "a7b39a5a13fdba4772a83e07cb864156": "Unable to load {{OAuth 2.0}} transactions from session", + "54d8df1f46d3358dd33ba274a537b519": "Unsupported token type: {0}", + "a23d7ec09a60c8bbd119d8287d8b5805": "{{oauth2orize.revoke}} middleware requires a {{revokeToken}} function", + "d3dd8b2220bba70a6e7e44af24a4157c": "Missing required parameter: {{token}}", + "f06abf2e08aba65a62f4486bc2418413": "{{oauth2orize.revoke}} middleware requires a {{server}} argument", + "4428b355c8ba3e248b8fc0857e2181a0": "Unsupported grant type: {0}", + "bac74f7415259e303903cfc8a7bcc4df": "{{oauth2orize.token}} middleware requires a {{server}} argument", + "5bc4d117f87ba20efb8cdd4c988ed46d": "{{oauth2orize.transactionLoader}} middleware requires a {{server}} argument", + "8000aa2c64d9c95c406053764fc33f64": "Unauthorized client", + "b1af8f34c3d25bd59189205046665bfd": "Missing required parameter: {0}", + "f24b75ecdc45edb84b0c9fec3b6f1e5b": "Unable to load {{OAuth 2.0}} transaction: {0}", + "452b7bd4aed1b69b7c20fb701377c4eb": "Unauthorized {{redirectURI}}: {0}", + "e173a3cecca19bcbf528408adc001750": "Unauthorized response type: {0}", + "eb30f051fcf0e7338bf4d8489f583378": "Unauthorized {{scope}}: {0}", + "ed5c8e0055b1cae433e4c393ae04d555": "Invalid items: {0}", + "fef19f407c562658e0132f1625ce968b": "Unauthorized grant type: {0}", + "10b283f781f47e879e25e3b3b84f85e3": "Permission denied by {0}", + "19d7beb3c393b20162c2c43135b4f1c0": "Authorization code is expired", + "1e1b2073df0a8963670e9ed1e181dfe8": "Invalid {{JWT}}: {0}", + "3cfcae051965e0da5f704a334e1e884c": "Client id mismatches", + "839544ec984d6290cd45669efdd39699": "Redirect {{uri}} mismatches", + "fa925eb4771bd2897f14b1de78ca0ed2": "Invalid subject: {0}", + "53c21d3a92686e2956d527d097e1e1b9": "Access token has invalid {{user id}}: {0}", + "d83b55a5dfd3e16ca22699e39b072f3f": "Access token has invalid {{app id}}: {0}", + "f483b2bebd08d285c7009c6812f5ba24": "Access token is expired", + "9508d060bc6fd6e25cb4d0c00ea0480d": "Insufficient scope", + "373b2ad70311b6537ad42719088da022": "Failed to deserialize client. Register deserialization function using {{deserializeClient()}}.", + "5b853ac61fab20ec62d53e183e067ec3": "Failed to serialize client. Register serialization function using {{serializeClient()}}.", + "a2f3ad97b7b05618b6722b8f07e9f805": "{{OAuth 2.0 JWT}} bearer strategy requires a verify callback", + "a82c6b0f0db10b6e198e99068e765570": "{{OAuth 2.0 JWT}} bearer strategy requires an audience option", + "db8b02ad229f45cbb12e0d9845b09eb2": "{{OAuth 2.0 JWT}} bearer strategy requires a keying callback", + "b1eb21b6d32dde679f9b4bf0b2e0939d": "{{MACStrategy}} requires a verify callback", + "e82c91e82424949d4390135e5e8adf1c": "Invalid {{MAC}} token" +} diff --git a/lib/exchange/authorizationCode.js b/lib/exchange/authorizationCode.js index cbebb7b..df72316 100644 --- a/lib/exchange/authorizationCode.js +++ b/lib/exchange/authorizationCode.js @@ -6,6 +6,8 @@ /** * Module dependencies. */ +var SG = require('strong-globalize'); +var g = SG(); var utils = require('../utils') , TokenError = require('../errors/tokenerror'); @@ -66,37 +68,37 @@ module.exports = function(options, issue) { options = undefined; } options = options || {}; - - if (!issue) { throw new TypeError('oauth2orize.authorizationCode exchange requires an issue callback'); } - + + if (!issue) { throw new TypeError(g.f('{{oauth2orize.authorizationCode}} exchange requires an issue callback')); } + var userProperty = options.userProperty || 'user'; return function authorization_code(req, res, next) { - if (!req.body) { return next(new Error('OAuth2orize requires body parsing. Did you forget app.use(express.bodyParser())?')); } - + if (!req.body) { return next(new Error(g.f('{{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] , code = req.body.code , redirectURI = req.body.redirect_uri; - - if (!code) { return next(new TokenError('Missing required parameter: code', 'invalid_request')); } - + + if (!code) { return next(new TokenError(g.f('Missing required parameter: {{code}}'), 'invalid_request')); } + try { issue(client, code, redirectURI, function(err, accessToken, refreshToken, params) { if (err) { return next(err); } - if (!accessToken) { return next(new TokenError('Invalid authorization code', 'invalid_grant')); } + if (!accessToken) { return next(new TokenError(g.f('Invalid authorization code'), 'invalid_grant')); } if (refreshToken && typeof refreshToken == 'object') { 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/clientCredentials.js b/lib/exchange/clientCredentials.js index ee2f13f..ad8587e 100644 --- a/lib/exchange/clientCredentials.js +++ b/lib/exchange/clientCredentials.js @@ -6,10 +6,12 @@ /** * Module dependencies. */ +var SG = require('strong-globalize'); +var g = SG(); var utils = require('../utils') , TokenError = require('../errors/tokenerror'); - + /** * Exchanges client credentials for access tokens. * @@ -65,8 +67,8 @@ module.exports = function(options, issue) { options = undefined; } options = options || {}; - - if (!issue) { throw new TypeError('oauth2orize.clientCredentials exchange requires an issue callback'); } + + if (!issue) { throw new TypeError(g.f('{{oauth2orize.clientCredentials}} exchange requires an issue callback')); } var userProperty = options.userProperty || 'user'; @@ -81,13 +83,13 @@ 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())?')); } - + if (!req.body) { return next(new Error(g.f('{{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) { for (var i = 0, len = separators.length; i < len; i++) { var separated = scope.split(separators[i]); @@ -100,11 +102,11 @@ 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')); + return next(new TokenError(g.f('Invalid client credentials'), 'invalid_grant')); } if (refreshToken && typeof refreshToken === 'object') { params = refreshToken; @@ -116,14 +118,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 === 4) { diff --git a/lib/exchange/jwt.js b/lib/exchange/jwt.js index 28d1463..66cbecc 100644 --- a/lib/exchange/jwt.js +++ b/lib/exchange/jwt.js @@ -6,6 +6,8 @@ /** * Module dependencies. */ +var SG = require('strong-globalize'); +var g = SG(); var AuthorizationError = require('../errors/authorizationerror'); /** @@ -97,14 +99,14 @@ module.exports = function jwtBearer(options, issue) { options = options || {}; if (!issue) { - throw new Error('OAuth 2.0 jwtBearer exchange middleware requires an issue function.'); + throw new Error(g.f('{{OAuth 2.0 jwtBearer}} exchange middleware requires an {{issue}} function.')); } var userProperty = options.userProperty || 'user'; return function jwt_bearer(req, res, next) { if (!req.body) { - return next(new Error('Request body not parsed. Use bodyParser middleware.')); + return next(new Error(g.f('Request body not parsed. Use {{bodyParser}} middleware.'))); } // The 'user' property of `req` holds the authenticated user. In the case @@ -115,7 +117,7 @@ module.exports = function jwtBearer(options, issue) { , separator = '.'; if (!jwtBearer) { - return next(new AuthorizationError('missing assertion parameter', 'invalid_request')); + return next(new AuthorizationError(g.f('missing assertion parameter'), 'invalid_request')); } contents = jwtBearer.split(separator); @@ -129,7 +131,7 @@ module.exports = function jwtBearer(options, issue) { return next(err); } if (!accessToken) { - return next(new AuthorizationError('invalid JWT', 'invalid_grant')); + return next(new AuthorizationError(g.f('invalid {{JWT}}'), 'invalid_grant')); } var tok = {}; diff --git a/lib/exchange/password.js b/lib/exchange/password.js index aaa94e3..7b0b876 100644 --- a/lib/exchange/password.js +++ b/lib/exchange/password.js @@ -6,6 +6,8 @@ /** * Module dependencies. */ +var SG = require('strong-globalize'); +var g = SG(); var utils = require('../utils') , TokenError = require('../errors/tokenerror'); @@ -67,7 +69,7 @@ module.exports = function(options, issue) { } options = options || {}; - if (!issue) { throw new TypeError('oauth2orize.password exchange requires an issue callback'); } + if (!issue) { throw new TypeError(g.f('{{oauth2orize.password}} exchange requires an {{issue}} callback')); } var userProperty = options.userProperty || 'user'; @@ -82,18 +84,18 @@ 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())?')); } - + if (!req.body) { return next(new Error(g.f('{{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 (!username) { return next(new TokenError(g.f('Missing required parameter: {{username}}'), 'invalid_request')); } + if (!passwd) { return next(new TokenError(g.f('Missing required parameter: {{password}}'), 'invalid_request')); } + if (scope) { for (var i = 0, len = separators.length; i < len; i++) { var separated = scope.split(separators[i]); @@ -106,28 +108,28 @@ 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')); } + if (!accessToken) { return next(new TokenError(g.f('Invalid resource owner credentials'), 'invalid_grant')); } if (refreshToken && typeof refreshToken == 'object') { 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 === 5) { diff --git a/lib/exchange/refreshToken.js b/lib/exchange/refreshToken.js index 9fdb10a..e03686d 100644 --- a/lib/exchange/refreshToken.js +++ b/lib/exchange/refreshToken.js @@ -6,6 +6,8 @@ /** * Module dependencies. */ +var SG = require('strong-globalize'); +var g = SG(); var utils = require('../utils') , TokenError = require('../errors/tokenerror'); @@ -63,9 +65,9 @@ module.exports = function(options, issue) { options = undefined; } options = options || {}; - - if (!issue) { throw new TypeError('oauth2orize.refreshToken exchange requires an issue callback'); } - + + if (!issue) { throw new TypeError(g.f('{{oauth2orize.refreshToken}} exchange requires an {{issue}} callback')); } + var userProperty = options.userProperty || 'user'; // For maximum flexibility, multiple scope spearators can optionally be @@ -79,16 +81,16 @@ 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())?')); } - + if (!req.body) { return next(new Error(g.f('{{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 (!refreshToken) { return next(new TokenError(g.f('Missing required parameter: {{refresh_token}}'), 'invalid_request')); } + if (scope) { for (var i = 0, len = separators.length; i < len; i++) { var separated = scope.split(separators[i]); @@ -101,28 +103,28 @@ 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')); } + if (!accessToken) { return next(new TokenError(g.f('Invalid refresh token'), 'invalid_grant')); } if (refreshToken && typeof refreshToken === 'object') { 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 === 4) { diff --git a/lib/grant/code.js b/lib/grant/code.js index 60708b4..5c56d66 100644 --- a/lib/grant/code.js +++ b/lib/grant/code.js @@ -6,6 +6,8 @@ /** * Module dependencies. */ +var SG = require('strong-globalize'); +var g = SG(); var url = require('url') , AuthorizationError = require('../errors/authorizationerror'); @@ -66,9 +68,9 @@ module.exports = function code(options, issue) { options = undefined; } options = options || {}; - - if (!issue) { throw new TypeError('oauth2orize.code grant requires an issue callback'); } - + + if (!issue) { throw new TypeError(g.f('{{oauth2orize.code}} grant requires an {{issue}} callback')); } + // For maximum flexibility, multiple scope separators can optionally be // allowed. This allows the server to accept clients that separate scope // with either space or comma (' ', ','). This violates the specification, @@ -86,7 +88,7 @@ module.exports = function code(options, issue) { return req.query[name]; } } - + /* Parse requests that request `code` as `response_type`. * * @param {http.ServerRequest} req @@ -97,9 +99,9 @@ module.exports = function code(options, issue) { , redirectURI = getParam(req, 'redirect_uri') , scope = getParam(req, 'scope') , state = getParam(req, 'state'); - - if (!clientID) { throw new AuthorizationError('Missing required parameter: client_id', 'invalid_request'); } - + + if (!clientID) { throw new AuthorizationError(g.f('Missing required parameter: {{client_id}}'), 'invalid_request'); } + if (scope) { for (var i = 0, len = separators.length; i < len; i++) { var separated = scope.split(separators[i]); @@ -110,10 +112,10 @@ module.exports = function code(options, issue) { break; } } - + if (!Array.isArray(scope)) { scope = [ scope ]; } } - + return { clientID: clientID, redirectURI: redirectURI, @@ -121,7 +123,7 @@ module.exports = function code(options, issue) { state: state }; } - + /* Sends responses to transactions that request `code` as `response_type`. * * @param {Object} txn @@ -130,30 +132,30 @@ module.exports = function code(options, issue) { * @api public */ function response(txn, res, next) { - if (!txn.redirectURI) { return next(new Error('Unable to issue redirect for OAuth 2.0 transaction')); } + if (!txn.redirectURI) { return next(new Error(g.f('Unable to issue redirect for {{OAuth 2.0}} transaction'))); } if (!txn.res.allow) { var parsed = url.parse(txn.redirectURI, true); delete parsed.search; parsed.query.error = 'access_denied'; if (txn.req && txn.req.state) { parsed.query.state = txn.req.state; } - + var location = url.format(parsed); return res.redirect(location); } - + function issued(err, code) { if (err) { return next(err); } - if (!code) { return next(new AuthorizationError('Request denied by authorization server', 'access_denied')); } - + if (!code) { return next(new AuthorizationError(g.f('Request denied by authorization server'), 'access_denied')); } + var parsed = url.parse(txn.redirectURI, true); delete parsed.search; parsed.query.code = code; if (txn.req && txn.req.state) { parsed.query.state = txn.req.state; } - + var location = url.format(parsed); return res.redirect(location); } - + // NOTE: The `redirect_uri`, if present in the client's authorization // request, must also be present in the subsequent request to exchange // the authorization code for an access token. Acting as a verifier, @@ -161,7 +163,7 @@ module.exports = function code(options, issue) { // types of attacks. More information can be found here: // // http://hueniverse.com/2011/06/oauth-2-0-redirection-uri-validation/ - + try { // console.log(issue.toString()); var arity = issue.length; @@ -169,13 +171,13 @@ module.exports = function code(options, issue) { issue(txn.client, txn.req.redirectURI, txn.user, txn.req.scope, txn.res, issued); } else { // arity == 5 issue(txn.client, txn.req.redirectURI, txn.user, txn.req.scope, issued); - } + } } catch (ex) { return next(ex); } } - - + + /** * Return `code` approval module. */ diff --git a/lib/grant/token.js b/lib/grant/token.js index f95d276..af7072e 100644 --- a/lib/grant/token.js +++ b/lib/grant/token.js @@ -6,7 +6,9 @@ /** * Module dependencies. */ -var url = require('url') + var SG = require('strong-globalize'); + var g = SG(); + var url = require('url') , qs = require('querystring') , utils = require('../utils') , AuthorizationError = require('../errors/authorizationerror'); @@ -66,9 +68,9 @@ module.exports = function token(options, issue) { options = undefined; } options = options || {}; - - if (!issue) { throw new TypeError('oauth2orize.token grant requires an issue callback'); } - + + if (!issue) { throw new TypeError(g.f('{{oauth2orize.token}} grant requires an {{issue}} callback')); } + // For maximum flexibility, multiple scope spearators can optionally be // allowed. This allows the server to accept clients that separate scope // with either space or comma (' ', ','). This violates the specification, @@ -86,7 +88,7 @@ module.exports = function token(options, issue) { return req.query[name]; } } - + /* Parse requests that request `token` as `response_type`. * * @param {http.ServerRequest} req @@ -97,9 +99,9 @@ module.exports = function token(options, issue) { , redirectURI = getParam(req, 'redirect_uri') , scope = getParam(req, 'scope') , state = getParam(req, 'state'); - - if (!clientID) { throw new AuthorizationError('Missing required parameter: client_id', 'invalid_request'); } - + + if (!clientID) { throw new AuthorizationError(g.f('Missing required parameter: {{client_id}}'), 'invalid_request'); } + if (scope) { for (var i = 0, len = separators.length; i < len; i++) { var separated = scope.split(separators[i]); @@ -110,10 +112,10 @@ module.exports = function token(options, issue) { break; } } - + if (!Array.isArray(scope)) { scope = [ scope ]; } } - + return { clientID: clientID, redirectURI: redirectURI, @@ -121,7 +123,7 @@ module.exports = function token(options, issue) { state: state }; } - + /* Sends responses to transactions that request `token` as `response_type`. * * @param {Object} txn @@ -130,43 +132,43 @@ module.exports = function token(options, issue) { * @api public */ function response(txn, res, next) { - if (!txn.redirectURI) { return next(new Error('Unable to issue redirect for OAuth 2.0 transaction')); } + if (!txn.redirectURI) { return next(new Error(g.f('Unable to issue redirect for {{OAuth 2.0}} transaction'))); } if (!txn.res.allow) { var err = {}; err.error = 'access_denied'; if (txn.req && txn.req.state) { err.state = txn.req.state; } - + var parsed = url.parse(txn.redirectURI); parsed.hash = qs.stringify(err); - + var location = url.format(parsed); return res.redirect(location); } - + function issued(err, accessToken, params) { if (err) { return next(err); } - if (!accessToken) { return next(new AuthorizationError('Request denied by authorization server', 'access_denied')); } - + if (!accessToken) { return next(new AuthorizationError(g.f('Request denied by authorization server'), 'access_denied')); } + var tok = {}; tok.access_token = accessToken; if (params) { utils.merge(tok, params); } tok.token_type = tok.token_type || 'Bearer'; if (txn.req && txn.req.state) { tok.state = txn.req.state; } - + var parsed = url.parse(txn.redirectURI); parsed.hash = qs.stringify(tok); - + var location = url.format(parsed); return res.redirect(location); } - + // NOTE: In contrast to an authorization code grant, redirectURI is not // passed as an argument to the issue callback because it is not used // as a verifier in a subsequent token exchange. However, when // issuing an implicit access tokens, an application must ensure that // the redirection URI is registered, which can be done in the // `validate` callback of `authorization` middleware. - + try { var arity = issue.length; if (arity === 5) { @@ -178,8 +180,8 @@ module.exports = function token(options, issue) { return next(ex); } } - - + + /** * Return `token` approval module. */ diff --git a/lib/index.js b/lib/index.js index 6c4bfb1..e7c5ed3 100644 --- a/lib/index.js +++ b/lib/index.js @@ -6,6 +6,10 @@ /** * Module dependencies. */ +var path = require('path'); +var SG = require('strong-globalize'); +SG.SetRootDir(path.join(__dirname, '..')); +var g = SG(); var loopbackOAuth2 = require('./oauth2-loopback'); var exports = module.exports = loopbackOAuth2; @@ -24,8 +28,8 @@ exports.authenticate = function(options) { var authenticate = app._oauth2Handlers && app._oauth2Handlers.authenticate; if (!authenticate) { - return next(new Error( - 'The OAuth2 component was not configured for this application.')); + return next(new Error(g.f( + 'The {{OAuth2}} component was not configured for this application.'))); } var handlers = authenticate(options); diff --git a/lib/mac-token.js b/lib/mac-token.js index 726c57e..0a949d5 100644 --- a/lib/mac-token.js +++ b/lib/mac-token.js @@ -133,7 +133,3 @@ MACTokenGenerator.prototype.validate = function(req) { return params.access_token; } - - - - diff --git a/lib/middleware/authorization.js b/lib/middleware/authorization.js index 97d8bda..af1c6d0 100644 --- a/lib/middleware/authorization.js +++ b/lib/middleware/authorization.js @@ -6,7 +6,9 @@ /** * Module dependencies. */ -var utils = require('../utils') + var SG = require('strong-globalize'); + var g = SG(); + var utils = require('../utils') , helpers = require('../oauth2-helper') , AuthorizationError = require('../errors/authorizationerror'); @@ -109,32 +111,32 @@ module.exports = function(server, options, validate, immediate) { } options = options || {}; immediate = immediate || function (client, user, done) { return done(null, false); }; - - if (!server) { throw new TypeError('oauth2orize.authorization middleware requires a server argument'); } - if (!validate) { throw new TypeError('oauth2orize.authorization middleware requires a validate function'); } - + + if (!server) { throw new TypeError(g.f('{{oauth2orize.authorization}} middleware requires a {{server}} argument')); } + if (!validate) { throw new TypeError(g.f('{{oauth2orize.authorization}} middleware requires a {{validate}} function')); } + var lenTxnID = options.idLength || 8 , userProperty = options.userProperty || 'user' , key = options.sessionKey || 'authorize'; - + return function authorization(req, res, next) { if (!req.session) { - return next(new Error('OAuth2orize requires session support.' + - ' Did you forget app.use(express.session(...))?')); + return next(new Error(g.f('{{OAuth2orize}} requires {{session}} support.' + + ' Did you forget {{app.use(express.session(...))}}?'))); } - + var body = req.body || {} , type = req.query.response_type || body.response_type; server._parse(type, req, function(err, areq) { if (err) { return next(err); } if (!areq || !Object.keys(areq).length) { - return next(new AuthorizationError( - 'Missing required parameter: response_type', 'invalid_request')); + return next(new AuthorizationError(g.f( + 'Missing required parameter: {{response_type}}'), 'invalid_request')); } if (Object.keys(areq).length === 1 && areq.type) { - return next(new AuthorizationError( - 'Unsupported response type: ' + type, 'unsupported_response_type')); + return next(new AuthorizationError(g.f( + 'Unsupported response type: %s', type), 'unsupported_response_type')); } function validated(err, client, redirectURI) { @@ -147,7 +149,7 @@ module.exports = function(server, options, validate, immediate) { return next(err); } if (!client) { - return next(new AuthorizationError('Invalid client', + return next(new AuthorizationError(g.f('Invalid client'), 'invalid_client')); } @@ -163,14 +165,14 @@ module.exports = function(server, options, validate, immediate) { } if (!redirectURI) { - return next(new AuthorizationError( - 'Invalid request: redirect_uri is missing', + return next(new AuthorizationError(g.f( + 'Invalid request: {{redirect_uri}} is missing'), 'invalid_request')); } helpers.validateClient(client, {redirectURI: redirectURI}, next); req.oauth2.redirectURI = redirectURI; - + req.oauth2.req = areq; req.oauth2.user = req[userProperty]; @@ -182,7 +184,7 @@ module.exports = function(server, options, validate, immediate) { server._respond(req.oauth2, res, function(err) { if (err) { return next(err); } - return next(new AuthorizationError('Unsupported response type: ' + req.oauth2.req.type, 'unsupported_response_type')); + return next(new AuthorizationError(g.f('Unsupported response type: %s', req.oauth2.req.type), 'unsupported_response_type')); }); } else { // A dialog needs to be conducted to obtain the user's approval. @@ -233,7 +235,7 @@ module.exports = function(server, options, validate, immediate) { immediate(req.oauth2.client, req.oauth2.user, immediated); } } - + try { var arity = validate.length; if (arity === 3) { diff --git a/lib/middleware/decision.js b/lib/middleware/decision.js index 23bd97c..9437309 100644 --- a/lib/middleware/decision.js +++ b/lib/middleware/decision.js @@ -6,7 +6,9 @@ /** * Module dependencies. */ -var AuthorizationError = require('../errors/authorizationerror') + var SG = require('strong-globalize'); + var g = SG(); + var AuthorizationError = require('../errors/authorizationerror') , ForbiddenError = require('../errors/forbiddenerror'); @@ -77,31 +79,31 @@ module.exports = function(server, options, parse) { } options = options || {}; parse = parse || function(req, done) { return done(); }; - - if (!server) { throw new TypeError('oauth2orize.decision middleware requires a server argument'); } - + + if (!server) { throw new TypeError(g.f('{{oauth2orize.decision}} middleware requires a {{server}} argument')); } + var cancelField = options.cancelField || 'cancel' , userProperty = options.userProperty || 'user' , key = options.sessionKey || 'authorize'; - + return function decision(req, res, next) { - if (!req.session) { return next(new Error('OAuth2orize requires session support. Did you forget app.use(express.session(...))?')); } - if (!req.body) { return next(new Error('OAuth2orize requires body parsing. Did you forget app.use(express.bodyParser())?')); } - if (!req.oauth2) { return next(new Error('OAuth2orize requires transaction support. Did you forget oauth2orize.transactionLoader(...)?')); } - if (!req.session[key]) { return next(new ForbiddenError('Unable to load OAuth 2.0 transactions from session')); } - + if (!req.session) { return next(new Error(g.f('{{OAuth2orize}} requires {{session}} support. Did you forget {{app.use(express.session(...))}}?'))); } + if (!req.body) { return next(new Error(g.f('{{OAuth2orize}} requires body parsing. Did you forget {{app.use(express.bodyParser())}}?'))); } + if (!req.oauth2) { return next(new Error(g.f('{{OAuth2orize}} requires transaction support. Did you forget {{oauth2orize.transactionLoader(...)}}?'))); } + if (!req.session[key]) { return next(new ForbiddenError(g.f('Unable to load {{OAuth 2.0}} transactions from session'))); } + parse(req, function(err, ares) { if (err) { return next(err); } - + var tid = req.oauth2.transactionID; req.oauth2.user = req[userProperty]; req.oauth2.res = ares || {}; - + if (req.oauth2.res.allow === undefined) { if (!req.body[cancelField]) { req.oauth2.res.allow = true; } else { req.oauth2.res.allow = false; } } - + // proxy end() to delete the transaction var end = res.end; res.end = function(chunk, encoding) { @@ -109,10 +111,10 @@ module.exports = function(server, options, parse) { res.end = end; res.end(chunk, encoding); }; - + server._respond(req.oauth2, res, function(err) { if (err) { return next(err); } - return next(new AuthorizationError('Unsupported response type: ' + req.oauth2.req.type, 'unsupported_response_type')); + return next(new AuthorizationError(g.f('Unsupported response type: %s', req.oauth2.req.type), 'unsupported_response_type')); }); }); }; diff --git a/lib/middleware/revoke.js b/lib/middleware/revoke.js index a5d6e36..83e9da2 100644 --- a/lib/middleware/revoke.js +++ b/lib/middleware/revoke.js @@ -6,7 +6,9 @@ /** * Module dependencies. */ -var TokenError = require('../errors/tokenerror'); + var SG = require('strong-globalize'); + var g = SG(); + var TokenError = require('../errors/tokenerror'); /** * https://tools.ietf.org/html/rfc7009 @@ -24,13 +26,13 @@ module.exports = function revoke(server, options, revokeToken) { options = options || {}; if (!server) { - throw new TypeError( - 'oauth2orize.revoke middleware requires a server argument'); + throw new TypeError(g.f( + '{{oauth2orize.revoke}} middleware requires a {{server}} argument')); } if (typeof revokeToken !== 'function') { - throw new TypeError( - 'oauth2orize.revoke middleware requires a revokeToken function'); + throw new TypeError(g.f( + '{{oauth2orize.revoke}} middleware requires a {{revokeToken}} function')); } var userProperty = options.userProperty || 'user'; @@ -43,15 +45,15 @@ module.exports = function revoke(server, options, revokeToken) { var token = (req.body && req.body.token) || req.query.token; if (!token) { - return next(new TokenError( - 'Missing required parameter: token', 'invalid_request')); + return next(new TokenError(g.f( + 'Missing required parameter: {{token}}'), 'invalid_request')); } var type = (req.body && req.body.token_type_hint) || req.query.token_type_hint || 'access_token'; if (type !== 'refresh_token' && type !== 'access_token') { - return next(new TokenError( - 'Unsupported token type: ' + type, 'unsupported_token_type')); + return next(new TokenError(g.f( + 'Unsupported token type: %s', type), 'unsupported_token_type')); } revokeToken(client, token, type, function(err) { diff --git a/lib/middleware/token.js b/lib/middleware/token.js index 2688ec6..6766af7 100644 --- a/lib/middleware/token.js +++ b/lib/middleware/token.js @@ -6,7 +6,9 @@ /** * Module dependencies. */ -var TokenError = require('../errors/tokenerror'); + var SG = require('strong-globalize'); + var g = SG(); + var TokenError = require('../errors/tokenerror'); /** @@ -52,15 +54,15 @@ var TokenError = require('../errors/tokenerror'); */ module.exports = function token(server, options) { options = options || {}; - - if (!server) { throw new TypeError('oauth2orize.token middleware requires a server argument'); } - + + if (!server) { throw new TypeError(g.f('{{oauth2orize.token}} middleware requires a {{server}} argument')); } + return function token(req, res, next) { var type = req.body.grant_type; - + server._exchange(type, req, res, function(err) { if (err) { return next(err); } - return next(new TokenError('Unsupported grant type: ' + type, 'unsupported_grant_type')); + return next(new TokenError(g.f('Unsupported grant type: %s', type), 'unsupported_grant_type')); }); }; }; diff --git a/lib/middleware/transactionLoader.js b/lib/middleware/transactionLoader.js index 054c4a3..f29b155 100644 --- a/lib/middleware/transactionLoader.js +++ b/lib/middleware/transactionLoader.js @@ -6,7 +6,9 @@ /** * Module dependencies. */ -var AuthorizationError = require('../errors/authorizationerror') + var SG = require('strong-globalize'); + var g = SG(); + var AuthorizationError = require('../errors/authorizationerror') , BadRequestError = require('../errors/badrequesterror') , ForbiddenError = require('../errors/forbiddenerror'); @@ -31,24 +33,24 @@ var AuthorizationError = require('../errors/authorizationerror') */ module.exports = function(server, options) { options = options || {}; - - if (!server) { throw new TypeError('oauth2orize.transactionLoader middleware requires a server argument'); } - + + if (!server) { throw new TypeError(g.f('{{oauth2orize.transactionLoader}} middleware requires a {{server}} argument')); } + var field = options.transactionField || 'transaction_id' , key = options.sessionKey || 'authorize'; - + return function transactionLoader(req, res, next) { - if (!req.session) { return next(new Error('OAuth2orize requires session support. Did you forget app.use(express.session(...))?')); } - if (!req.session[key]) { return next(new ForbiddenError('Unable to load OAuth 2.0 transactions from session')); } - + if (!req.session) { return next(new Error(g.f('{{OAuth2orize}} requires {{session}} support. Did you forget {{app.use(express.session(...))}}?'))); } + if (!req.session[key]) { return next(new ForbiddenError(g.f('Unable to load {{OAuth 2.0}} transactions from session'))); } + var query = req.query || {} , body = req.body || {} , tid = query[field] || body[field]; - - if (!tid) { return next(new BadRequestError('Missing required parameter: ' + field)); } + + if (!tid) { return next(new BadRequestError(g.f('Missing required parameter: %s', field))); } var txn = req.session[key][tid]; - if (!txn) { return next(new ForbiddenError('Unable to load OAuth 2.0 transaction: ' + tid)); } - + if (!txn) { return next(new ForbiddenError(g.f('Unable to load {{OAuth 2.0}} transaction: %s', tid))); } + server.deserializeClient(txn.client, function(err, client) { if (err) { return next(err); } if (!client) { @@ -56,9 +58,9 @@ module.exports = function(server, options) { // Since then, however, it has been invalidated. The transaction will // be invalidated and no response will be sent to the client. delete req.session[key][tid]; - return next(new AuthorizationError('Unauthorized client', 'unauthorized_client')); + return next(new AuthorizationError(g.f('Unauthorized client'), 'unauthorized_client')); } - + req.oauth2 = {}; req.oauth2.transactionID = tid; req.oauth2.client = client; diff --git a/lib/models/index.js b/lib/models/index.js index 10e620e..d56f58f 100644 --- a/lib/models/index.js +++ b/lib/models/index.js @@ -255,4 +255,3 @@ module.exports = function(app, options) { return models; }; - diff --git a/lib/oauth2-helper.js b/lib/oauth2-helper.js index c0034c6..8a2cb12 100644 --- a/lib/oauth2-helper.js +++ b/lib/oauth2-helper.js @@ -2,7 +2,8 @@ // Node module: loopback-component-oauth2 // US Government Users Restricted Rights - Use, duplication or disclosure // restricted by GSA ADP Schedule Contract with IBM Corp. - +var SG = require('strong-globalize'); +var g = SG(); var jwt = require('jws'); var AuthorizationError = require('./errors/authorizationerror'); @@ -48,7 +49,7 @@ function normalizeList(items) { } else if (typeof items === 'string') { list = items.split(/[\s,]+/g).filter(Boolean); } else { - throw new Error('Invalid items: ' + items); + throw new Error(g.f('Invalid items: %s', items)); } return list; } @@ -121,8 +122,8 @@ function validateClient(client, options, next) { } } if (!matched) { - err = new AuthorizationError( - 'Unauthorized redirectURI: ' + options.redirectURI, 'access_denied'); + err = new AuthorizationError(g.f( + 'Unauthorized {{redirectURI}}: %s', options.redirectURI), 'access_denied'); return next(err) || err; } } @@ -132,8 +133,8 @@ function validateClient(client, options, next) { var authorizedScopes = normalizeList(client.scopes); var requestedScopes = normalizeList(options.scope); if (authorizedScopes.length && !isScopeAuthorized(requestedScopes, authorizedScopes)) { - err = new AuthorizationError( - 'Unauthorized scope: ' + options.scope, 'access_denied'); + err = new AuthorizationError(g.f( + 'Unauthorized {{scope}}: %s', options.scope), 'access_denied'); return next(err) || err; } } @@ -143,8 +144,8 @@ function validateClient(client, options, next) { var authorizedTypes = normalizeList(client.responseTypes); if (authorizedTypes.length && authorizedTypes.indexOf(options.responseType) === -1) { - err = new AuthorizationError( - 'Unauthorized response type: ' + options.responseType, 'access_denied'); + err = new AuthorizationError(g.f( + 'Unauthorized response type: %s', options.responseType), 'access_denied'); return next(err) || err; } } @@ -155,8 +156,8 @@ function validateClient(client, options, next) { var authorizedGrantTypes = normalizeList(client.grantTypes); if (authorizedGrantTypes.length && authorizedGrantTypes.indexOf(options.grantType) === -1) { - err = new AuthorizationError( - 'Unauthorized grant type: ' + options.grantType, 'access_denied'); + err = new AuthorizationError(g.f( + 'Unauthorized grant type: %s', options.grantType), 'access_denied'); return next(err) || err; } } diff --git a/lib/oauth2-loopback.js b/lib/oauth2-loopback.js index 2314bcc..69b8c26 100644 --- a/lib/oauth2-loopback.js +++ b/lib/oauth2-loopback.js @@ -6,7 +6,9 @@ /** * Module dependencies. */ -var url = require('url') + var SG = require('strong-globalize'); + var g = SG(); + var url = require('url') , oauth2Provider = require('./oauth2orize') , TokenError = require('./errors/tokenerror') , AuthorizationError = require('./errors/authorizationerror') @@ -209,16 +211,16 @@ module.exports = function(app, options) { // The client id can be a number instead of string if (client.id != clientId) { - return done(new TokenError('Client id mismatches', + return done(new TokenError(g.f('Client id mismatches'), 'invalid_grant')); } if (redirectURI != authCode.redirectURI) { - return done(new TokenError('Redirect uri mismatches', + return done(new TokenError(g.f('Redirect {{uri}} mismatches'), 'invalid_grant')); } if (isExpired(authCode)) { - return done(new TokenError('Authorization code is expired', + return done(new TokenError(g.f('Authorization code is expired'), 'invalid_grant')); } @@ -368,8 +370,8 @@ module.exports = function(app, options) { return done(err); } if (!user) { - return done(new AuthorizationError( - 'Invalid subject: ' + subject, 'access_denied')); + return done(new AuthorizationError(g.f( + 'Invalid subject: %s', subject), 'access_denied')); } models.permissions.isAuthorized(client.id, user.id, scope, function(err, authorized) { @@ -379,8 +381,8 @@ module.exports = function(app, options) { if (authorized) { generateAccessToken(user); } else { - return done(new AuthorizationError( - 'Permission denied by ' + subject, 'access_denied')); + return done(new AuthorizationError(g.f( + 'Permission denied by %s', subject), 'access_denied')); } }); }); @@ -504,7 +506,7 @@ module.exports = function(app, options) { decodedJWT = jwt.decode(jwtToken); debug('Decoded JWT: %j', decodedJWT); } else { - done(new Error('Invalid JWT: ' + jwtToken)); + done(new Error(g.f('Invalid {{JWT}}: %j', jwtToken))); } } catch (err) { return done(err); @@ -540,8 +542,8 @@ module.exports = function(app, options) { return done(err); } if (!user) { - return done(new AuthorizationError( - 'Invalid subject: ' + payload.sub, 'access_denied')); + return done(new AuthorizationError(g.f( + 'Invalid subject: %s', payload.sub), 'access_denied')); } models.permissions.isAuthorized(client.id, user.id, payload.scope, function(err, authorized) { @@ -551,8 +553,8 @@ module.exports = function(app, options) { if (authorized) { generateAccessToken(user); } else { - done(new AuthorizationError( - 'Permission denied by ' + payload.sub), 'access_denied'); + done(new AuthorizationError(g.f( + 'Permission denied by %s', payload.sub), 'access_denied')); } }); }); @@ -620,8 +622,7 @@ module.exports = function(app, options) { if (err) { return next(err); } - return next(new AuthorizationError('Unsupported response type: ' - + req.oauth2.req.type, 'unsupported_response_type')); + return next(new AuthorizationError(g.f('Unsupported response type: %s', req.oauth2.req.type), 'unsupported_response_type')); }); } else { next(); diff --git a/lib/resource-server.js b/lib/resource-server.js index a3e32fb..528436a 100644 --- a/lib/resource-server.js +++ b/lib/resource-server.js @@ -3,6 +3,8 @@ // US Government Users Restricted Rights - Use, duplication or disclosure // restricted by GSA ADP Schedule Contract with IBM Corp. +var SG = require('strong-globalize'); +var g = SG(); var async = require('async') , oauth2Provider = require('./oauth2orize') , scopeValidator = require('./scope') @@ -40,7 +42,7 @@ function setupResourceServer(app, options, models) { debug('Access token found: %j', token); if (isExpired(token)) { - return done(new TokenError('Access token is expired', + return done(new TokenError(g.f('Access token is expired'), 'invalid_grant')); } @@ -59,8 +61,7 @@ function setupResourceServer(app, options, models) { } if (!u) { return done( - new TokenError('Access token has invalid user id: ' + - userId, 'invalid_grant')); + new TokenError(g.f('Access token has invalid {{user id}}: %s', userId), 'invalid_grant')); } debug('User found: %s', userInfo(u)); user = u; @@ -77,8 +78,7 @@ function setupResourceServer(app, options, models) { } if (!a) { return done( - new TokenError('Access token has invalid app id: ' + appId, - 'invalid_grant')); + new TokenError(g.f('Access token has invalid {{app id}}: %s', appId), 'invalid_grant')); } debug('Client found: %s', clientInfo(a)); app = a; @@ -177,4 +177,4 @@ function setupResourceServer(app, options, models) { } return authenticate; -} \ No newline at end of file +} diff --git a/lib/scope.js b/lib/scope.js index 1ab3463..f6535bf 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -3,6 +3,8 @@ // US Government Users Restricted Rights - Use, duplication or disclosure // restricted by GSA ADP Schedule Contract with IBM Corp. +var SG = require('strong-globalize'); +var g = SG(); var pathToRegexp = require('path-to-regexp'); var debug = require('debug')('loopback:oauth2:scope'); var oauth2Provider = require('./oauth2orize'); @@ -109,8 +111,8 @@ module.exports = function(options) { cb(); } else { debug('Insufficient scope: ', tokenScopes); - cb(new oauth2Provider.TokenError( - 'Insufficient scope', 'insufficient_scope', null, 403)); + cb(new oauth2Provider.TokenError(g.f( + 'Insufficient scope'), 'insufficient_scope', null, 403)); } }; } @@ -120,4 +122,3 @@ module.exports = function(options) { checkScopes(req, scopes, next); }; } - diff --git a/lib/server.js b/lib/server.js index 23285fa..dd68d38 100644 --- a/lib/server.js +++ b/lib/server.js @@ -6,7 +6,9 @@ /** * Module dependencies. */ -var UnorderedList = require('./unorderedlist') + var SG = require('strong-globalize'); + var g = SG(); + var UnorderedList = require('./unorderedlist') , authorization = require('./middleware/authorization') , decision = require('./middleware/decision') , transactionLoader = require('./middleware/transactionLoader') @@ -72,7 +74,7 @@ Server.prototype.grant = function(type, phase, fn) { if (mod.response) { this.grant(type, 'response', mod.response); } return this; } - + if (typeof phase == 'function') { // sig: grant(type, fn) fn = phase; @@ -80,7 +82,7 @@ Server.prototype.grant = function(type, phase, fn) { } if (type === '*') { type = null; } if (type) { type = new UnorderedList(type); } - + if (phase == 'request') { debug('register request parser %s %s', type || '*', fn.name || 'anonymous'); this._reqParsers.push({ type: type, handle: fn }); @@ -116,7 +118,7 @@ Server.prototype.exchange = function(type, fn) { type = fn.name; } if (type === '*') { type = null; } - + debug('register exchanger %s %s', type || '*', fn.name || 'anonymous'); this._exchanges.push({ type: type, handle: fn }); return this; @@ -187,23 +189,23 @@ Server.prototype.serializeClient = function(fn, done) { if (typeof fn === 'function') { return this._serializers.push(fn); } - + // private implementation that traverses the chain of serializers, attempting // to serialize a client var client = fn; - + var stack = this._serializers; (function pass(i, err, obj) { // serializers use 'pass' as an error to skip processing if ('pass' === err) { err = undefined; } // an error or serialized object was obtained, done if (err || obj) { return done(err, obj); } - + var layer = stack[i]; if (!layer) { - return done(new Error('Failed to serialize client. Register serialization function using serializeClient().')); + return done(new Error(g.f('Failed to serialize client. Register serialization function using {{serializeClient()}}.'))); } - + try { layer(client, function(e, o) { pass(i + 1, e, o); } ); } catch (ex) { @@ -229,11 +231,11 @@ Server.prototype.deserializeClient = function(fn, done) { if (typeof fn === 'function') { return this._deserializers.push(fn); } - + // private implementation that traverses the chain of deserializers, // attempting to deserialize a client var obj = fn; - + var stack = this._deserializers; (function pass(i, err, client) { // deserializers use 'pass' as an error to skip processing @@ -243,12 +245,12 @@ Server.prototype.deserializeClient = function(fn, done) { // a valid client existed when establishing the session, but that client has // since been deauthorized if (client === null || client === false) { return done(null, false); } - + var layer = stack[i]; if (!layer) { - return done(new Error('Failed to deserialize client. Register deserialization function using deserializeClient().')); + return done(new Error(g.f('Failed to deserialize client. Register deserialization function using {{deserializeClient()}}.'))); } - + try { layer(obj, function(e, c) { pass(i + 1, e, c); } ); } catch (ex) { @@ -259,7 +261,7 @@ Server.prototype.deserializeClient = function(fn, done) { /** - * Parse authorization request into transaction using registered grant middleware. + * Parse authorization request into transaction using registered grant middleware. * * @param {String} type * @param {http.ServerRequest} req @@ -270,13 +272,13 @@ Server.prototype._parse = function(type, req, cb) { var ultype = new UnorderedList(type) , stack = this._reqParsers , areq = {}; - + if (type) { areq.type = type; } - + (function pass(i) { var layer = stack[i]; if (!layer) { return cb(null, areq); } - + try { debug('parse:%s', layer.handle.name || 'anonymous'); if (layer.type === null || layer.type.equalTo(ultype)) { @@ -302,7 +304,7 @@ Server.prototype._parse = function(type, req, cb) { }; /** - * Respond to authorization transaction using registered grant middleware. + * Respond to authorization transaction using registered grant middleware. * * @param {Object} txn * @param {http.ServerResponse} res @@ -313,13 +315,13 @@ Server.prototype._respond = function(txn, res, cb) { var ultype = new UnorderedList(txn.req.type) , stack = this._resHandlers , idx = 0; - + function next(err) { if (err) { return cb(err); } - + var layer = stack[idx++]; if (!layer) { return cb(); } - + try { debug('respond:%s', layer.handle.name || 'anonymous'); if (layer.type === null || layer.type.equalTo(ultype)) { @@ -335,7 +337,7 @@ Server.prototype._respond = function(txn, res, cb) { }; /** - * Process token request using registered exchange middleware. + * Process token request using registered exchange middleware. * * @param {String} type * @param {http.ServerRequest} req @@ -346,13 +348,13 @@ Server.prototype._respond = function(txn, res, cb) { Server.prototype._exchange = function(type, req, res, cb) { var stack = this._exchanges , idx = 0; - + function next(err) { if (err) { return cb(err); } - + var layer = stack[idx++]; if (!layer) { return cb(); } - + try { debug('exchange:%s', layer.handle.name || 'anonymous'); if (layer.type === null || layer.type === type) { diff --git a/lib/strategy/jwt-bearer/strategy.js b/lib/strategy/jwt-bearer/strategy.js index c082b62..a481831 100755 --- a/lib/strategy/jwt-bearer/strategy.js +++ b/lib/strategy/jwt-bearer/strategy.js @@ -10,6 +10,8 @@ /** * Module dependencies. */ +var SG = require('strong-globalize'); +var g = SG(); var passport = require('passport-strategy'); var util = require('util'); var jwt = require('jws'); @@ -30,13 +32,13 @@ function Strategy(options, keying, verify) { var audience = options.audience; if (!audience) { - throw new TypeError('OAuth 2.0 JWT bearer strategy requires an audience option'); + throw new TypeError(g.f('{{OAuth 2.0 JWT}} bearer strategy requires an audience option')); } if (!keying) { - throw new TypeError('OAuth 2.0 JWT bearer strategy requires a keying callback'); + throw new TypeError(g.f('{{OAuth 2.0 JWT}} bearer strategy requires a keying callback')); } if (!verify) { - throw new TypeError('OAuth 2.0 JWT bearer strategy requires a verify callback'); + throw new TypeError(g.f('{{OAuth 2.0 JWT}} bearer strategy requires a verify callback')); } if (!Array.isArray(audience)) { diff --git a/lib/strategy/mac/strategy.js b/lib/strategy/mac/strategy.js index ae15879..1b5f267 100644 --- a/lib/strategy/mac/strategy.js +++ b/lib/strategy/mac/strategy.js @@ -6,9 +6,11 @@ /** * Module dependencies. */ +var SG = require('strong-globalize'); +var g = SG(); var passport = require('passport-strategy') - , util = require('util') - , MACGenerator = require('../../mac-token'); +, util = require('util') +, MACGenerator = require('../../mac-token'); /** * Creates an instance of `Strategy`. @@ -64,7 +66,7 @@ function Strategy(options, verify) { options = {}; } if (!verify) { - throw new TypeError('MACStrategy requires a verify callback'); + throw new TypeError(g.f('{{MACStrategy}} requires a verify callback')); } passport.Strategy.call(this); @@ -96,7 +98,7 @@ Strategy.prototype.authenticate = function(req) { var token = this._macGenerator.validate(req); if (!token) { - return this.fail(this._challenge('Invalid MAC token')); + return this.fail(this._challenge(g.f('Invalid {{MAC}} token'))); } var self = this; diff --git a/package.json b/package.json index 267280a..b3efb7e 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "passport-strategy": "^1.0.0", "path-to-regexp": "^1.2.0", "pkginfo": "^0.4.0", + "strong-globalize": "^2.5.8", "uid2": "^0.0.3", "utils-merge": "^1.0.0" }, diff --git a/test/middleware/authorization.test.js b/test/middleware/authorization.test.js index 7896317..35f4482 100644 --- a/test/middleware/authorization.test.js +++ b/test/middleware/authorization.test.js @@ -12,13 +12,13 @@ var chai = require('chai') describe('authorization', function() { - + var server = new Server(); server.serializeClient(function(client, done) { if (client.id == '1234' || client.id == '2234' || client.id == '3234') { return done(null, client.id); } return done(new Error('something went wrong while serializing client')); }); - + server.grant('code', function(req) { return { clientID: req.query['client_id'], @@ -26,11 +26,11 @@ describe('authorization', function() { scope: req.query['scope'] }; }); - + server.grant('throw-error', function(req) { throw new Error('something went wrong while parsing authorization request'); }); - + function validate(clientID, redirectURI, done) { if (clientID == '1234' && redirectURI == 'http://example.com/auth/callback') { return done(null, { id: '1234', name: 'Example' }, 'http://example.com/auth/callback'); @@ -49,24 +49,24 @@ describe('authorization', function() { } return done(new Error('something went wrong while validating client')); } - - + + it('should be named authorization', function() { expect(authorization(server, function(){}).name).to.equal('authorization'); }); - + it('should throw if constructed without a server argument', function() { expect(function() { authorization(); }).to.throw(TypeError, 'oauth2orize.authorization middleware requires a server argument'); }); - + it('should throw if constructed without a validate argument', function() { expect(function() { authorization(server); }).to.throw(TypeError, 'oauth2orize.authorization middleware requires a validate function'); }); - + describe('handling a request for authorization', function() { var request, err; @@ -83,11 +83,11 @@ describe('authorization', function() { }) .dispatch(); }); - + it('should not error', function() { expect(err).to.be.undefined; }); - + it('should add transaction', function() { expect(request.oauth2).to.be.an('object'); expect(request.oauth2.transactionID).to.be.a('string'); @@ -99,7 +99,7 @@ describe('authorization', function() { expect(request.oauth2.req.clientID).to.equal('1234'); expect(request.oauth2.req.redirectURI).to.equal('http://example.com/auth/callback'); }); - + it('should store transaction in session', function() { var tid = request.oauth2.transactionID; expect(request.session['authorize'][tid]).to.be.an('object'); @@ -111,7 +111,7 @@ describe('authorization', function() { expect(request.session['authorize'][tid].req.redirectURI).to.equal('http://example.com/auth/callback'); }); }); - + describe('handling a request for authorization with empty query', function() { var request, err; @@ -128,19 +128,19 @@ describe('authorization', function() { }) .dispatch(); }); - + it('should error', function() { expect(err).to.be.an.instanceOf(Error); expect(err.constructor.name).to.equal('AuthorizationError'); expect(err.message).to.equal('Missing required parameter: response_type'); expect(err.code).to.equal('invalid_request'); }); - + it('should not start transaction', function() { expect(request.oauth2).to.be.undefined; }); }); - + describe('handling a request for authorization with unsupported response type', function() { var request, err; @@ -157,19 +157,19 @@ describe('authorization', function() { }) .dispatch(); }); - + it('should error', function() { expect(err).to.be.an.instanceOf(Error); expect(err.constructor.name).to.equal('AuthorizationError'); expect(err.message).to.equal('Unsupported response type: foo'); expect(err.code).to.equal('unsupported_response_type'); }); - + it('should not start transaction', function() { expect(request.oauth2).to.be.undefined; }); }); - + describe('handling a request for authorization from unauthorized client', function() { var request, err; @@ -186,21 +186,21 @@ describe('authorization', function() { }) .dispatch(); }); - + it('should error', function() { expect(err).to.be.an.instanceOf(Error); expect(err.constructor.name).to.equal('AuthorizationError'); expect(err.message).to.equal('Invalid client'); expect(err.code).to.equal('invalid_client'); }); - + it('should start transaction', function() { expect(request.oauth2).to.be.an('object'); expect(request.oauth2.client).to.be.undefined; expect(request.oauth2.redirectURI).to.be.undefined; }); }); - + describe('handling a request for authorization from unauthorized client informed via redirect', function() { var request, err; @@ -217,21 +217,21 @@ describe('authorization', function() { }) .dispatch(); }); - + it('should error', function() { expect(err).to.be.an.instanceOf(Error); expect(err.constructor.name).to.equal('AuthorizationError'); expect(err.message).to.equal('Invalid client'); expect(err.code).to.equal('invalid_client'); }); - + it('should start transaction', function() { expect(request.oauth2).to.be.an('object'); expect(request.oauth2.client).to.be.undefined; // expect(request.oauth2.redirectURI).to.equal('http://example.com/auth/callback'); }); }); - + describe('encountering an error thrown while parsing request', function() { var request, err; @@ -248,17 +248,17 @@ describe('authorization', function() { }) .dispatch(); }); - + it('should error', function() { expect(err).to.be.an.instanceOf(Error); expect(err.message).to.equal('something went wrong while parsing authorization request'); }); - + it('should not start transaction', function() { expect(request.oauth2).to.be.undefined; }); }); - + describe('encountering an error while validating client', function() { var request, err; @@ -275,19 +275,19 @@ describe('authorization', function() { }) .dispatch(); }); - + it('should error', function() { expect(err).to.be.an.instanceOf(Error); expect(err.message).to.equal('something went wrong while validating client'); }); - + it('should start transaction', function() { expect(request.oauth2).to.be.an('object'); expect(request.oauth2.client).to.be.undefined; expect(request.oauth2.redirectURI).to.be.undefined; }); }); - + describe('encountering an error thrown while validating client', function() { var request, err; @@ -304,17 +304,17 @@ describe('authorization', function() { }) .dispatch(); }); - + it('should error', function() { expect(err).to.be.an.instanceOf(Error); expect(err.message).to.equal('something was thrown while validating client'); }); - + it('should not start transaction', function() { expect(request.oauth2).to.be.undefined; }); }); - + describe('encountering an error while serializing client', function() { var request, err; @@ -331,12 +331,12 @@ describe('authorization', function() { }) .dispatch(); }); - + it('should error', function() { expect(err).to.be.an.instanceOf(Error); expect(err.message).to.equal('something went wrong while serializing client'); }); - + it('should start transaction', function() { expect(request.oauth2).to.be.an('object'); expect(request.oauth2.client.id).to.equal('1235'); @@ -348,7 +348,7 @@ describe('authorization', function() { expect(request.oauth2.req.redirectURI).to.equal('http://example.com/auth/callback'); }); }); - + describe('handling a request for authorization without a session', function() { var request, err; @@ -364,17 +364,17 @@ describe('authorization', function() { }) .dispatch(); }); - + it('should error', function() { expect(err).to.be.an.instanceOf(Error); expect(err.message).to.equal('OAuth2orize requires session support. Did you forget app.use(express.session(...))?'); }); - + it('should not start transaction', function() { expect(request.oauth2).to.be.undefined; }); }); - + describe('validate with scope', function() { function validate(clientID, redirectURI, scope, done) { if (clientID == '1234' && redirectURI == 'http://example.com/auth/callback' && scope == 'write') { @@ -382,7 +382,7 @@ describe('authorization', function() { } return done(new Error('something went wrong while validating client')); } - + describe('handling a request for authorization', function() { var request, err; @@ -399,11 +399,11 @@ describe('authorization', function() { }) .dispatch(); }); - + it('should not error', function() { expect(err).to.be.undefined; }); - + it('should add transaction', function() { expect(request.oauth2).to.be.an('object'); expect(request.oauth2.transactionID).to.be.a('string'); @@ -416,7 +416,7 @@ describe('authorization', function() { expect(request.oauth2.req.redirectURI).to.equal('http://example.com/auth/callback'); expect(request.oauth2.req.scope).to.equal('write'); }); - + it('should store transaction in session', function() { var tid = request.oauth2.transactionID; expect(request.session['authorize'][tid]).to.be.an('object'); @@ -430,7 +430,7 @@ describe('authorization', function() { }); }); }); - + describe('validate with scope and type', function() { function validate(clientID, redirectURI, scope, type, done) { if (clientID == '1234' && redirectURI == 'http://example.com/auth/callback' && scope == 'write' && type == 'code') { @@ -438,7 +438,7 @@ describe('authorization', function() { } return done(new Error('something went wrong while validating client')); } - + describe('handling a request for authorization', function() { var request, err; @@ -455,11 +455,11 @@ describe('authorization', function() { }) .dispatch(); }); - + it('should not error', function() { expect(err).to.be.undefined; }); - + it('should add transaction', function() { expect(request.oauth2).to.be.an('object'); expect(request.oauth2.transactionID).to.be.a('string'); @@ -472,7 +472,7 @@ describe('authorization', function() { expect(request.oauth2.req.redirectURI).to.equal('http://example.com/auth/callback'); expect(request.oauth2.req.scope).to.equal('write'); }); - + it('should store transaction in session', function() { var tid = request.oauth2.transactionID; expect(request.session['authorize'][tid]).to.be.an('object'); @@ -486,7 +486,7 @@ describe('authorization', function() { }); }); }); - + describe('validate with authorization request', function() { function validate(areq, done) { if (areq.clientID == '1234' && areq.redirectURI == 'http://example.com/auth/callback') { @@ -494,7 +494,7 @@ describe('authorization', function() { } return done(new Error('something went wrong while validating client')); } - + describe('handling a request for authorization', function() { var request, err; @@ -511,11 +511,11 @@ describe('authorization', function() { }) .dispatch(); }); - + it('should not error', function() { expect(err).to.be.undefined; }); - + it('should add transaction', function() { expect(request.oauth2).to.be.an('object'); expect(request.oauth2.transactionID).to.be.a('string'); @@ -527,7 +527,7 @@ describe('authorization', function() { expect(request.oauth2.req.clientID).to.equal('1234'); expect(request.oauth2.req.redirectURI).to.equal('http://example.com/auth/callback'); }); - + it('should store transaction in session', function() { var tid = request.oauth2.transactionID; expect(request.session['authorize'][tid]).to.be.an('object'); @@ -540,7 +540,7 @@ describe('authorization', function() { }); }); }); - + describe('with id length option', function() { describe('handling a request for authorization', function() { var request, err; @@ -558,11 +558,11 @@ describe('authorization', function() { }) .dispatch(); }); - + it('should not error', function() { expect(err).to.be.undefined; }); - + it('should add transaction', function() { expect(request.oauth2).to.be.an('object'); expect(request.oauth2.transactionID).to.be.a('string'); @@ -574,7 +574,7 @@ describe('authorization', function() { expect(request.oauth2.req.clientID).to.equal('1234'); expect(request.oauth2.req.redirectURI).to.equal('http://example.com/auth/callback'); }); - + it('should store transaction in session', function() { var tid = request.oauth2.transactionID; expect(request.session['authorize'][tid]).to.be.an('object'); @@ -587,7 +587,7 @@ describe('authorization', function() { }); }); }); - + describe('with session key option', function() { describe('handling a request for authorization', function() { var request, err; @@ -605,11 +605,11 @@ describe('authorization', function() { }) .dispatch(); }); - + it('should not error', function() { expect(err).to.be.undefined; }); - + it('should add transaction', function() { expect(request.oauth2).to.be.an('object'); expect(request.oauth2.transactionID).to.be.a('string'); @@ -621,7 +621,7 @@ describe('authorization', function() { expect(request.oauth2.req.clientID).to.equal('1234'); expect(request.oauth2.req.redirectURI).to.equal('http://example.com/auth/callback'); }); - + it('should store transaction in session', function() { var tid = request.oauth2.transactionID; expect(request.session['oauth2z'][tid]).to.be.an('object'); @@ -634,20 +634,20 @@ describe('authorization', function() { }); }); }); - + describe('server without registered grants', function() { var server = new Server(); server.serializeClient(function(client, done) { return done(null, client.id); }); - + function validate(clientID, redirectURI, done) { if (clientID == '1234' && redirectURI == 'http://example.com/auth/callback') { return done(null, { id: '1234', name: 'Example' }, 'http://example.com/auth/callback'); } return done(new Error('something went wrong while validating client')); } - + describe('handling a request for authorization', function() { var request, err; @@ -664,18 +664,18 @@ describe('authorization', function() { }) .dispatch(); }); - + it('should error', function() { expect(err).to.be.an.instanceOf(Error); expect(err.constructor.name).to.equal('AuthorizationError'); expect(err.message).to.equal('Unsupported response type: code'); expect(err.code).to.equal('unsupported_response_type'); }); - + it('should not start transaction', function() { expect(request.oauth2).to.be.undefined; }); }); }); - + });