class Issuer {
/**
* @name constructor
* @api public
*/
constructor(metadata) {
const properties = Object.assign({}, ISSUER_DEFAULTS, metadata);
if (!properties.introspection_endpoint && properties.token_introspection_endpoint) {
properties.introspection_endpoint = properties.token_introspection_endpoint;
}
if (!properties.revocation_endpoint && properties.token_revocation_endpoint) {
properties.revocation_endpoint = properties.token_revocation_endpoint;
}
_.forEach(properties, (value, key) => {
instance(this).metadata[key] = value;
if (!this[key]) {
Object.defineProperty(this, key, {
get() { return instance(this).metadata[key]; },
});
}
});
instance(this).cache = new LRU({ max: 100 });
registry.set(this.issuer, this);
const self = this;
Object.defineProperty(this, 'Client', {
value: class Client extends BaseClient {
static get issuer() {
return self;
}
get issuer() {
return this.constructor.issuer;
}
},
});
}
/**
* @name inspect
* @api public
*/
inspect() {
return util.format('Issuer <%s>', this.issuer);
}
/**
* @name keystore
* @api private
*/
keystore(reload) {
if (!this.jwks_uri) return Promise.reject(new Error('jwks_uri must be configured'));
const keystore = instance(this).keystore;
const lookupCache = instance(this).cache;
if (reload || !keystore) {
lookupCache.reset();
return got(this.jwks_uri, this.httpOptions())
.then(expectResponse(200))
.then(response => JSON.parse(response.body))
.then(jwks => jose.JWK.asKeyStore(jwks))
.then((joseKeyStore) => {
lookupCache.set('throttle', true, 60 * 1000);
instance(this).keystore = joseKeyStore;
return joseKeyStore;
})
.catch(errorHandler);
}
return Promise.resolve(keystore);
}
/**
* @name key
* @api private
*/
key(def, allowMulti) {
const lookupCache = instance(this).cache;
// refresh keystore on every unknown key but also only upto once every minute
const freshJwksUri = lookupCache.get(def) || lookupCache.get('throttle');
return this.keystore(!freshJwksUri)
.then(store => store.all(def))
.then((keys) => {
assert(keys.length, 'no valid key found');
if (!allowMulti) {
assert.equal(keys.length, 1, 'multiple matching keys, kid must be provided');
lookupCache.set(def, true);
}
return keys[0];
});
}
/**
* @name metadata
* @api public
*/
get metadata() {
return instance(this).metadata;
}
/**
* @name webfinger
* @api public
*/
static webfinger(input) {
const resource = webfingerNormalize(input);
const host = url.parse(resource).host;
const query = { resource, rel: REL };
const opts = { query, followRedirect: true };
const webfingerUrl = `https://${host}${WEBFINGER}`;
return got(webfingerUrl, this.httpOptions(opts))
.then(expectResponse(200))
.then(response => JSON.parse(response.body))
.then((body) => {
const location = _.find(body.links, link => typeof link === 'object' && link.rel === REL && link.href);
assert(location, 'no issuer found in webfinger');
assert(typeof location.href === 'string' && location.href.startsWith('https://'), 'invalid issuer location');
const expectedIssuer = location.href;
if (registry.has(expectedIssuer)) return registry.get(expectedIssuer);
return this.discover(expectedIssuer).then((issuer) => {
try {
assert.equal(issuer.issuer, expectedIssuer, 'discovered issuer mismatch');
} catch (err) {
registry.delete(issuer.issuer);
throw err;
}
return issuer;
});
});
}
/**
* @name discover
* @api public
*/
static discover(uri) {
uri = stripTrailingSlash(uri); // eslint-disable-line no-param ...
n/a
function OpenIDConnectStrategy(options, verify) { const opts = (() => { if (options instanceof Client) return { client: options }; return options; })(); const client = opts.client; assert.equal(client instanceof Client, true); assert.equal(typeof verify, 'function'); assert(client.issuer && client.issuer.issuer, 'client must have an issuer with an identifier'); this._client = client; this._issuer = client.issuer; this._verify = verify; this._params = opts.params || {}; const params = this._params; this.name = url.parse(client.issuer.issuer).hostname; if (!params.response_type) params.response_type = _.get(client, 'response_types[0]', 'code'); if (!params.redirect_uri) params.redirect_uri = _.get(client, 'redirect_uris[0]'); if (!params.scope) params.scope = 'openid'; }
n/a
class TokenSet {
/**
* @name constructor
* @api public
*/
constructor(values) {
Object.assign(this, values);
}
/**
* @name expires_in=
* @api public
*/
set expires_in(value) { // eslint-disable-line camelcase
this.expires_at = now() + Number(value);
}
/**
* @name expires_in
* @api public
*/
get expires_in() { // eslint-disable-line camelcase
return Math.max.apply(null, [this.expires_at - now(), 0]);
}
/**
* @name expired
* @api public
*/
expired() {
return this.expires_in === 0;
}
/**
* @name claims
* @api public
*/
get claims() {
if (decodedClaims.has(this)) return decodedClaims.get(this);
if (!this.id_token) throw new Error('id_token not present in TokenSet');
const decoded = JSON.parse(base64url.decode(this.id_token.split('.')[1]));
decodedClaims.set(this, decoded);
return decoded;
}
}
n/a
open_id_connect_error = function () { Object.defineProperty(this, 'name', { configurable: true, value: className, writable: true }); captureStackTrace(this, this.constructor); setup.apply(this, arguments); }
n/a
function OpenIDConnectStrategy(options, verify) { const opts = (() => { if (options instanceof Client) return { client: options }; return options; })(); const client = opts.client; assert.equal(client instanceof Client, true); assert.equal(typeof verify, 'function'); assert(client.issuer && client.issuer.issuer, 'client must have an issuer with an identifier'); this._client = client; this._issuer = client.issuer; this._verify = verify; this._params = opts.params || {}; const params = this._params; this.name = url.parse(client.issuer.issuer).hostname; if (!params.response_type) params.response_type = _.get(client, 'response_types[0]', 'code'); if (!params.redirect_uri) params.redirect_uri = _.get(client, 'redirect_uris[0]'); if (!params.scope) params.scope = 'openid'; }
n/a
function authenticate(req, options) {
const client = this._client;
const issuer = this._issuer;
try {
if (!req.session) throw new Error('authentication requires session support when using state, max_age or nonce');
const reqParams = client.callbackParams(req);
const sessionKey = `oidc:${url.parse(issuer.issuer).hostname}`;
/* start authentication request */
if (_.isEmpty(reqParams)) {
// provide options objecti with extra authentication parameters
const opts = _.defaults({}, options, this._params, {
state: uuid(),
});
if (!opts.nonce && opts.response_type.includes('id_token')) {
opts.nonce = uuid();
}
req.session[sessionKey] = _.pick(opts, 'nonce', 'state', 'max_age');
this.redirect(client.authorizationUrl(opts));
return;
}
/* end authentication request */
/* start authentication response */
const session = req.session[sessionKey];
const state = _.get(session, 'state');
const maxAge = _.get(session, 'max_age');
const nonce = _.get(session, 'nonce');
if (req.session) delete req.session[sessionKey];
const opts = _.defaults({}, options, {
redirect_uri: this._params.redirect_uri,
});
const checks = { state, nonce, max_age: maxAge };
let callback = client.authorizationCallback(opts.redirect_uri, reqParams, checks)
.then((tokenset) => {
const result = { tokenset };
return result;
});
const loadUserinfo = this._verify.length > 2 && client.issuer.userinfo_endpoint;
if (loadUserinfo) {
callback = callback.then((result) => {
if (result.tokenset.access_token) {
const userinfoRequest = client.userinfo(result.tokenset);
return userinfoRequest.then((userinfo) => {
result.userinfo = userinfo;
return result;
});
}
return result;
});
}
callback.then((result) => {
if (loadUserinfo) {
this._verify(result.tokenset, result.userinfo, verified.bind(this));
} else {
this._verify(result.tokenset, verified.bind(this));
}
}).catch((error) => {
if (error instanceof OpenIdConnectError &&
error.error !== 'server_error' &&
!error.error.startsWith('invalid')) {
this.fail(error);
} else {
this.error(error);
}
});
/* end authentication response */
} catch (err) {
this.error(err);
}
}
...
if (err) return done(err);
return done(null, user);
});
}));
// start authentication request
// options [optional], extra authentication parameters
app.get('/auth', passport.authenticate('oidc', [options]));
// authentication callback
app.get('/auth/cb', passport.authenticate('oidc', { successRedirect: '/', failureRedirect: '/
login' }));
```
## Configuration
...
open_id_connect_error = function () { Object.defineProperty(this, 'name', { configurable: true, value: className, writable: true }); captureStackTrace(this, this.constructor); setup.apply(this, arguments); }
n/a
function Error() { [native code] }
n/a