implemented creating tokens for other apps
requires user to already have a token from/for the issuer
This commit is contained in:
parent
ef43b56f1f
commit
c03f380e8d
@ -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',
|
||||
|
@ -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
79
rest.js
@ -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);
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user