oidc-provider
Advanced tools
Comparing version 0.6.0 to 0.7.0
Following semver, 1.0.0 will mark the first API stable release and commence of this file, | ||
until then please use the compare views of github for reference. | ||
- https://github.com/panva/node-oidc-provider/compare/v0.6.0...v0.7.0 | ||
- all things `authentication` renamed to `authorization` | ||
- https://github.com/panva/node-oidc-provider/compare/v0.5.0...v0.6.0 | ||
@@ -5,0 +7,0 @@ - https://github.com/panva/node-oidc-provider/compare/v0.4.0...v0.5.0 |
'use strict'; | ||
module.exports = function discoveryAction(provider) { | ||
const config = provider.configuration; | ||
const config = provider.configuration(); | ||
@@ -9,3 +9,3 @@ return function * renderConfiguration(next) { | ||
acr_values_supported: config.acrValues, | ||
authorization_endpoint: this.oidc.urlFor('authentication'), | ||
authorization_endpoint: this.oidc.urlFor('authorization'), | ||
claims_parameter_supported: !!config.features.claimsParameter, | ||
@@ -12,0 +12,0 @@ claims_supported: config.claimsSupported, |
@@ -10,4 +10,4 @@ 'use strict'; | ||
const dupesMiddleware = require('../middlewares/check_dupes'); | ||
const bodyMiddleware = require('../middlewares/selective_body'); | ||
const rejectDupes = require('../middlewares/check_dupes'); | ||
const bodyParser = require('../middlewares/conditional_body'); | ||
const paramsMiddleware = require('../middlewares/get_params'); | ||
@@ -22,3 +22,3 @@ | ||
const getParams = paramsMiddleware(PARAM_LIST); | ||
const body = bodyMiddleware('application/x-www-form-urlencoded'); | ||
const parseBody = bodyParser('application/x-www-form-urlencoded'); | ||
@@ -28,3 +28,3 @@ module.exports = function endSessionAction(provider) { | ||
// Validate: client_id param | ||
const client = yield provider.Client.find(clientId); | ||
const client = yield provider.get('Client').find(clientId); | ||
@@ -39,13 +39,7 @@ this.assert(client, | ||
function * parseBody(next) { | ||
if (this.method === 'POST') { | ||
yield body.call(this, next); | ||
} else { | ||
yield next; | ||
} | ||
}, | ||
parseBody, | ||
getParams, | ||
dupesMiddleware, | ||
rejectDupes, | ||
@@ -72,3 +66,3 @@ function * validateIdTokenHintPresence(next) { | ||
client = yield loadClient.call(this, clientId); | ||
yield provider.IdToken.validate(params.id_token_hint, client); | ||
yield provider.get('IdToken').validate(params.id_token_hint, client); | ||
} catch (err) { | ||
@@ -75,0 +69,0 @@ this.throw(new errors.InvalidRequestError( |
@@ -14,3 +14,3 @@ 'use strict'; | ||
module.exports = function introspectionAction(provider) { | ||
const Claims = mask(provider.configuration); | ||
const Claims = mask(provider.configuration()); | ||
@@ -36,3 +36,3 @@ return compose([ | ||
try { | ||
payload = provider.OAuthToken.decode(params.token).decoded; | ||
payload = provider.get('OAuthToken').decode(params.token).decoded; | ||
@@ -50,3 +50,3 @@ Object.assign(this.body, { | ||
this.body.token_type = 'access_token'; | ||
token = yield provider.AccessToken.find(params.token, { | ||
token = yield provider.get('AccessToken').find(params.token, { | ||
ignoreExpiration: true, | ||
@@ -58,3 +58,3 @@ }); | ||
this.body.token_type = 'client_credentials'; | ||
token = yield provider.ClientCredentials.find(params.token, { | ||
token = yield provider.get('ClientCredentials').find(params.token, { | ||
ignoreExpiration: true, | ||
@@ -66,3 +66,3 @@ }); | ||
this.body.token_type = 'refresh_token'; | ||
token = yield provider.RefreshToken.find(params.token, { | ||
token = yield provider.get('RefreshToken').find(params.token, { | ||
ignoreExpiration: true, | ||
@@ -69,0 +69,0 @@ }); |
@@ -9,7 +9,6 @@ 'use strict'; | ||
const bodyMiddleware = require('../middlewares/selective_body'); | ||
const getBearer = require('../middlewares/get_bearer'); | ||
const bodyParser = require('../middlewares/selective_body'); | ||
const errors = require('../helpers/errors'); | ||
const body = bodyMiddleware('application/json'); | ||
const parseBody = bodyParser('application/json'); | ||
@@ -19,7 +18,7 @@ module.exports = function registrationAction(provider) { | ||
post: compose([ | ||
body, | ||
parseBody, | ||
function * registrationResponse() { | ||
const params = {}; | ||
const properties = {}; | ||
Object.assign(params, this.request.body, { | ||
Object.assign(properties, this.request.body, { | ||
client_id: uuid.v4(), | ||
@@ -31,16 +30,13 @@ client_secret: crypto.randomBytes(48).toString('base64'), | ||
const client = yield provider.addClient(params); | ||
const client = yield provider.addClient(properties); | ||
const dumpable = _.mapKeys(client, (value, key) => _.snakeCase(key)); | ||
yield provider.get('Client').adapter.upsert(client.clientId, dumpable); | ||
yield provider.Client.adapter.upsert(client.clientId, dumpable); | ||
const response = Object.assign({ | ||
registration_client_uri: this.oidc.urlFor('registration_client', { | ||
clientId: params.client_id, | ||
clientId: properties.client_id, | ||
}), | ||
}, _.mapKeys(client, (value, key) => _.snakeCase(key))); | ||
}, dumpable); | ||
this.body = response; | ||
this.status = 201; | ||
@@ -52,9 +48,6 @@ }, | ||
getBearer, | ||
function * validateAccessToken(next) { | ||
const client = yield provider.Client.find(this.params.clientId); | ||
const client = yield provider.get('Client').find(this.params.clientId); | ||
this.assert(client, | ||
new errors.InvalidClientError()); | ||
this.assert(client, new errors.InvalidClientError()); | ||
@@ -68,3 +61,2 @@ const valid = bufferEqualsConstant( | ||
this.assert(valid, new errors.InvalidTokenError()); | ||
this.oidc.client = client; | ||
@@ -76,8 +68,6 @@ | ||
function * clientResponse() { | ||
this.body = _.mapKeys(this.oidc.client, (value, key) => _.snakeCase(key)); | ||
this.body.registration_client_uri = | ||
this.oidc.urlFor('registration_client', { | ||
clientId: this.params.clientId, | ||
}); | ||
this.body = this.oidc.client.metadata(); | ||
this.body.registration_client_uri = this.oidc.urlFor('registration_client', { | ||
clientId: this.params.clientId, | ||
}); | ||
}, | ||
@@ -84,0 +74,0 @@ ]), |
@@ -32,3 +32,3 @@ 'use strict'; | ||
try { | ||
payload = provider.OAuthToken.decode(params.token).decoded; | ||
payload = provider.get('OAuthToken').decode(params.token).decoded; | ||
} catch (err) { | ||
@@ -41,3 +41,3 @@ return; | ||
try { | ||
const token = yield provider.AccessToken.find(params.token); | ||
const token = yield provider.get('AccessToken').find(params.token); | ||
yield token.destroy(); | ||
@@ -51,3 +51,3 @@ } catch (err) { | ||
try { | ||
const token = yield provider.ClientCredentials.find(params.token); | ||
const token = yield provider.get('ClientCredentials').find(params.token); | ||
yield token.destroy(); | ||
@@ -61,3 +61,3 @@ } catch (err) { | ||
try { | ||
const token = yield provider.RefreshToken.find(params.token); | ||
const token = yield provider.get('RefreshToken').find(params.token); | ||
yield token.destroy(); | ||
@@ -64,0 +64,0 @@ } catch (err) { |
@@ -25,3 +25,3 @@ 'use strict'; | ||
const handlers = {}; | ||
const grantTypes = provider.configuration.grantTypes; | ||
const grantTypes = provider.configuration('grantTypes'); | ||
@@ -32,3 +32,3 @@ if (grantTypes.indexOf('authorization_code') !== -1) { | ||
const code = yield provider.AuthorizationCode.find(this.oidc.params.code, { | ||
const code = yield provider.get('AuthorizationCode').find(this.oidc.params.code, { | ||
ignoreExpiration: true, | ||
@@ -60,3 +60,3 @@ }); | ||
const account = yield provider.Account.findById(code.accountId); | ||
const account = yield provider.get('Account').findById(code.accountId); | ||
@@ -67,4 +67,4 @@ this.assert(account, | ||
)); | ||
const at = new provider.AccessToken({ | ||
const AccessToken = provider.get('AccessToken'); | ||
const at = new AccessToken({ | ||
accountId: account.accountId, | ||
@@ -79,11 +79,12 @@ claims: code.claims, | ||
const tokenType = 'Bearer'; | ||
const expiresIn = provider.AccessToken.expiresIn; | ||
const expiresIn = AccessToken.expiresIn; | ||
let refreshToken; | ||
const clientAllowed = this.oidc.client.grantTypes.indexOf('refresh_token') !== -1; | ||
const grantAllowed = provider.configuration.features.refreshToken || | ||
const grantAllowed = provider.configuration('features.refreshToken') || | ||
code.scope.split(' ').indexOf('offline_access') !== -1; | ||
if (clientAllowed && grantAllowed) { | ||
const rt = new provider.RefreshToken({ | ||
const RefreshToken = provider.get('RefreshToken'); | ||
const rt = new RefreshToken({ | ||
accountId: account.accountId, | ||
@@ -101,3 +102,4 @@ acr: code.acr, | ||
const token = new provider.IdToken(Object.assign({}, account.claims(), { | ||
const IdToken = provider.get('IdToken'); | ||
const token = new IdToken(Object.assign({}, account.claims(), { | ||
acr: code.acr, | ||
@@ -129,3 +131,4 @@ auth_time: code.authTime, | ||
handlers.client_credentials = function * clientCredentialsResponse(next) { | ||
const at = new provider.ClientCredentials({ | ||
const ClientCredentials = provider.get('ClientCredentials'); | ||
const at = new ClientCredentials({ | ||
clientId: this.oidc.client.clientId, | ||
@@ -137,3 +140,3 @@ scope: this.oidc.params.scope, | ||
const tokenType = 'Bearer'; | ||
const expiresIn = provider.ClientCredentials.expiresIn; | ||
const expiresIn = ClientCredentials.expiresIn; | ||
@@ -154,3 +157,8 @@ this.body = { | ||
const refreshToken = yield provider.RefreshToken.find( | ||
const RefreshToken = provider.get('RefreshToken'); | ||
const Account = provider.get('Account'); | ||
const AccessToken = provider.get('AccessToken'); | ||
const IdToken = provider.get('IdToken'); | ||
const refreshToken = yield RefreshToken.find( | ||
this.oidc.params.refresh_token, { | ||
@@ -179,3 +187,3 @@ ignoreExpiration: true, | ||
const account = yield provider.Account.findById(refreshToken.accountId); | ||
const account = yield Account.findById(refreshToken.accountId); | ||
@@ -186,3 +194,3 @@ this.assert(account, | ||
const at = new provider.AccessToken({ | ||
const at = new AccessToken({ | ||
accountId: account.accountId, | ||
@@ -197,5 +205,5 @@ claims: refreshToken.claims, | ||
const tokenType = 'Bearer'; | ||
const expiresIn = provider.AccessToken.expiresIn; | ||
const expiresIn = AccessToken.expiresIn; | ||
const token = new provider.IdToken(Object.assign({}, account.claims(), { | ||
const token = new IdToken(Object.assign({}, account.claims(), { | ||
acr: refreshToken.acr, | ||
@@ -232,3 +240,3 @@ auth_time: refreshToken.authTime, | ||
const supported = provider.configuration.grantTypes; | ||
const supported = provider.configuration('grantTypes'); | ||
const value = supported.indexOf(this.oidc.params.grant_type) !== -1; | ||
@@ -235,0 +243,0 @@ |
@@ -9,5 +9,4 @@ 'use strict'; | ||
const body = require('../middlewares/selective_body'); | ||
const dupes = require('../middlewares/check_dupes'); | ||
const getBearer = require('../middlewares/get_bearer'); | ||
const bodyParser = require('../middlewares/conditional_body'); | ||
const rejectDupes = require('../middlewares/check_dupes'); | ||
const params = require('../middlewares/get_params'); | ||
@@ -21,7 +20,7 @@ const errorHandler = require('../middlewares/api_error_handler'); | ||
const bodyMiddleware = body('application/x-www-form-urlencoded'); | ||
const parseBody = bodyParser('application/x-www-form-urlencoded'); | ||
const getParams = params(PARAM_LIST); | ||
module.exports = function userinfoAction(provider) { | ||
const Claims = getMask(provider.configuration); | ||
const Claims = getMask(provider.configuration()); | ||
@@ -46,18 +45,10 @@ return compose([ | ||
function * parseBody(next) { | ||
if (this.method === 'POST') { | ||
yield bodyMiddleware.call(this, next); | ||
} else { | ||
yield next; | ||
} | ||
}, | ||
parseBody, | ||
getParams, | ||
dupes, | ||
rejectDupes, | ||
getBearer, | ||
function * validateBearer(next) { | ||
const accessToken = yield provider.AccessToken.find(this.oidc.bearer); | ||
const accessToken = yield provider.get('AccessToken').find(this.oidc.bearer); | ||
this.assert(accessToken, | ||
@@ -85,3 +76,3 @@ new errors.InvalidTokenError()); | ||
function * loadClient(next) { | ||
const client = yield provider.Client.find(this.oidc.accessToken.clientId); | ||
const client = yield provider.get('Client').find(this.oidc.accessToken.clientId); | ||
@@ -97,3 +88,3 @@ this.assert(client, | ||
function * loadAccount(next) { | ||
const account = yield provider.Account.findById(this.oidc.accessToken.accountId); | ||
const account = yield provider.get('Account').findById(this.oidc.accessToken.accountId); | ||
@@ -113,3 +104,4 @@ this.assert(account, new errors.InvalidTokenError()); | ||
if (client.userinfoSignedResponseAlg || client.userinfoEncryptedResponseAlg) { | ||
const token = new provider.IdToken(this.oidc.account.claims(), client.sectorIdentifier); | ||
const IdToken = provider.get('IdToken'); | ||
const token = new IdToken(this.oidc.account.claims(), client.sectorIdentifier); | ||
@@ -116,0 +108,0 @@ token.scope = scope; |
'use strict'; | ||
const LRU = require('lru-cache'); | ||
const storage = new LRU({}); | ||
const client = new LRU({}); | ||
class MemoryAdapter { | ||
@@ -22,5 +21,5 @@ constructor(name) { | ||
const key = this.key(id); | ||
const grantId = client.get(key) && client.get(key).grantId; | ||
const grantId = storage.get(key) && storage.get(key).grantId; | ||
client.del(key); | ||
storage.del(key); | ||
@@ -30,3 +29,3 @@ if (grantId) { | ||
client.get(grantKey).forEach(token => client.del(token)); | ||
storage.get(grantKey).forEach(token => storage.del(token)); | ||
} | ||
@@ -38,3 +37,3 @@ | ||
consume(id) { | ||
client.get(this.key(id)).consumed = Date.now() / 1000 | 0; | ||
storage.get(this.key(id)).consumed = Date.now() / 1000 | 0; | ||
return Promise.resolve(); | ||
@@ -44,3 +43,3 @@ } | ||
find(id) { | ||
return Promise.resolve(client.get(this.key(id))); | ||
return Promise.resolve(storage.get(this.key(id))); | ||
} | ||
@@ -54,5 +53,5 @@ | ||
const grantKey = this.grantKey(grantId); | ||
const grant = client.get(grantKey); | ||
const grant = storage.get(grantKey); | ||
if (!grant) { | ||
client.set(grantKey, [key]); | ||
storage.set(grantKey, [key]); | ||
} else { | ||
@@ -63,3 +62,3 @@ grant.push(key); | ||
client.set(key, payload, expiresIn * 1000); | ||
storage.set(key, payload, expiresIn * 1000); | ||
@@ -66,0 +65,0 @@ return Promise.resolve(); |
'use strict'; | ||
const _ = require('lodash'); | ||
const defaults = require('./defaults'); | ||
const MemoryAdapter = require('../adapters/memory_adapter'); | ||
const ConfigurationSchema = require('./configuration_schema'); | ||
class Configuration { | ||
constructor(config) { | ||
_.mergeWith(this, defaults, config, (objValue, srcValue) => { | ||
if (_.isArray(objValue)) { | ||
return srcValue; | ||
const schema = new ConfigurationSchema(config); | ||
Object.assign(this, schema); | ||
this.subjectTypes.forEach((type) => { | ||
/* istanbul ignore if */ | ||
if (['public', 'pairwise'].indexOf(type) === -1) { | ||
throw new Error('only public and pairwise subjectTypes are supported'); | ||
} | ||
return undefined; | ||
}); | ||
const encryptionEnc = [ | ||
'A128CBC-HS256', | ||
'A128GCM', | ||
'A192CBC-HS384', | ||
'A192GCM', | ||
'A256CBC-HS512', | ||
'A256GCM', | ||
]; | ||
const secretSig = [ | ||
'none', | ||
'HS256', | ||
'HS384', | ||
'HS512', | ||
]; | ||
const fullSig = [ | ||
'none', | ||
'HS256', | ||
'HS384', | ||
'HS512', | ||
'RS256', | ||
'RS384', | ||
'RS512', | ||
'PS256', | ||
'PS384', | ||
'PS512', | ||
'ES256', | ||
'ES384', | ||
'ES512', | ||
]; | ||
const fullEncAlg = [ | ||
'RSA-OAEP', | ||
'RSA-OAEP-256', | ||
'RSA1_5', | ||
'ECDH-ES', | ||
'ECDH-ES+A128KW', | ||
'ECDH-ES+A192KW', | ||
'ECDH-ES+A256KW', | ||
]; | ||
this.idTokenEncryptionAlgValues = fullEncAlg; | ||
this.idTokenEncryptionEncValues = this.features.encryption ? encryptionEnc : []; | ||
this.idTokenSigningAlgValues = secretSig; | ||
this.requestObjectEncryptionAlgValues = []; | ||
this.requestObjectEncryptionEncValues = this.features.encryption ? encryptionEnc : []; | ||
this.requestObjectSigningAlgValues = fullSig; | ||
this.tokenEndpointAuthSigningAlgValues = _.without(fullSig, 'none'); | ||
this.userinfoEncryptionAlgValues = fullEncAlg; | ||
this.userinfoEncryptionEncValues = this.features.encryption ? encryptionEnc : []; | ||
this.userinfoSigningAlgValues = secretSig; | ||
if (this.subjectTypes.indexOf('pairwise') !== -1 && !this.pairwiseSalt) { | ||
const msg = 'pairwiseSalt must be configured when pairwise subjectType is to be supported'; | ||
throw new Error(msg); | ||
throw new Error( | ||
'pairwiseSalt must be configured when pairwise subjectType is to be supported'); | ||
} | ||
this.claimsSupported = _.chain(this.scopes) | ||
.map(scope => _.keys(_.get(this.claims, scope, {}))) | ||
.union(_.chain(this.claims) | ||
.pickBy(_.isNull) | ||
.keys() | ||
.value()) | ||
.flatten() | ||
.sort() | ||
.value(); | ||
const grantTypes = []; | ||
_.forEach(this.responseTypes, (responseType) => { | ||
if (responseType.indexOf('token') !== -1) { | ||
grantTypes.push('implicit'); | ||
} | ||
if (responseType.indexOf('code') !== -1) { | ||
grantTypes.push('authorization_code'); | ||
} | ||
}); | ||
if (this.features.refreshToken || this.scopes.indexOf('offline_access') !== -1) { | ||
grantTypes.push('refresh_token'); | ||
} | ||
if (this.features.clientCredentials) { | ||
grantTypes.push('client_credentials'); | ||
} | ||
this.grantTypes = _.uniq(grantTypes); | ||
if (!this.adapter) { | ||
@@ -107,0 +24,0 @@ this.adapter = MemoryAdapter; |
@@ -73,3 +73,3 @@ 'use strict'; | ||
routes: { | ||
authentication: '/auth', | ||
authorization: '/auth', | ||
certificates: '/certs', | ||
@@ -92,3 +92,3 @@ check_session: '/session/check', | ||
], | ||
subjectTypes: ['public', 'pairwise'], | ||
subjectTypes: ['public'], | ||
pairwiseSalt: '', | ||
@@ -104,7 +104,7 @@ tokenEndpointAuthMethods: [ | ||
acr: 1 * 60, | ||
accessToken: 5 * 60, | ||
authorizationCode: 1 * 60, | ||
clientCredentials: 1 * 60, | ||
idToken: 5 * 60, | ||
refreshToken: 30 * 60, | ||
AccessToken: 5 * 60, | ||
AuthorizationCode: 1 * 60, | ||
ClientCredentials: 1 * 60, | ||
IdToken: 5 * 60, | ||
RefreshToken: 30 * 60, | ||
}, | ||
@@ -111,0 +111,0 @@ uniqueness: function uniqueness() { |
@@ -38,6 +38,6 @@ 'use strict'; | ||
function InvalidClientMetadata(description, key) { | ||
return createError(400, key || 'invalid_client_metadata', { | ||
error_description: description, | ||
}); | ||
function InvalidClientMetadata(description) { | ||
const message = description.startsWith('redirect_uris') ? | ||
'invalid_redirect_uri' : 'invalid_client_metadata'; | ||
return createError(400, message, { error_description: description }); | ||
} | ||
@@ -44,0 +44,0 @@ |
@@ -5,2 +5,3 @@ 'use strict'; | ||
this.type = 'html'; | ||
this.status = 200; | ||
@@ -7,0 +8,0 @@ const formInputs = Object.keys(inputs) |
@@ -24,3 +24,3 @@ 'use strict'; | ||
}, | ||
timeout: this.provider.configuration.timeouts.request_uri, | ||
timeout: this.provider.configuration('timeouts.request_uri'), | ||
retries: 0, | ||
@@ -27,0 +27,0 @@ followRedirect: false, |
@@ -7,3 +7,3 @@ 'use strict'; | ||
module.exports = function validatePresence(required) { | ||
const missing = _.difference(required, _.keys(this.oidc.params)); | ||
const missing = _.difference(required, _.keys(_.omitBy(this.oidc.params, _.isUndefined))); | ||
@@ -10,0 +10,0 @@ this.assert(_.isEmpty(missing), |
250
lib/index.js
'use strict'; | ||
const koa = require('koa'); | ||
const pkg = require('../package.json'); | ||
const events = require('events'); | ||
const _ = require('lodash'); | ||
const url = require('url'); | ||
const Router = require('koa-router'); | ||
const jose = require('node-jose'); | ||
const Provider = require('./provider'); | ||
const AdapterTest = require('./adapter_test'); | ||
const getClient = require('./models/client'); | ||
const getIdToken = require('./models/id_token'); | ||
const getOauthToken = require('./models/oauth_token'); | ||
const getSession = require('./models/session'); | ||
const getAccessToken = require('./models/access_token'); | ||
const getAuthorizationCode = require('./models/authorization_code'); | ||
const getClientCredentials = require('./models/client_credentials'); | ||
const getRefreshToken = require('./models/refresh_token'); | ||
const getAuthentication = require('./actions/authentication'); | ||
const getUserinfo = require('./actions/userinfo'); | ||
const getToken = require('./actions/token'); | ||
const getCertificates = require('./actions/certificates'); | ||
const getRegistration = require('./actions/registration'); | ||
const getRevocation = require('./actions/revocation'); | ||
const getIntrospection = require('./actions/introspection'); | ||
const getWebfinger = require('./actions/webfinger'); | ||
const getDiscovery = require('./actions/discovery'); | ||
const getCheckSession = require('./actions/check_session'); | ||
const getEndSession = require('./actions/end_session'); | ||
const getResumeMiddleware = require('./middlewares/resume'); | ||
const getSessionMiddleware = require('./middlewares/session'); | ||
const apiErrorHandlerMiddleware = require('./middlewares/api_error_handler'); | ||
const getConfiguration = require('./helpers/configuration'); | ||
class Provider extends events.EventEmitter { | ||
constructor(issuer, setup) { | ||
super(); | ||
this.issuer = issuer; | ||
Object.defineProperty(this, 'configuration', { | ||
value: getConfiguration(setup), | ||
}); | ||
Object.defineProperties(this, { | ||
Client: { | ||
value: getClient(this), | ||
}, | ||
IdToken: { | ||
value: getIdToken(this), | ||
}, | ||
OAuthToken: { | ||
value: getOauthToken(this), | ||
}, | ||
Session: { | ||
value: getSession(this), | ||
}, | ||
app: { | ||
value: koa(), | ||
}, | ||
keystore: { | ||
writable: process.env.NODE_ENV === 'test', | ||
value: jose.JWK.createKeyStore(), | ||
}, | ||
router: { | ||
value: new Router(), | ||
}, | ||
}); | ||
this.mountPath = url.parse(this.issuer).pathname; | ||
Object.defineProperties(this, { | ||
AccessToken: { | ||
value: getAccessToken(this.OAuthToken), | ||
}, | ||
AuthorizationCode: { | ||
value: getAuthorizationCode(this.OAuthToken), | ||
}, | ||
ClientCredentials: { | ||
value: getClientCredentials(this.OAuthToken), | ||
}, | ||
RefreshToken: { | ||
value: getRefreshToken(this.OAuthToken), | ||
}, | ||
}); | ||
this.AccessToken.expiresIn = this.configuration.ttl.accessToken; | ||
this.AuthorizationCode.expiresIn = this.configuration.ttl.authorizationCode; | ||
this.ClientCredentials.expiresIn = this.configuration.ttl.clientCredentials; | ||
this.IdToken.expiresIn = this.configuration.ttl.idToken; | ||
this.RefreshToken.expiresIn = this.configuration.ttl.refreshToken; | ||
const session = getSessionMiddleware(this); | ||
const errorHandler = apiErrorHandlerMiddleware; | ||
const authentication = getAuthentication(this); | ||
const authMountPath = url.resolve('/', | ||
this.configuration.routes.authentication); | ||
this.router.get('authentication', authMountPath, session, authentication); | ||
this.router.post('authentication', authMountPath, session, authentication); | ||
const resume = getResumeMiddleware(this); | ||
const resumeMountPath = url.resolve('/', `${authMountPath}/:grant`); | ||
this.router.get('resume', resumeMountPath, session, resume, authentication); | ||
const userinfo = getUserinfo(this); | ||
const userinfoMountPath = url.resolve('/', this.configuration.routes.userinfo); | ||
this.router.get('userinfo', userinfoMountPath, userinfo); | ||
this.router.post('userinfo', userinfoMountPath, userinfo); | ||
const token = getToken(this); | ||
const tokenMountPath = url.resolve('/', this.configuration.routes.token); | ||
this.router.post('token', tokenMountPath, errorHandler(this, 'grant.error'), token); | ||
const certificates = getCertificates(this); | ||
const certMountPath = url.resolve('/', this.configuration.routes.certificates); | ||
this.router.get('certificates', certMountPath, errorHandler(this, 'certificates.error'), | ||
certificates); | ||
if (this.configuration.features.registration) { | ||
const registration = getRegistration(this); | ||
const regMountPath = url.resolve('/', this.configuration.routes.registration); | ||
const regClientMountPath = `${regMountPath}/:clientId`; | ||
this.router.post('registration', regMountPath, errorHandler(this, 'registration.error'), | ||
registration.post); | ||
this.router.get('registration_client', regClientMountPath, errorHandler(this, | ||
'registration.error'), registration.get); | ||
} | ||
if (this.configuration.features.revocation) { | ||
const revocation = getRevocation(this); | ||
const revokeMountPath = url.resolve('/', this.configuration.routes.revocation); | ||
this.router.post('revocation', revokeMountPath, errorHandler(this, 'revocation.error'), | ||
revocation); | ||
} | ||
if (this.configuration.features.introspection) { | ||
const introspection = getIntrospection(this); | ||
const introMountPath = url.resolve('/', this.configuration.routes.introspection); | ||
this.router.post('introspection', introMountPath, errorHandler(this, 'introspection.error'), | ||
introspection); | ||
} | ||
if (this.configuration.features.discovery) { | ||
const webfinger = getWebfinger(this); | ||
const discovery = getDiscovery(this); | ||
this.router.get('webfinger', '/.well-known/webfinger', errorHandler(this, 'webfinger.error'), | ||
webfinger); | ||
this.router.get('discovery', '/.well-known/openid-configuration', errorHandler(this, | ||
'discovery.error'), discovery); | ||
} | ||
if (this.configuration.features.sessionManagement) { | ||
const checkFrame = getCheckSession(this); | ||
const checkFrameMountPath = url.resolve('/', this.configuration.routes.check_session); | ||
this.router.get('check_session', checkFrameMountPath, errorHandler(this, | ||
'check_session.error'), checkFrame); | ||
const endSession = getEndSession(this); | ||
const endSessionMountPath = url.resolve('/', this.configuration.routes.end_session); | ||
this.router.get('end_session', endSessionMountPath, errorHandler(this, 'end_session.error'), | ||
session, endSession); | ||
this.router.post('end_session', endSessionMountPath, errorHandler(this, 'end_session.error'), | ||
session, endSession); | ||
} | ||
const self = this; | ||
this.app.use(function * contextEnsureOidc(next) { | ||
this.oidc = {}; | ||
this.oidc.pathFor = (name, opt) => self.pathFor(name, opt); | ||
this.oidc.urlFor = (name, opt) => url.resolve(this.href, this.oidc.pathFor(name, opt)); | ||
yield next; | ||
}); | ||
this.app.use(this.router.routes()); | ||
this.app.use(this.router.allowedMethods()); | ||
} | ||
} | ||
Provider.prototype.pathFor = function pathFor(name, opts) { | ||
return [ | ||
this.mountPath !== '/' ? this.mountPath : undefined, | ||
this.router.url(name, opts), | ||
].join(''); | ||
}; | ||
/* istanbul ignore next */ | ||
Provider.prototype.resume = function resume(ctx, grant, result) { | ||
const resumePath = this.pathFor('resume', { grant }); | ||
ctx.cookies.set('_grant_result', JSON.stringify(result), _.merge({ path: resumePath }, | ||
this.configuration.cookies.short)); | ||
ctx.redirect(resumePath); | ||
}; | ||
Provider.prototype.addClient = function addClient(client) { | ||
return this.Client.add(client); | ||
}; | ||
Provider.prototype.userAgent = function userAgent() { | ||
return `${pkg.name}/${pkg.version} (${this.issuer}; ${pkg.homepage})`; | ||
}; | ||
Provider.prototype.addKey = function addKey(key) { | ||
return this.keystore.add(key).then((jwk) => { | ||
// check if private key was added | ||
try { | ||
jwk.toPEM(true); | ||
} catch (err) { | ||
this.keystore.remove(jwk); | ||
throw new Error('only private keys should be added'); | ||
} | ||
if (this.configuration.features.encryption) { | ||
const encryptionAlgs = jwk.algorithms('wrap'); | ||
[ | ||
// 'idTokenEncryptionAlgValues', | ||
'requestObjectEncryptionAlgValues', | ||
// 'userinfoEncryptionAlgValues', | ||
].forEach((prop) => { | ||
this.configuration[prop] = _.union(this.configuration[prop], | ||
encryptionAlgs); | ||
}); | ||
} | ||
const signingAlgs = jwk.algorithms('sign'); | ||
[ | ||
'idTokenSigningAlgValues', | ||
// 'requestObjectSigningAlgValues', | ||
// 'tokenEndpointAuthSigningAlgValues', | ||
'userinfoSigningAlgValues', | ||
].forEach((prop) => { | ||
this.configuration[prop] = _.union(this.configuration[prop], signingAlgs); | ||
}); | ||
return Promise.resolve(jwk); | ||
}); | ||
}; | ||
module.exports.Provider = Provider; | ||
module.exports.AdapterTest = AdapterTest; |
@@ -5,6 +5,6 @@ 'use strict'; | ||
const body = require('../selective_body'); | ||
const bodyParser = require('../selective_body'); | ||
const params = require('../get_params'); | ||
const dupes = require('../check_dupes'); | ||
const client = require('../find_client_id'); | ||
const rejectDupes = require('../check_dupes'); | ||
const getClientId = require('../find_client_id'); | ||
const loadClient = require('../load_client'); | ||
@@ -15,10 +15,10 @@ const tokenAuth = require('../token_auth'); | ||
const auth = tokenAuth(provider); | ||
const bodyMiddleware = body('application/x-www-form-urlencoded'); | ||
const getParams = params(whitelist); | ||
const parseBody = bodyParser('application/x-www-form-urlencoded'); | ||
const buildParams = params(whitelist); | ||
return compose([ | ||
bodyMiddleware, | ||
getParams, | ||
dupes, | ||
client, | ||
parseBody, | ||
buildParams, | ||
rejectDupes, | ||
getClientId, | ||
loadClient(provider), | ||
@@ -25,0 +25,0 @@ auth, |
@@ -14,6 +14,5 @@ 'use strict'; | ||
this.assert(_.isEmpty(dupes), | ||
new errors.InvalidRequestError( | ||
`parameters must not be provided twice. ${dupes.join(',')}`)); | ||
new errors.InvalidRequestError(`parameters must not be provided twice. ${dupes.join(',')}`)); | ||
yield next; | ||
}; |
@@ -10,10 +10,9 @@ 'use strict'; | ||
if (this.headers.authorization) { | ||
this.assert(!this.oidc.params.client_id, | ||
new errors.InvalidRequestError( | ||
'combining multiple client authentication mechanism is no good')); | ||
// client_secret_basic | ||
this.assert(!this.oidc.params.client_id, new errors.InvalidRequestError( | ||
'combining multiple client authentication mechanism is no good')); | ||
const parts = this.headers.authorization.split(' '); | ||
this.assert(parts.length === 2 && parts[0] === 'Basic', | ||
new errors.InvalidRequestError( | ||
'invalid authorization header value format')); | ||
new errors.InvalidRequestError('invalid authorization header value format')); | ||
@@ -24,4 +23,3 @@ const basic = new Buffer(parts[1], 'base64').toString('utf8'); | ||
this.assert(i !== -1, | ||
new errors.InvalidRequestError( | ||
'invalid authorization header value format')); | ||
new errors.InvalidRequestError('invalid authorization header value format')); | ||
@@ -31,4 +29,6 @@ this.oidc.authorization.clientId = basic.slice(0, i); | ||
} else if (this.oidc.params.client_id && !this.oidc.params.client_assertion) { | ||
// client_secret_post | ||
this.oidc.authorization.clientId = this.oidc.params.client_id; | ||
} else if (this.oidc.params.client_assertion) { | ||
// client_secret_jwt and private_key_jwt | ||
let assertionSub; | ||
@@ -41,10 +41,7 @@ | ||
} catch (err) { | ||
this.throw(new errors.InvalidRequestError( | ||
'invalid client_assertion')); | ||
this.throw(new errors.InvalidRequestError('invalid client_assertion')); | ||
} | ||
this.assert(!this.oidc.params.client_id || | ||
assertionSub === this.oidc.params.client_id, | ||
new errors.InvalidRequestError( | ||
'subject of client_assertion must be the same as client_id')); | ||
this.assert(!this.oidc.params.client_id || assertionSub === this.oidc.params.client_id, | ||
new errors.InvalidRequestError('subject of client_assertion must be the same as client_id')); | ||
@@ -55,6 +52,5 @@ this.oidc.authorization.clientId = assertionSub; | ||
this.assert(this.oidc.authorization.clientId, | ||
new errors.InvalidClientError( | ||
'no client authentication mechanism provided')); | ||
new errors.InvalidClientError('no client authentication mechanism provided')); | ||
yield next; | ||
}; |
'use strict'; | ||
const _ = require('lodash'); | ||
const assert = require('assert'); | ||
@@ -9,7 +8,16 @@ | ||
class Params { | ||
constructor(params) { | ||
whitelist.forEach((prop) => { | ||
this[prop] = params[prop]; | ||
}); | ||
Object.seal(this); | ||
} | ||
} | ||
return function * assembleParams(next) { | ||
const params = this.method === 'POST' ? this.request.body : this.query; | ||
this.oidc.params = _.pick(params, whitelist); | ||
this.oidc.params = new Params(params); | ||
yield next; | ||
}; | ||
}; |
@@ -7,7 +7,6 @@ 'use strict'; | ||
return function * loadClient(next) { | ||
const client = yield provider.Client.find(this.oidc.authorization.clientId); | ||
const client = yield provider.get('Client').find(this.oidc.authorization.clientId); | ||
this.assert(client, | ||
new errors.InvalidClientError( | ||
'invalid client authentication provided (client not found)')); | ||
this.assert(client, new errors.InvalidClientError( | ||
'invalid client authentication provided (client not found)')); | ||
@@ -14,0 +13,0 @@ this.oidc.client = client; |
'use strict'; | ||
const j = JSON.parse; | ||
const errors = require('../helpers/errors'); | ||
@@ -9,8 +10,8 @@ module.exports = function getResumeAction(provider) { | ||
const cookieOptions = provider.configuration('cookies.short'); | ||
try { | ||
this.query = j(this.cookies.get('_grant', provider.configuration.cookies.short)); | ||
this.query = j(this.cookies.get('_grant', cookieOptions)); | ||
} catch (err) { | ||
this.body = 'authentication request has expired'; | ||
this.status = 400; | ||
return; | ||
throw new errors.InvalidRequestError('authorization request has expired'); | ||
} | ||
@@ -20,3 +21,3 @@ | ||
try { | ||
result = j(this.cookies.get('_grant_result', provider.configuration.cookies.short)); | ||
result = j(this.cookies.get('_grant_result', cookieOptions)); | ||
} catch (err) { | ||
@@ -28,5 +29,5 @@ result = {}; | ||
if (!result.login.remember) { | ||
// clear the existing session and create a fake one. | ||
// clear the existing session and create a temp one. | ||
yield this.oidc.session.destroy(); | ||
this.oidc.session = new provider.Session(); | ||
this.oidc.session = new (provider.get('Session'))(); | ||
} | ||
@@ -39,6 +40,5 @@ | ||
// TODO: finish this | ||
// if (result.consent) { | ||
// | ||
// } | ||
if (result.consent && result.consent.scope !== undefined) { | ||
this.query.scope = String(result.consent.scope); | ||
} | ||
@@ -45,0 +45,0 @@ this.oidc.result = result; |
@@ -5,3 +5,3 @@ 'use strict'; | ||
return function * sessionHandler(next) { | ||
this.oidc.session = yield provider.Session.get(this); | ||
this.oidc.session = yield provider.get('Session').get(this); | ||
yield next; | ||
@@ -8,0 +8,0 @@ yield this.oidc.session.save(); |
@@ -15,4 +15,3 @@ 'use strict'; | ||
this.throw(new errors.InvalidRequestError( | ||
'client not supposed to access token endpoint')); | ||
this.throw(new errors.InvalidRequestError('client not supposed to access token endpoint')); | ||
@@ -30,4 +29,3 @@ /* istanbul ignore next */ | ||
yield tokenCredentialAuth.call(this, this.oidc.client.clientSecret, | ||
params.client_secret); | ||
yield tokenCredentialAuth.call(this, this.oidc.client.clientSecret, params.client_secret); | ||
@@ -40,4 +38,3 @@ break; | ||
this.oidc.client.tokenEndpointAuthSigningAlg ? | ||
[this.oidc.client.tokenEndpointAuthSigningAlg] : ['HS256', 'HS384', | ||
'HS512']); | ||
[this.oidc.client.tokenEndpointAuthSigningAlg] : ['HS256', 'HS384', 'HS512']); | ||
@@ -49,4 +46,4 @@ break; | ||
this.oidc.client.tokenEndpointAuthSigningAlg ? | ||
[this.oidc.client.tokenEndpointAuthSigningAlg] : ['ES256', 'ES384', | ||
'ES512', 'RS256', 'RS384', 'RS512', 'PS256', 'PS384', 'PS512']); | ||
[this.oidc.client.tokenEndpointAuthSigningAlg] : ['ES256', 'ES384', 'ES512', 'RS256', | ||
'RS384', 'RS512', 'PS256', 'PS384', 'PS512']); | ||
@@ -63,4 +60,3 @@ break; | ||
yield tokenCredentialAuth.call(this, this.oidc.client.clientSecret, | ||
auth.clientSecret); | ||
yield tokenCredentialAuth.call(this, this.oidc.client.clientSecret, auth.clientSecret); | ||
} | ||
@@ -67,0 +63,0 @@ } |
@@ -65,4 +65,4 @@ 'use strict'; | ||
'sub (JWT subject) must be the client id')); | ||
const unique = yield provider.configuration.uniqueness(payload.jti, payload.exp); | ||
const uniqueCheck = provider.configuration('uniqueness'); | ||
const unique = yield uniqueCheck(payload.jti, payload.exp); | ||
this.assert(unique, new errors.InvalidRequestError( | ||
@@ -69,0 +69,0 @@ 'jwt-bearer tokens must only be used once')); |
'use strict'; | ||
module.exports = function getAccessToken(BaseOauthToken) { | ||
module.exports = function getAccessToken(provider) { | ||
const BaseOauthToken = provider.get('OAuthToken'); | ||
return class AccessToken extends BaseOauthToken {}; | ||
}; |
'use strict'; | ||
module.exports = function getAuthorizationCode(BaseOauthToken) { | ||
module.exports = function getAuthorizationCode(provider) { | ||
const BaseOauthToken = provider.get('OAuthToken'); | ||
return class AuthorizationCode extends BaseOauthToken {}; | ||
}; |
'use strict'; | ||
module.exports = function getClientCredentials(BaseOauthToken) { | ||
module.exports = function getClientCredentials(provider) { | ||
const BaseOauthToken = provider.get('OAuthToken'); | ||
return class ClientCredentials extends BaseOauthToken {}; | ||
}; |
@@ -5,3 +5,2 @@ /* eslint-disable newline-per-chained-call */ | ||
const _ = require('lodash'); | ||
const Joi = require('joi'); | ||
const url = require('url'); | ||
@@ -14,223 +13,39 @@ const jose = require('node-jose'); | ||
const errors = require('../helpers/errors'); | ||
const getSchema = require('../helpers/client_schema'); | ||
const KEY_ATTRIBUTES = ['crv', 'e', 'kid', 'kty', 'n', 'use', 'x', 'y']; | ||
const KEY_TYPES = ['RSA', 'EC']; | ||
const RECOGNIZED_METADATA = [ | ||
'application_type', | ||
'client_id', | ||
'client_name', | ||
'client_secret', | ||
'client_secret_expires_at', | ||
'client_uri', | ||
'contacts', | ||
'default_acr_values', | ||
'default_max_age', | ||
'grant_types', | ||
'id_token_encrypted_response_alg', | ||
'id_token_encrypted_response_enc', | ||
'id_token_signed_response_alg', | ||
'initiate_login_uri', | ||
'jwks', | ||
'jwks_uri', | ||
'logo_uri', | ||
'policy_uri', | ||
'post_logout_redirect_uris', | ||
'redirect_uris', | ||
'registration_access_token', | ||
'request_object_encryption_alg', | ||
'request_object_encryption_enc', | ||
'request_object_signing_alg', | ||
'request_uris', | ||
'require_auth_time', | ||
'response_types', | ||
'sector_identifier_uri', | ||
'subject_type', | ||
'token_endpoint_auth_method', | ||
'token_endpoint_auth_signing_alg', | ||
'tos_uri', | ||
'userinfo_encrypted_response_alg', | ||
'userinfo_encrypted_response_enc', | ||
'userinfo_signed_response_alg', | ||
]; | ||
const SECRET_LENGTH_REQUIRED = [ | ||
'id_token_signed_response_alg', | ||
'request_object_signing_alg', | ||
'token_endpoint_auth_signing_alg', | ||
'userinfo_signed_response_alg', | ||
]; | ||
function handled(kty) { | ||
return KEY_TYPES.indexOf(kty) !== -1; | ||
} | ||
// TODO: validate all | ||
module.exports = function getClient(provider) { | ||
const webUri = Joi.string().uri({ | ||
scheme: ['http', 'https'], | ||
}); | ||
const Schema = getSchema(provider); | ||
const cache = new Map(); | ||
function presenceDependant(field, value) { | ||
return (client) => { | ||
if (_.isUndefined(client[field])) { | ||
return undefined; | ||
} | ||
return value; | ||
}; | ||
} | ||
function baseSchema() { | ||
const conf = provider.configuration; | ||
return Joi.object().keys({ | ||
application_type: Joi.string().valid('web', 'native').default('web'), | ||
client_id: Joi.string().required(), | ||
client_name: Joi.string(), | ||
// TODO: validate secret length | ||
client_secret: Joi.string().required(), | ||
client_uri: webUri, | ||
contacts: Joi.array().items(Joi.string().email()), | ||
default_acr_values: Joi.array().items(Joi.string()), | ||
default_max_age: Joi.number().integer().positive().strict(), | ||
grant_types: Joi.array().min(1).items(conf.grantTypes) | ||
.default(['authorization_code']), | ||
id_token_signed_response_alg: Joi.string().when('response_types', { | ||
is: Joi.array().items(Joi.string().regex(/token/).forbidden()), | ||
then: Joi.string().valid( | ||
conf.idTokenSigningAlgValues), | ||
otherwise: Joi.string().valid(_.without( | ||
conf.idTokenSigningAlgValues, 'none')), | ||
}).default('RS256'), | ||
initiate_login_uri: webUri, | ||
redirect_uris: Joi.array().min(1).items( | ||
Joi.string().regex(/#/).forbidden()).when('application_type', { | ||
is: 'web', | ||
then: Joi.array().items(webUri), | ||
otherwise: Joi.array().items(Joi.string().uri()), | ||
}).required(), | ||
jwks_uri: webUri, | ||
logo_uri: webUri, | ||
policy_uri: webUri, | ||
post_logout_redirect_uris: Joi.array().items(webUri).default([]), | ||
request_object_signing_alg: Joi.string( | ||
conf.requestObjectSigningAlgValues), | ||
request_uris: Joi.array().items(Joi.string().uri({ scheme: ['https'] })).default( | ||
conf.features.requestUri.requireRequestUriRegistration ? [] : undefined | ||
), | ||
require_auth_time: Joi.boolean().default(false), | ||
response_types: Joi.array().min(1).items(conf.responseTypes) | ||
.default(['code']), | ||
sector_identifier_uri: Joi.string().uri({ | ||
scheme: ['https'], | ||
}), | ||
subject_type: Joi.string().valid(conf.subjectTypes) | ||
.default('public'), | ||
token_endpoint_auth_method: Joi.string().valid( | ||
conf.tokenEndpointAuthMethods).default('client_secret_basic'), | ||
tos_uri: webUri, | ||
userinfo_signed_response_alg: Joi.string().valid( | ||
conf.userinfoSigningAlgValues).default(undefined), | ||
id_token_encrypted_response_alg: Joi.string() | ||
.valid(conf.idTokenEncryptionAlgValues).default(undefined), | ||
id_token_encrypted_response_enc: Joi.string() | ||
.valid(conf.idTokenEncryptionEncValues) | ||
.default(presenceDependant( | ||
'id_token_encrypted_response_alg', 'A128CBC-HS256'), | ||
'id_token_encrypted_response_alg dependant default'), | ||
userinfo_encrypted_response_alg: Joi.string() | ||
.valid(conf.userinfoEncryptionAlgValues).default(undefined), | ||
userinfo_encrypted_response_enc: Joi.string() | ||
.valid(conf.userinfoEncryptionEncValues) | ||
.default(presenceDependant( | ||
'userinfo_encrypted_response_alg', 'A128CBC-HS256'), | ||
'userinfo_encrypted_response_alg dependant default'), | ||
}) | ||
.with('id_token_encrypted_response_enc', 'id_token_encrypted_response_alg') | ||
.with('userinfo_encrypted_response_enc', 'userinfo_encrypted_response_alg') | ||
.unknown(); | ||
} | ||
function schemaValidate(meta) { | ||
let metadata = meta; | ||
let schema = baseSchema(); | ||
function schemaValidate(client, metadata) { | ||
try { | ||
const signingAlg = metadata.request_object_signing_alg; | ||
const requireJwks = | ||
metadata.token_endpoint_auth_method === 'private_key_jwt' || | ||
(signingAlg && signingAlg.startsWith('RS')) || | ||
(signingAlg && signingAlg.startsWith('ES')); | ||
const schema = new Schema(metadata); | ||
if (requireJwks) { | ||
schema = schema.or('jwks', 'jwks_uri'); | ||
} | ||
metadata = Joi.attempt(_.chain(metadata).omitBy(_.isNull) | ||
.pick(RECOGNIZED_METADATA).value(), schema); | ||
const hsLengths = SECRET_LENGTH_REQUIRED.map((prop) => { | ||
if (metadata[prop] && metadata[prop].startsWith('HS')) { | ||
return parseInt(metadata[prop].slice(-3) / 8, 10); | ||
} | ||
return undefined; | ||
Object.defineProperty(client, 'sectorIdentifier', { | ||
enumerable: false, | ||
writable: true, | ||
}); | ||
const validateSecretLength = _.max(hsLengths); | ||
Object.assign(client, _.mapKeys(schema, (value, key) => _.camelCase(key))); | ||
if (validateSecretLength) { | ||
Joi.assert(metadata, Joi.object().keys({ | ||
client_secret: Joi.string().min(validateSecretLength), | ||
}).unknown()); | ||
} | ||
const rts = _.chain(metadata.response_types).map(rt => rt.split(' ')) | ||
.flatten().uniq().value(); | ||
if (_.includes(rts, 'code')) { | ||
Joi.assert(metadata.grant_types, | ||
Joi.array().items( | ||
Joi.string().valid('authorization_code').required(), | ||
Joi.string())); | ||
} | ||
if (_.includes(rts, 'token') || _.includes(rts, 'id_token')) { | ||
Joi.assert(metadata.grant_types, | ||
Joi.array().items( | ||
Joi.string().valid('implicit').required(), | ||
Joi.string())); | ||
} | ||
if (metadata.subject_type === 'pairwise' && | ||
!metadata.sector_identifier_uri) { | ||
const hosts = _.chain(metadata.redirect_uris) | ||
.map(uri => url.parse(uri).host).uniq().value(); | ||
if (hosts.length === 1) { | ||
metadata.sector_identifier = hosts[0]; | ||
} else { | ||
throw new Error('sector_identifier_uri is required when using ' + | ||
'multiple hosts in your redirect_uris'); | ||
} | ||
} else if (metadata.sector_identifier_uri) { | ||
metadata.sector_identifier = | ||
url.parse(metadata.sector_identifier_uri).host; | ||
} | ||
return Promise.resolve(metadata); | ||
return Promise.resolve(client); | ||
} catch (err) { | ||
const redirectUriError = err.details && err.details.length === 1 && | ||
err.details[0].path.startsWith('redirect_uri'); | ||
const message = _.map(err.details, | ||
member => `Validation error '${member.message}' in path '${member.path}'`).join('. '); | ||
return Promise.reject(new errors.InvalidClientMetadata( | ||
message || err.message, redirectUriError ? | ||
'invalid_redirect_uri' : undefined)); | ||
return Promise.reject(err); | ||
} | ||
} | ||
function sectorValidate(metadata) { | ||
if (metadata.sector_identifier_uri !== undefined) { | ||
return got(metadata.sector_identifier_uri, { | ||
function sectorValidate(client) { | ||
if (client.sectorIdentifierUri !== undefined) { | ||
return got(client.sectorIdentifierUri, { | ||
headers: { | ||
'User-Agent': provider.userAgent(), | ||
}, | ||
timeout: provider.configuration.timeouts.sector_identifier_uri, | ||
timeout: provider.configuration('timeouts.sector_identifier_uri'), | ||
retries: 0, | ||
@@ -245,3 +60,3 @@ followRedirect: false, | ||
'sector_identifier_uri must return single JSON array'); | ||
const missing = metadata.redirect_uris.find((uri) => body.indexOf(uri) === -1); | ||
const missing = client.redirectUris.find((uri) => body.indexOf(uri) === -1); | ||
assert(!missing, | ||
@@ -253,3 +68,3 @@ 'all registered redirect_uris must be included in the sector_identifier_uri'); | ||
return metadata; | ||
return client; | ||
}, (error) => { | ||
@@ -261,15 +76,2 @@ throw new errors.InvalidClientMetadata( | ||
return metadata; | ||
} | ||
function buildClient(metadata) { | ||
const client = new Client(); // eslint-disable-line no-use-before-define | ||
Object.defineProperty(client, 'sectorIdentifier', { | ||
enumerable: false, | ||
writable: true, | ||
}); | ||
Object.assign(client, _.mapKeys(metadata, (value, key) => _.camelCase(key))); | ||
return client; | ||
@@ -283,5 +85,3 @@ } | ||
client.keystore.refresh = function refreshKeyStore() { | ||
if (!this.jwksUri) { | ||
return Promise.resolve(); | ||
} | ||
if (!this.jwksUri) return Promise.resolve(); | ||
@@ -292,3 +92,3 @@ return got(this.jwksUri, { | ||
}, | ||
timeout: provider.configuration.timeouts.jwks_uri, | ||
timeout: provider.configuration('timeouts.jwks_uri'), | ||
retries: 0, | ||
@@ -302,5 +102,3 @@ followRedirect: false, | ||
if (!Array.isArray(body.keys)) { | ||
throw new Error('invalid jwks_uri response'); | ||
} | ||
if (!Array.isArray(body.keys)) throw new Error('invalid jwks_uri response'); | ||
@@ -311,3 +109,3 @@ const promises = []; | ||
body.keys.forEach((key) => { | ||
if (KEY_TYPES.indexOf(key.kty) !== -1 && !this.get(key.kid)) { | ||
if (handled(key.kty) && !this.get(key.kid)) { | ||
promises.push(this.add(_.pick(key, KEY_ATTRIBUTES))); | ||
@@ -318,3 +116,3 @@ } | ||
this.all().forEach((key) => { | ||
if (KEY_TYPES.indexOf(key.kty) !== -1 && kids.indexOf(key.kid) === -1) { | ||
if (handled(key.kty) && kids.indexOf(key.kid) === -1) { | ||
promises.push(this.remove(key)); | ||
@@ -334,3 +132,3 @@ } | ||
client.jwks.keys.forEach((key) => { | ||
if (KEY_TYPES.indexOf(key.kty) !== -1) { | ||
if (handled(key.kty)) { | ||
promises.push(client.keystore.add(_.pick(key, KEY_ATTRIBUTES))); | ||
@@ -356,5 +154,3 @@ } | ||
function register(client) { | ||
client.constructor.clients = client.constructor.clients || /* istanbul ignore next */ {}; | ||
client.constructor.clients[client.clientId] = client; | ||
cache.set(client.clientId, client); | ||
return client; | ||
@@ -366,3 +162,3 @@ } | ||
static get adapter() { | ||
const Adapter = provider.configuration.adapter; | ||
const Adapter = provider.configuration('adapter'); | ||
if (!this._adapter) { | ||
@@ -402,6 +198,9 @@ this._adapter = new Adapter(this.name); | ||
metadata() { | ||
return _.mapKeys(this, (value, key) => _.snakeCase(key)); | ||
} | ||
static add(metadata) { | ||
return schemaValidate(metadata) | ||
return schemaValidate(new this(), metadata) | ||
.then(sectorValidate) | ||
.then(buildClient) | ||
.then(buildKeyStore) | ||
@@ -412,11 +211,13 @@ .then(register); | ||
static remove(id) { | ||
this.clients = this.clients || /* istanbul ignore next */ {}; | ||
delete this.clients[id]; | ||
cache.delete(id); | ||
return this.adapter.destroy(id); | ||
} | ||
static purge() { | ||
cache.clear(); | ||
} | ||
static find(id) { | ||
this.clients = this.clients || /* istanbul ignore next */ {}; | ||
if (this.clients[id]) { | ||
return Promise.resolve(this.clients[id]); | ||
if (cache.has(id)) { | ||
return Promise.resolve(cache.get(id)); | ||
} | ||
@@ -423,0 +224,0 @@ |
@@ -12,3 +12,3 @@ 'use strict'; | ||
module.exports = function getIdToken(provider) { | ||
const Claims = getMask(provider.configuration); | ||
const Claims = getMask(provider.configuration()); | ||
@@ -23,13 +23,5 @@ return class IdToken { | ||
static get expiresIn() { | ||
/* istanbul ignore if */ | ||
if (!this.ttl) { | ||
throw new Error('expiresIn not set'); | ||
} | ||
return this.ttl; | ||
return provider.configuration(`ttl.${this.name}`); | ||
} | ||
static set expiresIn(ttl) { | ||
this.ttl = ttl; | ||
} | ||
set(key, value) { | ||
@@ -62,4 +54,4 @@ this.extra[key] = value; | ||
const key = alg && | ||
alg.startsWith('HS') ? client.keystore.get({ alg }) : provider.keystore.get({ alg }); | ||
const keystore = alg && alg.startsWith('HS') ? client.keystore : provider.keystore; | ||
const key = keystore && keystore.get({ alg }); | ||
@@ -92,3 +84,2 @@ const payload = this.payload(); | ||
if (encryption.enc) { | ||
// TODO: refresh when? | ||
return promise.then((signed) => client.keystore.refresh().then(() => signed)) | ||
@@ -95,0 +86,0 @@ .then((signed) => { |
@@ -37,3 +37,3 @@ 'use strict'; | ||
static get adapter() { | ||
const Adapter = provider.configuration.adapter; | ||
const Adapter = provider.configuration('adapter'); | ||
if (!this._adapter) { | ||
@@ -50,13 +50,5 @@ this._adapter = new Adapter(this.name); | ||
static get expiresIn() { | ||
/* istanbul ignore if */ | ||
if (!this.ttl) { | ||
throw new Error('expiresIn not set'); | ||
} | ||
return this.ttl; | ||
return provider.configuration(`ttl.${this.name}`); | ||
} | ||
static set expiresIn(ttl) { | ||
this.ttl = ttl; | ||
} | ||
get standardPayload() { | ||
@@ -63,0 +55,0 @@ return IN_PAYLOAD; |
'use strict'; | ||
module.exports = function getRefreshToken(BaseOauthToken) { | ||
module.exports = function getRefreshToken(provider) { | ||
const BaseOauthToken = provider.get('OAuthToken'); | ||
return class RefreshToken extends BaseOauthToken {}; | ||
}; |
@@ -14,3 +14,3 @@ 'use strict'; | ||
static get adapter() { | ||
const Adapter = provider.configuration.adapter; | ||
const Adapter = provider.configuration('adapter'); | ||
if (!this._adapter) { | ||
@@ -36,3 +36,3 @@ this._adapter = new Adapter(this.name); | ||
acr() { | ||
const conf = provider.configuration; | ||
const conf = provider.configuration(); | ||
if ((Date.now() / 1000 | 0) - this.authTime() < conf.ttl.acr) { | ||
@@ -55,5 +55,8 @@ return _.get(this, 'acrValue', conf.acrValues[0]); | ||
save() { | ||
if (this.id) { | ||
return this.adapter.upsert(this.id, Object.assign({}, this), | ||
provider.configuration.cookies.long.maxAge / 1000); | ||
const payload = Object.assign({}, this); | ||
delete payload.id; | ||
if (this.id && !_.isEmpty(payload)) { | ||
return this.adapter.upsert(this.id, payload, | ||
provider.configuration('cookies.long.maxAge') / 1000); | ||
} | ||
@@ -72,15 +75,13 @@ | ||
static * get(ctx) { | ||
static get(ctx) { | ||
// is there supposed to be a session bound? generate if not | ||
const sessionId = ctx.cookies.get('_session', { | ||
signed: provider.configuration.cookies.long.signed, | ||
signed: provider.configuration('cookies.long.signed'), | ||
}) || uuid.v4(); | ||
const session = yield this.find(sessionId); | ||
// refresh the session duration | ||
ctx.cookies.set('_session', sessionId, | ||
provider.configuration.cookies.long); | ||
return session; | ||
return this.find(sessionId).then((session) => { | ||
// refresh the session duration | ||
ctx.cookies.set('_session', sessionId, provider.configuration('cookies.long')); | ||
return session; | ||
}); | ||
} | ||
@@ -87,0 +88,0 @@ } |
@@ -9,3 +9,2 @@ { | ||
"http-errors": "^1.4.0", | ||
"joi": "^8.0.5", | ||
"koa": "^1.0.0", | ||
@@ -18,3 +17,4 @@ "koa-body": "^1.2.1", | ||
"node-jose": "^0.8.0", | ||
"node-uuid": "^1.4.7" | ||
"node-uuid": "^1.4.7", | ||
"valid-url": "^1.0.9" | ||
}, | ||
@@ -60,3 +60,3 @@ "description": "OpenID Provider (OP) implementation for Node.js OpenID Connect servers.", | ||
}, | ||
"version": "0.6.0", | ||
"version": "0.7.0", | ||
"files": [ | ||
@@ -63,0 +63,0 @@ "lib" |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
126385
53
3498
+ Addedvalid-url@^1.0.9
+ Addedvalid-url@1.0.9(transitive)
- Removedjoi@^8.0.5
- Removedhoek@4.3.1(transitive)
- Removedisemail@2.2.1(transitive)
- Removedjoi@8.4.2(transitive)
- Removedmoment@2.30.1(transitive)
- Removedtopo@2.1.1(transitive)