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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// every grant in the array must be present
|
// every grant in the array must be present, though some grants can be satisfied
|
||||||
if (!grants.every(function (grant) {
|
// by multiple scopes.
|
||||||
var scopes = grant.split(/\|/g);
|
var missing = grants.filter(function (grant) {
|
||||||
return scopes.some(function (scp) {
|
return !grant.split('|').some(function (scp) {
|
||||||
return tokenScopes.some(function (s) {
|
return tokenScopes.indexOf(scp) !== -1;
|
||||||
return scp === s;
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
})) {
|
});
|
||||||
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -308,11 +307,7 @@ module.exports.create = function (xconfx, apiFactories, apiDeps) {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
var _getOauth3Controllers = pkgDeps.getOauth3Controllers = require('oauthcommon/example-oauthmodels').create(
|
myApp.use('/', require('./oauth3').attachOauth3);
|
||||||
{ sqlite3Sock: xconfx.sqlite3Sock, ipcKey: xconfx.ipcKey }
|
|
||||||
).getControllers;
|
|
||||||
//require('oauthcommon').inject(packagedApi._getOauth3Controllers, packagedApi._api, pkgConf, pkgDeps);
|
|
||||||
require('oauthcommon').inject(_getOauth3Controllers, myApp/*, pkgConf, pkgDeps*/);
|
|
||||||
|
|
||||||
// TODO delete these caches when config changes
|
// TODO delete these caches when config changes
|
||||||
var _stripe;
|
var _stripe;
|
||||||
|
@ -725,8 +720,8 @@ module.exports.create = function (xconfx, apiFactories, apiDeps) {
|
||||||
// Canonical client names
|
// Canonical client names
|
||||||
// example.com should use api.example.com/api for all requests
|
// example.com should use api.example.com/api for all requests
|
||||||
// sub.example.com/api should resolve to sub.example.com
|
// sub.example.com/api should resolve to sub.example.com
|
||||||
// example.com/subpath/api should resolve to example.com#subapp
|
// example.com/subapp/api should resolve to example.com#subapp
|
||||||
// sub.example.com/subpath/api should resolve to sub.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 clientUrih = req.hostname.replace(/^api\./, '') + req.url.replace(/\/api\/.*/, '/').replace(/\/+/g, '#').replace(/#$/, '');
|
||||||
var clientApiUri = req.hostname + req.url.replace(/\/api\/.*/, '/').replace(/\/$/, '');
|
var clientApiUri = req.hostname + req.url.replace(/\/api\/.*/, '/').replace(/\/$/, '');
|
||||||
// Canonical package names
|
// 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 = require('express-lazy')();
|
||||||
packagedApi._api_app = myApp;
|
packagedApi._api_app = myApp;
|
||||||
|
|
||||||
//require('./oauth3-auth').inject(conf, packagedApi._api, pkgConf, pkgDeps);
|
packagedApi._api.use('/', require('./oauth3').attachOauth3);
|
||||||
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();
|
|
||||||
});
|
|
||||||
//*/
|
|
||||||
|
|
||||||
// TODO fix backwards compat
|
// TODO fix backwards compat
|
||||||
|
|
||||||
|
|
|
@ -52,6 +52,8 @@
|
||||||
"express": "4.x",
|
"express": "4.x",
|
||||||
"express-lazy": "^1.1.1",
|
"express-lazy": "^1.1.1",
|
||||||
"express-session": "^1.11.3",
|
"express-session": "^1.11.3",
|
||||||
|
"jsonwebtoken": "^7.4.1",
|
||||||
|
"jwk-to-pem": "^1.2.6",
|
||||||
"mailchimp-api-v3": "^1.7.0",
|
"mailchimp-api-v3": "^1.7.0",
|
||||||
"mandrill-api": "^1.0.45",
|
"mandrill-api": "^1.0.45",
|
||||||
"masterquest-sqlite3": "git+https://git.daplie.com/node/masterquest-sqlite3.git",
|
"masterquest-sqlite3": "git+https://git.daplie.com/node/masterquest-sqlite3.git",
|
||||||
|
@ -59,7 +61,7 @@
|
||||||
"multiparty": "^4.1.3",
|
"multiparty": "^4.1.3",
|
||||||
"nodemailer": "^1.4.0",
|
"nodemailer": "^1.4.0",
|
||||||
"nodemailer-mailgun-transport": "1.x",
|
"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",
|
"serve-static": "1.x",
|
||||||
"sqlite3-cluster": "git+https://git.daplie.com/coolaj86/sqlite3-cluster.git#v2",
|
"sqlite3-cluster": "git+https://git.daplie.com/coolaj86/sqlite3-cluster.git#v2",
|
||||||
"stripe": "^4.22.0",
|
"stripe": "^4.22.0",
|
||||||
|
|
Loading…
Reference in New Issue