dev testing

This commit is contained in:
2026-03-23 15:29:13 -04:00
parent 28dae0dc60
commit d772b7ec9c
5664 changed files with 863006 additions and 73 deletions

91
functions/node_modules/jwks-rsa/src/JwksClient.js generated vendored Normal file
View File

@@ -0,0 +1,91 @@
const logger = require('debug')('jwks');
const { retrieveSigningKeys } = require('./utils') ;
const { request, cacheSigningKey, rateLimitSigningKey, getKeysInterceptor, callbackSupport } = require('./wrappers');
const JwksError = require('./errors/JwksError');
const SigningKeyNotFoundError = require('./errors/SigningKeyNotFoundError');
class JwksClient {
constructor(options) {
this.options = {
rateLimit: false,
cache: true,
timeout: 30000,
...options
};
// Initialize wrappers.
if (this.options.getKeysInterceptor) {
this.getSigningKey = getKeysInterceptor(this, options);
}
if (this.options.rateLimit) {
this.getSigningKey = rateLimitSigningKey(this, options);
}
if (this.options.cache) {
this.getSigningKey = cacheSigningKey(this, options);
}
this.getSigningKey = callbackSupport(this, options);
}
async getKeys() {
logger(`Fetching keys from '${this.options.jwksUri}'`);
try {
const res = await request({
uri: this.options.jwksUri,
headers: this.options.requestHeaders,
agent: this.options.requestAgent,
timeout: this.options.timeout,
fetcher: this.options.fetcher
});
logger('Keys:', res.keys);
return res.keys;
} catch (err) {
const { errorMsg } = err;
logger('Failure:', errorMsg || err);
throw (errorMsg ? new JwksError(errorMsg) : err);
}
}
async getSigningKeys() {
const keys = await this.getKeys();
if (!keys || !keys.length) {
throw new JwksError('The JWKS endpoint did not contain any keys');
}
const signingKeys = await retrieveSigningKeys(keys);
if (!signingKeys.length) {
throw new JwksError('The JWKS endpoint did not contain any signing keys');
}
logger('Signing Keys:', signingKeys);
return signingKeys;
}
async getSigningKey (kid) {
logger(`Fetching signing key for '${kid}'`);
const keys = await this.getSigningKeys();
const kidDefined = kid !== undefined && kid !== null;
if (!kidDefined && keys.length > 1) {
logger('No KID specified and JWKS endpoint returned more than 1 key');
throw new SigningKeyNotFoundError('No KID specified and JWKS endpoint returned more than 1 key');
}
const key = keys.find(k => !kidDefined || k.kid === kid);
if (key) {
return key;
} else {
logger(`Unable to find a signing key that matches '${kid}'`);
throw new SigningKeyNotFoundError(`Unable to find a signing key that matches '${kid}'`);
}
}
}
module.exports = {
JwksClient
};

View File

@@ -0,0 +1,10 @@
function ArgumentError(message) {
Error.call(this, message);
Error.captureStackTrace(this, this.constructor);
this.name = 'ArgumentError';
this.message = message;
}
ArgumentError.prototype = Object.create(Error.prototype);
ArgumentError.prototype.constructor = ArgumentError;
module.exports = ArgumentError;

View File

@@ -0,0 +1,10 @@
function JwksError(message) {
Error.call(this, message);
Error.captureStackTrace(this, this.constructor);
this.name = 'JwksError';
this.message = message;
}
JwksError.prototype = Object.create(Error.prototype);
JwksError.prototype.constructor = JwksError;
module.exports = JwksError;

View File

@@ -0,0 +1,10 @@
function JwksRateLimitError(message) {
Error.call(this, message);
Error.captureStackTrace(this, this.constructor);
this.name = 'JwksRateLimitError';
this.message = message;
}
JwksRateLimitError.prototype = Object.create(Error.prototype);
JwksRateLimitError.prototype.constructor = JwksRateLimitError;
module.exports = JwksRateLimitError;

View File

@@ -0,0 +1,10 @@
function SigningKeyNotFoundError(message) {
Error.call(this, message);
Error.captureStackTrace(this, this.constructor);
this.name = 'SigningKeyNotFoundError';
this.message = message;
}
SigningKeyNotFoundError.prototype = Object.create(Error.prototype);
SigningKeyNotFoundError.prototype.constructor = SigningKeyNotFoundError;
module.exports = SigningKeyNotFoundError;

6
functions/node_modules/jwks-rsa/src/errors/index.js generated vendored Normal file
View File

@@ -0,0 +1,6 @@
module.exports = {
ArgumentError: require('./ArgumentError'),
JwksError: require('./JwksError'),
JwksRateLimitError: require('./JwksRateLimitError'),
SigningKeyNotFoundError: require('./SigningKeyNotFoundError')
};

22
functions/node_modules/jwks-rsa/src/index.js generated vendored Normal file
View File

