Initial commit
This commit is contained in:
+15
@@ -0,0 +1,15 @@
|
||||
const logger = require('debug')('jwks');
|
||||
const memoizer = require('lru-memoizer');
|
||||
const { promisify, callbackify } = require('util');
|
||||
|
||||
function cacheWrapper(client, { cacheMaxEntries = 5, cacheMaxAge = 600000 }) {
|
||||
logger(`Configured caching of signing keys. Max: ${cacheMaxEntries} / Age: ${cacheMaxAge}`);
|
||||
return promisify(memoizer({
|
||||
hash: (kid) => kid,
|
||||
load: callbackify(client.getSigningKey.bind(client)),
|
||||
maxAge: cacheMaxAge,
|
||||
max: cacheMaxEntries
|
||||
}));
|
||||
}
|
||||
|
||||
module.exports.default = cacheWrapper;
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
const { callbackify } = require('util');
|
||||
|
||||
const callbackSupport = (client) => {
|
||||
const getSigningKey = client.getSigningKey.bind(client);
|
||||
|
||||
return (kid, cb) => {
|
||||
if (cb) {
|
||||
const callbackFunc = callbackify(getSigningKey);
|
||||
return callbackFunc(kid, cb);
|
||||
}
|
||||
|
||||
return getSigningKey(kid);
|
||||
};
|
||||
};
|
||||
|
||||
module.exports.default = callbackSupport;
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
request: require('./request').default,
|
||||
cacheSigningKey: require('./cache').default,
|
||||
rateLimitSigningKey: require('./rateLimit').default,
|
||||
getKeysInterceptor: require('./interceptor').default,
|
||||
callbackSupport: require('./callbackSupport').default
|
||||
};
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
const retrieveSigningKeys = require('../utils').retrieveSigningKeys;
|
||||
|
||||
/**
|
||||
* Uses getKeysInterceptor to allow users to retrieve keys from a file,
|
||||
* external cache, or provided object before falling back to the jwksUri endpoint
|
||||
*/
|
||||
function getKeysInterceptor(client, { getKeysInterceptor }) {
|
||||
const getSigningKey = client.getSigningKey.bind(client);
|
||||
|
||||
return async (kid) => {
|
||||
const keys = await getKeysInterceptor();
|
||||
|
||||
let signingKeys;
|
||||
if (keys && keys.length) {
|
||||
signingKeys = retrieveSigningKeys(keys);
|
||||
}
|
||||
|
||||
if (signingKeys && signingKeys.length) {
|
||||
const key = signingKeys.find(k => !kid || k.kid === kid);
|
||||
|
||||
if (key) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
return getSigningKey(kid);
|
||||
};
|
||||
}
|
||||
|
||||
module.exports.default = getKeysInterceptor;
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
const logger = require('debug')('jwks');
|
||||
const { RateLimiter } = require('limiter');
|
||||
|
||||
const JwksRateLimitError = require('../errors/JwksRateLimitError');
|
||||
|
||||
function rateLimitWrapper(client, { jwksRequestsPerMinute = 10 }) {
|
||||
const getSigningKey = client.getSigningKey.bind(client);
|
||||
|
||||
const limiter = new RateLimiter(jwksRequestsPerMinute, 'minute', true);
|
||||
logger(`Configured rate limiting to JWKS endpoint at ${jwksRequestsPerMinute}/minute`);
|
||||
|
||||
return async (kid) => await new Promise((resolve, reject) => {
|
||||
limiter.removeTokens(1, async (err, remaining) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
}
|
||||
|
||||
logger('Requests to the JWKS endpoint available for the next minute:', remaining);
|
||||
if (remaining < 0) {
|
||||
logger('Too many requests to the JWKS endpoint');
|
||||
reject(new JwksRateLimitError('Too many requests to the JWKS endpoint'));
|
||||
} else {
|
||||
try {
|
||||
const key = await getSigningKey(kid);
|
||||
resolve(key);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports.default = rateLimitWrapper;
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
const http = require('http');
|
||||
const https = require('https');
|
||||
const urlUtil = require('url');
|
||||
|
||||
module.exports.default = (options) => {
|
||||
if (options.fetcher) {
|
||||
return options.fetcher(options.uri);
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const {
|
||||
hostname,
|
||||
path,
|
||||
port,
|
||||
protocol
|
||||
} = urlUtil.parse(options.uri);
|
||||
|
||||
const requestOptions = {
|
||||
hostname,
|
||||
path,
|
||||
port,
|
||||
method: 'GET',
|
||||
...(options.headers && { headers: { ...options.headers } }),
|
||||
...(options.timeout && { timeout: options.timeout }),
|
||||
...(options.agent && { agent: options.agent })
|
||||
};
|
||||
|
||||
const httpRequestLib = protocol === 'https:' ? https : http;
|
||||
const httpRequest = httpRequestLib.request(requestOptions, (res) => {
|
||||
let rawData = '';
|
||||
res.setEncoding('utf8');
|
||||
res.on('data', (chunk) => { rawData += chunk; });
|
||||
res.on('end', () => {
|
||||
if (res.statusCode < 200 || res.statusCode >= 300) {
|
||||
const errorMsg = res.body && (res.body.message || res.body) || res.statusMessage || `Http Error ${res.statusCode}`;
|
||||
reject({ errorMsg });
|
||||
} else {
|
||||
try {
|
||||
resolve(rawData && JSON.parse(rawData));
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
httpRequest
|
||||
.on('timeout', () => httpRequest.destroy())
|
||||
.on('error', (e) => reject(e))
|
||||
.end();
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user