openid-client
Advanced tools
Comparing version 1.5.3 to 1.6.0
@@ -8,2 +8,3 @@ # openid-client CHANGELOG | ||
<!-- TOC START min:2 max:2 link:true update:true --> | ||
- [Version 1.6.0](#version-160) | ||
- [Version 1.5.0](#version-150) | ||
@@ -20,2 +21,7 @@ - [Version 1.4.0](#version-140) | ||
## Version 1.6.0 | ||
- [DIFF](https://github.com/panva/node-openid-client/compare/v1.5.3...v1.6.0) | ||
- added at_hash presence assertion for applicable (implicit) ID Token validation | ||
- added c_hash presence assertion for applicable (hybrid) ID Token validation from the authorization_endpoint | ||
## Version 1.5.0 | ||
@@ -22,0 +28,0 @@ ### Version 1.5.3 |
@@ -142,2 +142,6 @@ 'use strict'; | ||
class Client { | ||
/** | ||
* @name constructor | ||
* @api public | ||
*/ | ||
constructor(metadata, keystore) { | ||
@@ -164,2 +168,6 @@ const recognized = _.chain(metadata) | ||
/** | ||
* @name authorizationUrl | ||
* @api public | ||
*/ | ||
authorizationUrl(params) { | ||
@@ -172,2 +180,6 @@ return url.format(_.defaults({ | ||
/** | ||
* @name authorizationPost | ||
* @api public | ||
*/ | ||
authorizationPost(params) { | ||
@@ -190,2 +202,6 @@ const inputs = authorizationParams.call(this, params); | ||
/** | ||
* @name callbackParams | ||
* @api public | ||
*/ | ||
callbackParams(input) { // eslint-disable-line | ||
@@ -230,2 +246,6 @@ const isIncomingMessage = input instanceof http.IncomingMessage; | ||
/** | ||
* @name authorizationCallback | ||
* @api public | ||
*/ | ||
authorizationCallback(redirectUri, parameters, checks) { | ||
@@ -250,3 +270,3 @@ const params = _.pick(parameters, CALLBACK_PROPERTIES); | ||
.then(tokenset => this.decryptIdToken(tokenset, 'id_token')) | ||
.then(tokenset => this.validateIdToken(tokenset, toCheck.nonce, 'id_token', toCheck.max_age)); | ||
.then(tokenset => this.validateIdToken(tokenset, toCheck.nonce, 'authorization', toCheck.max_age)); | ||
} | ||
@@ -261,3 +281,3 @@ | ||
.then(tokenset => this.decryptIdToken(tokenset, 'id_token')) | ||
.then(tokenset => this.validateIdToken(tokenset, toCheck.nonce, 'id_token', toCheck.max_age)) | ||
.then(tokenset => this.validateIdToken(tokenset, toCheck.nonce, 'token', toCheck.max_age)) | ||
.then((tokenset) => { | ||
@@ -308,7 +328,6 @@ if (params.session_state) tokenset.session_state = params.session_state; | ||
const header = idToken.split('.')[0]; | ||
const headerObject = JSON.parse(base64url.decode(header)); | ||
const header = JSON.parse(base64url.decode(idToken.split('.')[0])); | ||
assert.equal(headerObject.alg, expectedAlg, 'unexpected alg received'); | ||
assert.equal(headerObject.enc, expectedEnc, 'unexpected enc received'); | ||
assert.equal(header.alg, expectedAlg, 'unexpected alg received'); | ||
assert.equal(header.enc, expectedEnc, 'unexpected enc received'); | ||
@@ -328,17 +347,11 @@ const keystoreOrSecret = expectedAlg.match(/^(RSA|ECDH)/) ? | ||
validateIdToken(token, nonce, intent, maxAge) { | ||
let idToken = token; | ||
/** | ||
* @name validateIdToken | ||
* @api private | ||
*/ | ||
validateIdToken(tokenSet, nonce, returnedBy, maxAge) { | ||
let idToken = tokenSet; | ||
const use = (() => { | ||
switch (intent) { | ||
case 'id_token': | ||
case 'userinfo': | ||
return intent; | ||
default: | ||
return 'id_token'; | ||
} | ||
})(); | ||
const expectedAlg = (() => { | ||
if (use === 'userinfo') return this.userinfo_signed_response_alg; | ||
if (returnedBy === 'userinfo') return this.userinfo_signed_response_alg; | ||
return this.id_token_signed_response_alg; | ||
@@ -361,9 +374,7 @@ })(); | ||
const parts = idToken.split('.'); | ||
const header = parts[0]; | ||
const payload = parts[1]; | ||
const headerObject = JSON.parse(base64url.decode(header)); | ||
const payloadObject = JSON.parse(base64url.decode(payload)); | ||
const header = JSON.parse(base64url.decode(parts[0])); | ||
const payload = JSON.parse(base64url.decode(parts[1])); | ||
const verifyPresence = (prop) => { | ||
if (payloadObject[prop] === undefined) { | ||
if (payload[prop] === undefined) { | ||
throw new Error(`missing required JWT property ${prop}`); | ||
@@ -373,44 +384,44 @@ } | ||
assert.equal(headerObject.alg, expectedAlg, 'unexpected algorithm received'); | ||
assert.equal(header.alg, expectedAlg, 'unexpected algorithm received'); | ||
if (use === 'id_token') { | ||
if (returnedBy !== 'userinfo') { | ||
['iss', 'sub', 'aud', 'exp', 'iat'].forEach(verifyPresence); | ||
} | ||
if (payloadObject.iss !== undefined) { | ||
assert.equal(this.issuer.issuer, payloadObject.iss, 'unexpected iss value'); | ||
if (payload.iss !== undefined) { | ||
assert.equal(this.issuer.issuer, payload.iss, 'unexpected iss value'); | ||
} | ||
if (payloadObject.iat !== undefined) { | ||
assert.equal(typeof payloadObject.iat, 'number', 'iat is not a number'); | ||
assert(payloadObject.iat <= timestamp + this.CLOCK_TOLERANCE, 'id_token issued in the future'); | ||
if (payload.iat !== undefined) { | ||
assert.equal(typeof payload.iat, 'number', 'iat is not a number'); | ||
assert(payload.iat <= timestamp + this.CLOCK_TOLERANCE, 'id_token issued in the future'); | ||
} | ||
if (payloadObject.nbf !== undefined) { | ||
assert.equal(typeof payloadObject.nbf, 'number', 'nbf is not a number'); | ||
assert(payloadObject.nbf <= timestamp + this.CLOCK_TOLERANCE, 'id_token not active yet'); | ||
if (payload.nbf !== undefined) { | ||
assert.equal(typeof payload.nbf, 'number', 'nbf is not a number'); | ||
assert(payload.nbf <= timestamp + this.CLOCK_TOLERANCE, 'id_token not active yet'); | ||
} | ||
if (maxAge || (maxAge !== null && this.require_auth_time)) { | ||
assert(payloadObject.auth_time, 'missing required JWT property auth_time'); | ||
assert.equal(typeof payloadObject.auth_time, 'number', 'auth_time is not a number'); | ||
assert(payload.auth_time, 'missing required JWT property auth_time'); | ||
assert.equal(typeof payload.auth_time, 'number', 'auth_time is not a number'); | ||
} | ||
if (maxAge) { | ||
assert(payloadObject.auth_time + maxAge >= timestamp - this.CLOCK_TOLERANCE, 'too much time has elapsed since the last End-User authentication'); | ||
assert(payload.auth_time + maxAge >= timestamp - this.CLOCK_TOLERANCE, 'too much time has elapsed since the last End-User authentication'); | ||
} | ||
if (nonce !== null && (payloadObject.nonce || nonce !== undefined)) { | ||
assert.equal(payloadObject.nonce, nonce, 'nonce mismatch'); | ||
if (nonce !== null && (payload.nonce || nonce !== undefined)) { | ||
assert.equal(payload.nonce, nonce, 'nonce mismatch'); | ||
} | ||
if (payloadObject.exp !== undefined) { | ||
assert.equal(typeof payloadObject.exp, 'number', 'exp is not a number'); | ||
assert(timestamp - this.CLOCK_TOLERANCE < payloadObject.exp, 'id_token expired'); | ||
if (payload.exp !== undefined) { | ||
assert.equal(typeof payload.exp, 'number', 'exp is not a number'); | ||
assert(timestamp - this.CLOCK_TOLERANCE < payload.exp, 'id_token expired'); | ||
} | ||
if (payloadObject.aud !== undefined) { | ||
if (!Array.isArray(payloadObject.aud)) { | ||
payloadObject.aud = [payloadObject.aud]; | ||
} else if (payloadObject.aud.length > 1 && !payloadObject.azp) { | ||
if (payload.aud !== undefined) { | ||
if (!Array.isArray(payload.aud)) { | ||
payload.aud = [payload.aud]; | ||
} else if (payload.aud.length > 1 && !payload.azp) { | ||
throw new Error('missing required JWT property azp'); | ||
@@ -420,29 +431,38 @@ } | ||
if (payloadObject.azp !== undefined) { | ||
assert.equal(this.client_id, payloadObject.azp, 'azp must be the client_id'); | ||
if (payload.azp !== undefined) { | ||
assert.equal(this.client_id, payload.azp, 'azp must be the client_id'); | ||
} | ||
if (payloadObject.aud !== undefined) { | ||
assert(payloadObject.aud.indexOf(this.client_id) !== -1, 'aud is missing the client_id'); | ||
if (payload.aud !== undefined) { | ||
assert(payload.aud.indexOf(this.client_id) !== -1, 'aud is missing the client_id'); | ||
} | ||
if (isTokenSet && payloadObject.at_hash !== undefined) { | ||
assert(tokenHash(payloadObject.at_hash, token.access_token), 'at_hash mismatch'); | ||
if (returnedBy === 'authorization') { | ||
assert(payload.at_hash || !tokenSet.access_token, 'missing required property at_hash'); | ||
assert(payload.c_hash || !tokenSet.code, 'missing required property c_hash'); | ||
} | ||
if (isTokenSet && payloadObject.c_hash !== undefined && token.code) { | ||
assert(tokenHash(payloadObject.c_hash, token.code), 'c_hash mismatch'); | ||
if (tokenSet.access_token && payload.at_hash !== undefined) { | ||
assert(tokenHash(payload.at_hash, tokenSet.access_token), 'at_hash mismatch'); | ||
} | ||
if (headerObject.alg === 'none') { | ||
return Promise.resolve(token); | ||
if (tokenSet.code && payload.c_hash !== undefined) { | ||
assert(tokenHash(payload.c_hash, tokenSet.code), 'c_hash mismatch'); | ||
} | ||
return (headerObject.alg.startsWith('HS') ? this.joseSecret() : this.issuer.key(headerObject)) | ||
if (header.alg === 'none') { | ||
return Promise.resolve(tokenSet); | ||
} | ||
return (header.alg.startsWith('HS') ? this.joseSecret() : this.issuer.key(header)) | ||
.then(key => jose.JWS.createVerify(key).verify(idToken).catch(() => { | ||
throw new Error('invalid signature'); | ||
})) | ||
.then(() => token); | ||
.then(() => tokenSet); | ||
} | ||
/** | ||
* @name refresh | ||
* @api public | ||
*/ | ||
refresh(refreshToken) { | ||
@@ -467,6 +487,10 @@ let token = refreshToken; | ||
return this.decryptIdToken(tokenset, 'id_token') | ||
.then(() => this.validateIdToken(tokenset, null, 'id_token', null)); | ||
.then(() => this.validateIdToken(tokenset, null, 'token', null)); | ||
}); | ||
} | ||
/** | ||
* @name userinfo | ||
* @api public | ||
*/ | ||
userinfo(accessToken, options) { | ||
@@ -558,2 +582,6 @@ let token = accessToken; | ||
/** | ||
* @name grant | ||
* @api public | ||
*/ | ||
grant(body) { | ||
@@ -565,2 +593,6 @@ assert(this.issuer.token_endpoint, 'issuer must be configured with token endpoint'); | ||
/** | ||
* @name revoke | ||
* @api public | ||
*/ | ||
revoke(token, hint) { | ||
@@ -575,2 +607,6 @@ assert(this.issuer.revocation_endpoint, 'issuer must be configured with revocation endpoint'); | ||
/** | ||
* @name introspect | ||
* @api public | ||
*/ | ||
introspect(token, hint) { | ||
@@ -586,2 +622,6 @@ assert(this.issuer.introspection_endpoint, 'issuer must be configured with introspection endpoint'); | ||
/* eslint-disable no-underscore-dangle */ | ||
/** | ||
* @name fetchDistributedClaims | ||
* @api public | ||
*/ | ||
fetchDistributedClaims(claims, accessTokens) { | ||
@@ -605,2 +645,6 @@ const distributedSources = _.pickBy(claims._claim_sources, def => !!def.endpoint); | ||
/** | ||
* @name unpackAggregatedClaims | ||
* @api public | ||
*/ | ||
unpackAggregatedClaims(claims) { | ||
@@ -706,2 +750,6 @@ const aggregatedSources = _.pickBy(claims._claim_sources, def => !!def.JWT); | ||
/** | ||
* @name register | ||
* @api public | ||
*/ | ||
static register(properties, opts) { | ||
@@ -747,2 +795,6 @@ const options = (() => { | ||
/** | ||
* @name fromUri | ||
* @api public | ||
*/ | ||
static fromUri(uri, token) { | ||
@@ -756,2 +808,6 @@ return got(uri, this.issuer.httpOptions({ | ||
/** | ||
* @name requestObject | ||
* @api public | ||
*/ | ||
requestObject(input, algorithms) { | ||
@@ -758,0 +814,0 @@ assert.equal(typeof input, 'object', 'pass an object as the first argument'); |
@@ -41,2 +41,6 @@ 'use strict'; | ||
class Issuer { | ||
/** | ||
* @name constructor | ||
* @api public | ||
*/ | ||
constructor(metadata) { | ||
@@ -123,2 +127,6 @@ const recognized = _.chain(metadata) | ||
/** | ||
* @name webfinger | ||
* @api public | ||
*/ | ||
static webfinger(input) { | ||
@@ -152,2 +160,6 @@ const resource = webfingerNormalize(input); | ||
/** | ||
* @name discover | ||
* @api public | ||
*/ | ||
static discover(uri) { | ||
@@ -171,2 +183,6 @@ uri = stripTrailingSlash(uri); // eslint-disable-line no-param-reassign | ||
/** | ||
* @name defaultHttpOptions | ||
* @api public | ||
*/ | ||
static get defaultHttpOptions() { | ||
@@ -176,2 +192,6 @@ return defaultHttpOptions; | ||
/** | ||
* @name defaultHttpOptions= | ||
* @api public | ||
*/ | ||
static set defaultHttpOptions(value) { | ||
@@ -178,0 +198,0 @@ defaultHttpOptions = _.merge({}, DEFAULT_HTTP_OPTIONS, value); |
@@ -25,2 +25,6 @@ 'use strict'; | ||
/** | ||
* @name constructor | ||
* @api public | ||
*/ | ||
function OpenIDConnectStrategy(options, verify) { | ||
@@ -27,0 +31,0 @@ const opts = (() => { |
{ | ||
"name": "openid-client", | ||
"version": "1.5.3", | ||
"version": "1.6.0", | ||
"description": "OpenID Connect Relying Party (RP, Client) implementation for Node.js servers, supports passportjs", | ||
@@ -5,0 +5,0 @@ "main": "lib/index.js", |
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
69085
1289