@@ -0,0 +1,22 @@
const { JwksClient } = require('./JwksClient');
const errors = require('./errors');
const { hapiJwt2Key, hapiJwt2KeyAsync } = require('./integrations/hapi');
const { expressJwtSecret } = require('./integrations/express');
const { koaJwtSecret } = require('./integrations/koa');
const { passportJwtSecret } = require('./integrations/passport');
module.exports = (options) => {
return new JwksClient(options);
};
module.exports.JwksClient = JwksClient;
module.exports.ArgumentError = errors.ArgumentError;
module.exports.JwksError = errors.JwksError;
module.exports.JwksRateLimitError = errors.JwksRateLimitError;
module.exports.SigningKeyNotFoundError = errors.SigningKeyNotFoundError;
module.exports.expressJwtSecret = expressJwtSecret;
module.exports.hapiJwt2Key = hapiJwt2Key;
module.exports.hapiJwt2KeyAsync = hapiJwt2KeyAsync;
module.exports.koaJwtSecret = koaJwtSecret;
module.exports.passportJwtSecret = passportJwtSecret;

View File

@@ -0,0 +1,15 @@
const allowedSignatureAlg = [
'RS256',
'RS384',
'RS512',
'PS256',
'PS384',
'PS512',
'ES256',
'ES256K',
'ES384',
'ES512',
'EdDSA'
];
module.exports = allowedSignatureAlg;

View File

@@ -0,0 +1,60 @@
const { ArgumentError } = require('../errors');
const { JwksClient } = require('../JwksClient');
const supportedAlg = require('./config');
const handleSigningKeyError = (err, cb) => {
// If we didn't find a match, can't provide a key.
if (err && err.name === 'SigningKeyNotFoundError') {
return cb(null);
}
// If an error occured like rate limiting or HTTP issue, we'll bubble up the error.
if (err) {
return cb(err);
}
};
module.exports.expressJwtSecret = function (options) {
if (options === null || options === undefined) {
throw new ArgumentError('An options object must be provided when initializing expressJwtSecret');
}
const client = new JwksClient(options);
const onError = options.handleSigningKeyError || handleSigningKeyError;
const expressJwt7Provider = async (req, token) => {
if (!token) { return; }
const header = token.header;
if (!header || !supportedAlg.includes(header.alg)) {
return;
}
try {
const key = await client.getSigningKey(header.kid);
return key.publicKey || key.rsaPublicKey;
} catch (err) {
return new Promise((resolve, reject) => {
onError(err, (newError) => {
if (!newError) { return resolve(); }
reject(newError);
});
});
}
};
return function secretProvider(req, header, payload, cb) {
//This function has 4 parameters to make it work with express-jwt@6
//but it also supports express-jwt@7 which only has 2.
if (arguments.length === 4) {
expressJwt7Provider(req, { header })
.then(key => {
setImmediate(cb, null, key);
}).catch(err => {
setImmediate(cb, err);
});
return;
}
return expressJwt7Provider(req, arguments[1]);
};
};

View File

@@ -0,0 +1,59 @@
const { ArgumentError } = require('../errors');
const { JwksClient } = require('../JwksClient');
const supportedAlg = require('./config');
const handleSigningKeyError = (err, cb) => {
// If we didn't find a match, can't provide a key.
if (err && err.name === 'SigningKeyNotFoundError') {
return cb(err, null, null);
}
// If an error occured like rate limiting or HTTP issue, we'll bubble up the error.
if (err) {
return cb(err, null, null);
}
};
/**
* Call hapiJwt2Key as a Promise
* @param {object} options
* @returns {Promise}
*/
module.exports.hapiJwt2KeyAsync = (options) => {
const secretProvider = module.exports.hapiJwt2Key(options);
return function(decoded) {
return new Promise((resolve, reject) => {
const cb = (err, key) => {
(!key || err) ? reject(err) : resolve({ key });
};
secretProvider(decoded, cb);
});
};
};
module.exports.hapiJwt2Key = function (options) {
if (options === null || options === undefined) {
throw new ArgumentError('An options object must be provided when initializing hapiJwt2Key');
}
const client = new JwksClient(options);
const onError = options.handleSigningKeyError || handleSigningKeyError;
return function secretProvider(decoded, cb) {
// We cannot find a signing certificate if there is no header (no kid).
if (!decoded || !decoded.header) {
return cb(new Error('Cannot find a signing certificate if there is no header'), null, null);
}
if (!supportedAlg.includes(decoded.header.alg)) {
return cb(new Error('Unsupported algorithm ' + decoded.header.alg + ' supplied.'), null, null);
}
client.getSigningKey(decoded.header.kid)
.then(key => {
return cb(null, key.publicKey || key.rsaPublicKey, key);
}).catch(err => {
return onError(err, (newError) => cb(newError, null, null));
});
};
};

View File

