112 lines
2.7 KiB
JavaScript
112 lines
2.7 KiB
JavaScript
const objectHash = require('object-hash');
|
|
const LRU = require('lru-cache');
|
|
|
|
const { RPError } = require('../errors');
|
|
|
|
const { assertIssuerConfiguration } = require('./assert');
|
|
const KeyStore = require('./keystore');
|
|
const { keystores } = require('./weak_cache');
|
|
const processResponse = require('./process_response');
|
|
const request = require('./request');
|
|
|
|
const inFlight = new WeakMap();
|
|
const caches = new WeakMap();
|
|
const lrus = (ctx) => {
|
|
if (!caches.has(ctx)) {
|
|
caches.set(ctx, new LRU({ max: 100 }));
|
|
}
|
|
return caches.get(ctx);
|
|
};
|
|
|
|
async function getKeyStore(reload = false) {
|
|
assertIssuerConfiguration(this, 'jwks_uri');
|
|
|
|
const keystore = keystores.get(this);
|
|
const cache = lrus(this);
|
|
|
|
if (reload || !keystore) {
|
|
if (inFlight.has(this)) {
|
|
return inFlight.get(this);
|
|
}
|
|
cache.reset();
|
|
inFlight.set(
|
|
this,
|
|
(async () => {
|
|
const response = await request
|
|
.call(this, {
|
|
method: 'GET',
|
|
responseType: 'json',
|
|
url: this.jwks_uri,
|
|
headers: {
|
|
Accept: 'application/json, application/jwk-set+json',
|
|
},
|
|
})
|
|
.finally(() => {
|
|
inFlight.delete(this);
|
|
});
|
|
const jwks = processResponse(response);
|
|
|
|
const joseKeyStore = KeyStore.fromJWKS(jwks, { onlyPublic: true });
|
|
cache.set('throttle', true, 60 * 1000);
|
|
keystores.set(this, joseKeyStore);
|
|
|
|
return joseKeyStore;
|
|
})(),
|
|
);
|
|
|
|
return inFlight.get(this);
|
|
}
|
|
|
|
return keystore;
|
|
}
|
|
|
|
async function queryKeyStore({ kid, kty, alg, use }, { allowMulti = false } = {}) {
|
|
const cache = lrus(this);
|
|
|
|
const def = {
|
|
kid,
|
|
kty,
|
|
alg,
|
|
use,
|
|
};
|
|
|
|
const defHash = objectHash(def, {
|
|
algorithm: 'sha256',
|
|
ignoreUnknown: true,
|
|
unorderedArrays: true,
|
|
unorderedSets: true,
|
|
respectType: false,
|
|
});
|
|
|
|
// refresh keystore on every unknown key but also only upto once every minute
|
|
const freshJwksUri = cache.get(defHash) || cache.get('throttle');
|
|
|
|
const keystore = await getKeyStore.call(this, !freshJwksUri);
|
|
const keys = keystore.all(def);
|
|
|
|
delete def.use;
|
|
if (keys.length === 0) {
|
|
throw new RPError({
|
|
printf: ["no valid key found in issuer's jwks_uri for key parameters %j", def],
|
|
jwks: keystore,
|
|
});
|
|
}
|
|
|
|
if (!allowMulti && keys.length > 1 && !kid) {
|
|
throw new RPError({
|
|
printf: [
|
|
"multiple matching keys found in issuer's jwks_uri for key parameters %j, kid must be provided in this case",
|
|
def,
|
|
],
|
|
jwks: keystore,
|
|
});
|
|
}
|
|
|
|
cache.set(defHash, true);
|
|
|
|
return keys;
|
|
}
|
|
|
|
module.exports.queryKeyStore = queryKeyStore;
|
|
module.exports.keystore = getKeyStore;
|