implemented creating tokens for other apps

requires user to already have a token from/for the issuer
This commit is contained in:
tigerbot 2017-07-19 17:42:06 -06:00
parent ef43b56f1f
commit c03f380e8d
3 changed files with 83 additions and 7 deletions

View File

@ -4,6 +4,12 @@ var apiname = 'issuer_oauth3_org';
var baseFields = [ 'createdAt', 'updatedAt', 'deletedAt' ];
module.exports = [
{
tablename: apiname + '_private_keys',
idname: 'id',
unique: ['id'],
indices: baseFields.concat([ 'kty', 'kid' ]),
},
{
tablename: apiname + '_jwks',
idname: 'id',

View File

@ -11,6 +11,9 @@
"url": "git+https://git.daplie.com/Oauth3/issuer_oauth3.org.git"
},
"dependencies": {
"bluebird": "^3.5.0"
"bluebird": "^3.5.0",
"elliptic": "^6.4.0",
"jsonwebtoken": "^7.4.1",
"jwk-to-pem": "^1.2.6"
}
}

79
rest.js
View File

@ -3,9 +3,14 @@
var PromiseA = require('bluebird');
var crypto = require('crypto');
function makeB64UrlSafe(b64) {
return b64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=*$/, '');
}
module.exports.create = function (bigconf, deps, app) {
var Jwks = { restful: {} };
var Grants = { restful: {} };
var Tokens = { restful: {} };
// This tablename is based on the tablename found in the objects in model.js.
// Instead of the snake_case the name with be UpperCammelCase, converted by masterquest-sqlite3.
@ -27,11 +32,9 @@ module.exports.create = function (bigconf, deps, app) {
// catch the error thrown when a token isn't provided and verifyAsync isn't a function.
return req.oauth3.verifyAsync();
}).then(function (token) {
// Now that we've confirmed the token is valid we also need to make sure the authorized party
// is us.
// TODO: For the time being the only verify-able tokens are the ones we issued, but this
// will not always be the case. We will need a better way to verify the authorized party.
if (token.iss !== token.azp || token.iss !== token.aud) {
// Now that we've confirmed the token is valid we also need to make sure the issuer, audience,
// and authorized party are all us, because no other app should be managing user identity.
if (token.iss !== req.experienceId || token.aud !== token.iss || token.azp !== token.iss) {
throw new Error("token does not allow access to requested resource");
}
@ -85,7 +88,7 @@ module.exports.create = function (bigconf, deps, app) {
// as the replacer argument the keys are always in the order they appeared in the array.
var jwkStr = JSON.stringify(jwk, keys);
var hash = crypto.createHash('sha256').update(jwkStr).digest('base64');
return PromiseA.resolve(hash.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+/g, ''));
return PromiseA.resolve(makeB64UrlSafe(hash));
};
Jwks.restful.get = function (req, res) {
@ -207,6 +210,67 @@ module.exports.create = function (bigconf, deps, app) {
app.handlePromise(req, res, promise, '[issuer@oauth3.org] save grants');
};
Tokens.getPrivKey = function (store, experienceId) {
return store.IssuerOauth3OrgPrivateKeys.get(experienceId).then(function (jwk) {
if (jwk) {
return jwk;
}
var keyPair = require('elliptic').ec('p256').genKeyPair();
jwk = {
kty: 'EC',
crv: 'P-256',
alg: 'ES256',
kid: experienceId,
x: makeB64UrlSafe(keyPair.getPublic().getX().toArrayLike(Buffer).toString('base64')),
y: makeB64UrlSafe(keyPair.getPublic().getY().toArrayLike(Buffer).toString('base64')),
d: makeB64UrlSafe(keyPair.getPrivate().toArrayLike(Buffer).toString('base64')),
};
return store.IssuerOauth3OrgPrivateKeys.upsert(experienceId, jwk).then(function () {
return jwk;
});
});
};
Tokens.restful.create = function (req, res) {
var store;
var promise = req.getSiteStore().then(function (_store) {
store = _store;
return store.IssuerOauth3OrgGrants.get(req.params.sub+'/'+req.params.azp);
}).then(function (grant) {
if (!grant) {
throw new Error("'"+req.params.azp+"' not given any grants from '"+req.params.sub+"'");
}
return Tokens.getPrivKey(store, req.experienceId).then(function (jwk) {
var pem = require('jwk-to-pem')(jwk, { private: true });
var payload = {
// standard
iss: req.experienceId, // https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-32#section-4.1.1
aud: req.params.aud, // https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-32#section-4.1.3
azp: grant.azp,
sub: grant.azpSub, // https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-32#section-4.1.2
// extended
scp: grant.scope,
};
var opts = {
algorithm: jwk.alg,
header: {
kid: jwk.kid
}
};
return {
access_token: require('jsonwebtoken').sign(payload, pem, Object.assign({expiresIn: '1d'}, opts)),
refresh_token: require('jsonwebtoken').sign(payload, pem, opts),
scope: grant.scope,
};
});
});
app.handlePromise(req, res, promise, '[issuer@oauth3.org] create tokens');
};
app.get( '/jwks/:sub/:kid.json', Jwks.restful.get);
// Everything but getting keys is only for the issuer
app.use( '/jwks/:sub', authorizeIssuer, attachSiteStore.bind(null, 'IssuerOauth3OrgJwks'));
@ -218,5 +282,8 @@ module.exports.create = function (bigconf, deps, app) {
app.get( '/grants/:sub/:azp', Grants.restful.getOne);
app.post( '/grants/:sub/:azp', Grants.restful.saveNew);
app.use( '/access_token/:sub', authorizeIssuer);
app.post( '/access_token/:sub/:aud/:azp', Tokens.restful.create);
app.use(detachSiteStore);
};