@@ -0,0 +1,30 @@
const { ArgumentError } = require('../errors');
const { JwksClient } = require('../JwksClient');
const supportedAlg = require('./config');
module.exports.koaJwtSecret = function (options = {}) {
if (!options.jwksUri) {
throw new ArgumentError('No JWKS provided. Please provide a jwksUri');
}
const client = new JwksClient(options);
return function secretProvider({ alg, kid } = {}) {
return new Promise((resolve, reject) => {
if (!supportedAlg.includes(alg)) {
return reject(new Error('Missing / invalid token algorithm'));
}
client.getSigningKey(kid)
.then(key => {
resolve(key.publicKey || key.rsaPublicKey);
}).catch(err => {
if (options.handleSigningKeyError) {
return options.handleSigningKeyError(err).then(reject);
}
return reject(err);
});
});
};
};

View File

@@ -0,0 +1,52 @@
const jose = require('jose');
const { ArgumentError } = require('../errors');
const { JwksClient } = require('../JwksClient');
const supportedAlg = require('./config');
const handleSigningKeyError = (err, cb) => {
// If we didn't find a match, can't provide a key.
if (err && err.name === 'SigningKeyNotFoundError') {
return cb(null);
}
// If an error occured like rate limiting or HTTP issue, we'll bubble up the error.
if (err) {
return cb(err);
}
};
module.exports.passportJwtSecret = function (options) {
if (options === null || options === undefined) {
throw new ArgumentError('An options object must be provided when initializing passportJwtSecret');
}
if (!options.jwksUri) {
throw new ArgumentError('No JWKS provided. Please provide a jwksUri');
}
const client = new JwksClient(options);
const onError = options.handleSigningKeyError || handleSigningKeyError;
return function secretProvider(req, rawJwtToken, cb) {
let decoded;
try {
decoded = {
payload: jose.decodeJwt(rawJwtToken),
header: jose.decodeProtectedHeader(rawJwtToken)
};
} catch (err) {
decoded = null;
}
if (!decoded || !supportedAlg.includes(decoded.header.alg)) {
return cb(null, null);
}
client.getSigningKey(decoded.header.kid)
.then(key => {
cb(null, key.publicKey || key.rsaPublicKey);
}).catch(err => {
onError(err, (newError) => cb(newError, null));
});
};
};

80
functions/node_modules/jwks-rsa/src/utils.js generated vendored Normal file
View File

@@ -0,0 +1,80 @@
const jose = require('jose');
const JwksError = require('./errors/JwksError');
function resolveAlg(jwk) {
if (jwk.alg) {
return jwk.alg;
}
if (jwk.kty === 'RSA') {
return 'RS256';
}
if (jwk.kty === 'EC') {
switch (jwk.crv) {
case 'P-256':
return 'ES256';
case 'secp256k1':
return 'ES256K';
case 'P-384':
return 'ES384';
case 'P-521':
return 'ES512';
}
}
if (jwk.kty === 'OKP') {
switch (jwk.crv) {
case 'Ed25519':
case 'Ed448':
return 'EdDSA';
}
}
throw new JwksError('Unsupported JWK');
}
async function retrieveSigningKeys(jwks) {
const results = [];
jwks = jwks
.filter(({ use }) => use === 'sig' || use === undefined)
.filter(({ kty }) => kty === 'RSA' || kty === 'EC' || kty === 'OKP');
for (const jwk of jwks) {
try {
const key = await jose.importJWK({ ...jwk, ext: true }, resolveAlg(jwk));
if (key.type !== 'public') {
continue;
}
let getSpki;
switch (key[Symbol.toStringTag]) {
case 'CryptoKey': {
const spki = await jose.exportSPKI(key);
getSpki = () => spki;
break;
}
case 'KeyObject':
// Assume legacy Node.js version without the Symbol.toStringTag backported
// Fall through
default:
getSpki = () => key.export({ format: 'pem', type: 'spki' });
}
results.push({
get publicKey() { return getSpki(); },
get rsaPublicKey() { return getSpki(); },
getPublicKey() { return getSpki(); },
...(typeof jwk.kid === 'string' && jwk.kid ? { kid: jwk.kid } : undefined),
...(typeof jwk.alg === 'string' && jwk.alg ? { alg: jwk.alg } : undefined)
});
} catch (err) {
continue;
}
}
return results;
}
module.exports = {
retrieveSigningKeys
};

15
functions/node_modules/jwks-rsa/src/wrappers/cache.js generated vendored Normal file
View File

@@ -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;

View File

@@ -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;

View File

@@ -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
};

View File

@@ -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 = await 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;

View File

@@ -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;

View File

@@ -0,0 +1,54 @@
const http = require('http');
const https = require('https');
const ArgumentError = require('../errors/ArgumentError');
module.exports.default = (options) => {
if (options.fetcher) {
return options.fetcher(options.uri);
}
return new Promise((resolve, reject) => {
let url;
try {
url = new URL(options.uri);
} catch (err) {
throw new ArgumentError('Invalid JWKS URI: The provided URI is not a valid URL.');
}
const { hostname, port, protocol, pathname, search } = url;
const path = pathname + search;
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();
});
};