implemented verification of token signed elsewhere
This commit is contained in:
parent
43a61546d8
commit
5053963874
27
lib/apis.js
27
lib/apis.js
|
@ -291,16 +291,15 @@ module.exports.create = function (xconfx, apiFactories, apiDeps) {
|
|||
return;
|
||||
}
|
||||
|
||||
// every grant in the array must be present
|
||||
if (!grants.every(function (grant) {
|
||||
var scopes = grant.split(/\|/g);
|
||||
return scopes.some(function (scp) {
|
||||
return tokenScopes.some(function (s) {
|
||||
return scp === s;
|
||||
});
|
||||
// every grant in the array must be present, though some grants can be satisfied
|
||||
// by multiple scopes.
|
||||
var missing = grants.filter(function (grant) {
|
||||
return !grant.split('|').some(function (scp) {
|
||||
return tokenScopes.indexOf(scp) !== -1;
|
||||
});
|
||||
})) {
|
||||
res.send({ error: { message: "Token does not contain valid grants: '" + grants + "'", code: "E_NO_GRANTS" } });
|
||||
});
|
||||
if (missing.length) {
|
||||
res.send({ error: { message: "Token missing required grants: '" + missing.join(',') + "'", code: "E_NO_GRANTS" } });
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -308,11 +307,7 @@ module.exports.create = function (xconfx, apiFactories, apiDeps) {
|
|||
};
|
||||
};
|
||||
|
||||
var _getOauth3Controllers = pkgDeps.getOauth3Controllers = require('oauthcommon/example-oauthmodels').create(
|
||||
{ sqlite3Sock: xconfx.sqlite3Sock, ipcKey: xconfx.ipcKey }
|
||||
).getControllers;
|
||||
//require('oauthcommon').inject(packagedApi._getOauth3Controllers, packagedApi._api, pkgConf, pkgDeps);
|
||||
require('oauthcommon').inject(_getOauth3Controllers, myApp/*, pkgConf, pkgDeps*/);
|
||||
myApp.use('/', require('./oauth3').attachOauth3);
|
||||
|
||||
// TODO delete these caches when config changes
|
||||
var _stripe;
|
||||
|
@ -725,8 +720,8 @@ module.exports.create = function (xconfx, apiFactories, apiDeps) {
|
|||
// Canonical client names
|
||||
// example.com should use api.example.com/api for all requests
|
||||
// sub.example.com/api should resolve to sub.example.com
|
||||
// example.com/subpath/api should resolve to example.com#subapp
|
||||
// sub.example.com/subpath/api should resolve to sub.example.com#subapp
|
||||
// example.com/subapp/api should resolve to example.com#subapp
|
||||
// sub.example.com/subapp/api should resolve to sub.example.com#subapp
|
||||
var clientUrih = req.hostname.replace(/^api\./, '') + req.url.replace(/\/api\/.*/, '/').replace(/\/+/g, '#').replace(/#$/, '');
|
||||
var clientApiUri = req.hostname + req.url.replace(/\/api\/.*/, '/').replace(/\/$/, '');
|
||||
// Canonical package names
|
||||
|
|
|
@ -0,0 +1,201 @@
|
|||
'use strict';
|
||||
|
||||
var PromiseA = require('bluebird');
|
||||
|
||||
function extractAccessToken(req) {
|
||||
var token;
|
||||
var parts;
|
||||
var scheme;
|
||||
var credentials;
|
||||
|
||||
if (req.headers && req.headers.authorization) {
|
||||
// Works for all of Authorization: Bearer {{ token }}, Token {{ token }}, JWT {{ token }}
|
||||
parts = req.headers.authorization.split(' ');
|
||||
|
||||
if (parts.length !== 2) {
|
||||
return PromiseA.reject(new Error("malformed Authorization header"));
|
||||
}
|
||||
|
||||
scheme = parts[0];
|
||||
credentials = parts[1];
|
||||
|
||||
if (-1 !== ['token', 'bearer'].indexOf(scheme.toLowerCase())) {
|
||||
token = credentials;
|
||||
}
|
||||
}
|
||||
|
||||
if (req.body && req.body.access_token) {
|
||||
if (token) { PromiseA.reject(new Error("token exists in header and body")); }
|
||||
token = req.body.access_token;
|
||||
}
|
||||
|
||||
// TODO disallow query with req.method === 'GET'
|
||||
// NOTE: the case of DDNS on routers requires a GET and access_token
|
||||
// (cookies should be used for protected static assets)
|
||||
if (req.query && req.query.access_token) {
|
||||
if (token) { PromiseA.reject(new Error("token already exists in either header or body and also in query")); }
|
||||
token = req.query.access_token;
|
||||
}
|
||||
|
||||
/*
|
||||
err = new Error(challenge());
|
||||
err.code = 'E_BEARER_REALM';
|
||||
|
||||
if (!token) { return PromiseA.reject(err); }
|
||||
*/
|
||||
|
||||
return PromiseA.resolve(token);
|
||||
}
|
||||
|
||||
function verifyToken(token) {
|
||||
var jwt = require('jsonwebtoken');
|
||||
var decoded;
|
||||
try {
|
||||
decoded = jwt.decode(token, {complete: true});
|
||||
} catch (e) {}
|
||||
if (!decoded) {
|
||||
return PromiseA.reject({
|
||||
message: 'provided token not a JSON Web Token'
|
||||
, code: 'E_NOT_JWT'
|
||||
, url: 'https://oauth3.org/docs/errors#E_NOT_JWT'
|
||||
});
|
||||
}
|
||||
|
||||
var sub = decoded.payload.sub || decoded.payload.ppid || decoded.payload.appScopedId;
|
||||
if (!sub) {
|
||||
return PromiseA.reject({
|
||||
message: 'token missing sub'
|
||||
, code: 'E_MISSING_SUB'
|
||||
, url: 'https://oauth3.org/docs/errors#E_MISSING_SUB'
|
||||
});
|
||||
}
|
||||
var kid = decoded.header.kid || decoded.payload.kid;
|
||||
if (!kid) {
|
||||
return PromiseA.reject({
|
||||
message: 'token missing kid'
|
||||
, code: 'E_MISSING_KID'
|
||||
, url: 'https://oauth3.org/docs/errors#E_MISSING_KID'
|
||||
});
|
||||
}
|
||||
if (!decoded.payload.iss) {
|
||||
return PromiseA.reject({
|
||||
message: 'token missing iss'
|
||||
, code: 'E_MISSING_ISS'
|
||||
, url: 'https://oauth3.org/docs/errors#E_MISSING_ISS'
|
||||
});
|
||||
}
|
||||
|
||||
var OAUTH3 = require('oauth3.js');
|
||||
OAUTH3._hooks = require('oauth3.js/oauth3.node.storage.js');
|
||||
return OAUTH3.discover(decoded.payload.iss).then(function (directives) {
|
||||
var args = (directives || {}).retrieve_jwk;
|
||||
if (typeof args === 'string') {
|
||||
args = { url: args, method: 'GET' };
|
||||
}
|
||||
if (typeof (args || {}).url !== 'string') {
|
||||
return PromiseA.reject({
|
||||
message: 'token issuer does not support retrieving JWKs'
|
||||
, code: 'E_INVALID_ISS'
|
||||
, url: 'https://oauth3.org/docs/errors#E_INVALID_ISS'
|
||||
});
|
||||
}
|
||||
|
||||
var params = {
|
||||
sub: sub
|
||||
, kid: kid
|
||||
};
|
||||
var url = args.url;
|
||||
var body;
|
||||
Object.keys(params).forEach(function (key) {
|
||||
if (url.indexOf(':'+key) !== -1) {
|
||||
url = url.replace(':'+key, params[key]);
|
||||
delete params[key];
|
||||
}
|
||||
});
|
||||
if (Object.keys(params).length > 0) {
|
||||
if ('GET' === (args.method || 'GET').toUpperCase()) {
|
||||
url += '?' + OAUTH3.query.stringify(params);
|
||||
} else {
|
||||
body = params;
|
||||
}
|
||||
}
|
||||
|
||||
return OAUTH3.request({
|
||||
url: OAUTH3.url.resolve(directives.api, url)
|
||||
, method: args.method
|
||||
, data: body
|
||||
});
|
||||
}, function (err) {
|
||||
return PromiseA.reject({
|
||||
message: 'token issuer is not a valid OAuth3 provider'
|
||||
, code: 'E_INVALID_ISS'
|
||||
, url: 'https://oauth3.org/docs/errors#E_INVALID_ISS'
|
||||
, subErr: err.toString()
|
||||
});
|
||||
}).then(function (res) {
|
||||
if (res.data.error) {
|
||||
return PromiseA.reject(res.data.error);
|
||||
}
|
||||
var opts = {};
|
||||
if (Array.isArray(res.data.alg)) {
|
||||
opts.algorithms = res.data.alg;
|
||||
} else if (typeof res.data.alg === 'string') {
|
||||
opts.algorithms = [res.data.alg];
|
||||
}
|
||||
|
||||
try {
|
||||
return jwt.verify(token, require('jwk-to-pem')(res.data), opts);
|
||||
} catch (err) {
|
||||
return PromiseA.reject({
|
||||
message: 'token verification failed'
|
||||
, code: 'E_INVALID_TOKEN'
|
||||
, url: 'https://oauth3.org/docs/errors#E_INVALID_TOKEN'
|
||||
, subErr: err.toString()
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function attachOauth3(req, res, next) {
|
||||
req.oauth3 = {};
|
||||
|
||||
extractAccessToken(req).then(function (token) {
|
||||
if (!token) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var decoded;
|
||||
try {
|
||||
decoded = require('jsonwebtoken').decode(token);
|
||||
} catch (e) {}
|
||||
if (!decoded) {
|
||||
return PromiseA.reject({
|
||||
message: 'provided token not a JSON Web Token'
|
||||
, code: 'E_NOT_JWT'
|
||||
, url: 'https://oauth3.org/docs/errors#E_NOT_JWT'
|
||||
});
|
||||
}
|
||||
var ppid = decoded.sub || decoded.ppid || decoded.appScopedId;
|
||||
|
||||
req.oauth3.encodedToken = token;
|
||||
req.oauth3.token = decoded;
|
||||
req.oauth3.ppid = ppid;
|
||||
|
||||
req.oauth3.verifyAsync = function () {
|
||||
return verifyToken(token);
|
||||
};
|
||||
|
||||
req.oauth3.rescope = function () {
|
||||
// TODO: this function is supposed to convert PPIDs of different parties to some account
|
||||
// ID that allows application to keep track of permisions and what-not.
|
||||
return PromiseA.resolve(ppid);
|
||||
};
|
||||
}).then(function () {
|
||||
next();
|
||||
}, function (err) {
|
||||
res.send(err);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports.attachOauth3 = attachOauth3;
|
||||
module.exports.verifyToken = verifyToken;
|
|
@ -55,19 +55,7 @@ function getApi(conf, pkgConf, pkgDeps, packagedApi) {
|
|||
packagedApi._api = require('express-lazy')();
|
||||
packagedApi._api_app = myApp;
|
||||
|
||||
//require('./oauth3-auth').inject(conf, packagedApi._api, pkgConf, pkgDeps);
|
||||
pkgDeps.getOauth3Controllers =
|
||||
packagedApi._getOauth3Controllers = require('oauthcommon/example-oauthmodels').create(conf).getControllers;
|
||||
require('oauthcommon').inject(packagedApi._getOauth3Controllers, packagedApi._api, pkgConf, pkgDeps);
|
||||
|
||||
// DEBUG
|
||||
//
|
||||
/*
|
||||
packagedApi._api.use('/', function (req, res, next) {
|
||||
console.log('[DEBUG pkgApiApp]', req.method, req.hostname, req.url);
|
||||
next();
|
||||
});
|
||||
//*/
|
||||
packagedApi._api.use('/', require('./oauth3').attachOauth3);
|
||||
|
||||
// TODO fix backwards compat
|
||||
|
||||
|
|
|
@ -52,6 +52,8 @@
|
|||
"express": "4.x",
|
||||
"express-lazy": "^1.1.1",
|
||||
"express-session": "^1.11.3",
|
||||
"jsonwebtoken": "^7.4.1",
|
||||
"jwk-to-pem": "^1.2.6",
|
||||
"mailchimp-api-v3": "^1.7.0",
|
||||
"mandrill-api": "^1.0.45",
|
||||
"masterquest-sqlite3": "git+https://git.daplie.com/node/masterquest-sqlite3.git",
|
||||
|
@ -59,7 +61,7 @@
|
|||
"multiparty": "^4.1.3",
|
||||
"nodemailer": "^1.4.0",
|
||||
"nodemailer-mailgun-transport": "1.x",
|
||||
"oauthcommon": "git+https://git.daplie.com/node/oauthcommon.git",
|
||||
"oauth3.js": "git+https://git.daplie.com/OAuth3/oauth3.js.git",
|
||||
"serve-static": "1.x",
|
||||
"sqlite3-cluster": "git+https://git.daplie.com/coolaj86/sqlite3-cluster.git#v2",
|
||||
"stripe": "^4.22.0",
|
||||
|
|
Loading…
Reference in New Issue