Auto-commit 2026-04-29 16:31
This commit is contained in:
24
node_modules/openid-client/lib/helpers/assert.js
generated
vendored
Normal file
24
node_modules/openid-client/lib/helpers/assert.js
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
function assertSigningAlgValuesSupport(endpoint, issuer, properties) {
|
||||
if (!issuer[`${endpoint}_endpoint`]) return;
|
||||
|
||||
const eam = `${endpoint}_endpoint_auth_method`;
|
||||
const easa = `${endpoint}_endpoint_auth_signing_alg`;
|
||||
const easavs = `${endpoint}_endpoint_auth_signing_alg_values_supported`;
|
||||
|
||||
if (properties[eam] && properties[eam].endsWith('_jwt') && !properties[easa] && !issuer[easavs]) {
|
||||
throw new TypeError(
|
||||
`${easavs} must be configured on the issuer if ${easa} is not defined on a client`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function assertIssuerConfiguration(issuer, endpoint) {
|
||||
if (!issuer[endpoint]) {
|
||||
throw new TypeError(`${endpoint} must be configured on the issuer`);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
assertSigningAlgValuesSupport,
|
||||
assertIssuerConfiguration,
|
||||
};
|
||||
13
node_modules/openid-client/lib/helpers/base64url.js
generated
vendored
Normal file
13
node_modules/openid-client/lib/helpers/base64url.js
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
let encode;
|
||||
if (Buffer.isEncoding('base64url')) {
|
||||
encode = (input, encoding = 'utf8') => Buffer.from(input, encoding).toString('base64url');
|
||||
} else {
|
||||
const fromBase64 = (base64) => base64.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
|
||||
encode = (input, encoding = 'utf8') =>
|
||||
fromBase64(Buffer.from(input, encoding).toString('base64'));
|
||||
}
|
||||
|
||||
const decode = (input) => Buffer.from(input, 'base64');
|
||||
|
||||
module.exports.decode = decode;
|
||||
module.exports.encode = encode;
|
||||
208
node_modules/openid-client/lib/helpers/client.js
generated
vendored
Normal file
208
node_modules/openid-client/lib/helpers/client.js
generated
vendored
Normal file
@@ -0,0 +1,208 @@
|
||||
const jose = require('jose');
|
||||
|
||||
const { RPError } = require('../errors');
|
||||
|
||||
const { assertIssuerConfiguration } = require('./assert');
|
||||
const { random } = require('./generators');
|
||||
const now = require('./unix_timestamp');
|
||||
const request = require('./request');
|
||||
const { keystores } = require('./weak_cache');
|
||||
const merge = require('./merge');
|
||||
|
||||
// TODO: in v6.x additionally encode the `- _ . ! ~ * ' ( )` characters
|
||||
// https://github.com/panva/node-openid-client/commit/5a2ea80ef5e59ec0c03dbd97d82f551e24a9d348
|
||||
const formUrlEncode = (value) => encodeURIComponent(value).replace(/%20/g, '+');
|
||||
|
||||
async function clientAssertion(endpoint, payload) {
|
||||
let alg = this[`${endpoint}_endpoint_auth_signing_alg`];
|
||||
if (!alg) {
|
||||
assertIssuerConfiguration(
|
||||
this.issuer,
|
||||
`${endpoint}_endpoint_auth_signing_alg_values_supported`,
|
||||
);
|
||||
}
|
||||
|
||||
if (this[`${endpoint}_endpoint_auth_method`] === 'client_secret_jwt') {
|
||||
if (!alg) {
|
||||
const supported = this.issuer[`${endpoint}_endpoint_auth_signing_alg_values_supported`];
|
||||
alg =
|
||||
Array.isArray(supported) && supported.find((signAlg) => /^HS(?:256|384|512)/.test(signAlg));
|
||||
}
|
||||
|
||||
if (!alg) {
|
||||
throw new RPError(
|
||||
`failed to determine a JWS Algorithm to use for ${
|
||||
this[`${endpoint}_endpoint_auth_method`]
|
||||
} Client Assertion`,
|
||||
);
|
||||
}
|
||||
|
||||
return new jose.CompactSign(Buffer.from(JSON.stringify(payload)))
|
||||
.setProtectedHeader({ alg })
|
||||
.sign(this.secretForAlg(alg));
|
||||
}
|
||||
|
||||
const keystore = await keystores.get(this);
|
||||
|
||||
if (!keystore) {
|
||||
throw new TypeError('no client jwks provided for signing a client assertion with');
|
||||
}
|
||||
|
||||
if (!alg) {
|
||||
const supported = this.issuer[`${endpoint}_endpoint_auth_signing_alg_values_supported`];
|
||||
alg =
|
||||
Array.isArray(supported) &&
|
||||
supported.find((signAlg) => keystore.get({ alg: signAlg, use: 'sig' }));
|
||||
}
|
||||
|
||||
if (!alg) {
|
||||
throw new RPError(
|
||||
`failed to determine a JWS Algorithm to use for ${
|
||||
this[`${endpoint}_endpoint_auth_method`]
|
||||
} Client Assertion`,
|
||||
);
|
||||
}
|
||||
|
||||
const key = keystore.get({ alg, use: 'sig' });
|
||||
if (!key) {
|
||||
throw new RPError(
|
||||
`no key found in client jwks to sign a client assertion with using alg ${alg}`,
|
||||
);
|
||||
}
|
||||
|
||||
return new jose.CompactSign(Buffer.from(JSON.stringify(payload)))
|
||||
.setProtectedHeader({ alg, kid: key.jwk && key.jwk.kid })
|
||||
.sign(await key.keyObject(alg));
|
||||
}
|
||||
|
||||
async function authFor(endpoint, { clientAssertionPayload } = {}) {
|
||||
const authMethod = this[`${endpoint}_endpoint_auth_method`];
|
||||
switch (authMethod) {
|
||||
case 'self_signed_tls_client_auth':
|
||||
case 'tls_client_auth':
|
||||
case 'none':
|
||||
return { form: { client_id: this.client_id } };
|
||||
case 'client_secret_post':
|
||||
if (typeof this.client_secret !== 'string') {
|
||||
throw new TypeError(
|
||||
'client_secret_post client authentication method requires a client_secret',
|
||||
);
|
||||
}
|
||||
return { form: { client_id: this.client_id, client_secret: this.client_secret } };
|
||||
case 'private_key_jwt':
|
||||
case 'client_secret_jwt': {
|
||||
const timestamp = now();
|
||||
|
||||
const assertion = await clientAssertion.call(this, endpoint, {
|
||||
iat: timestamp,
|
||||
exp: timestamp + 60,
|
||||
jti: random(),
|
||||
iss: this.client_id,
|
||||
sub: this.client_id,
|
||||
aud: this.issuer.issuer,
|
||||
...clientAssertionPayload,
|
||||
});
|
||||
|
||||
return {
|
||||
form: {
|
||||
client_id: this.client_id,
|
||||
client_assertion: assertion,
|
||||
client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
|
||||
},
|
||||
};
|
||||
}
|
||||
case 'client_secret_basic': {
|
||||
// This is correct behaviour, see https://tools.ietf.org/html/rfc6749#section-2.3.1 and the
|
||||
// related appendix. (also https://github.com/panva/node-openid-client/pull/91)
|
||||
// > The client identifier is encoded using the
|
||||
// > "application/x-www-form-urlencoded" encoding algorithm per
|
||||
// > Appendix B, and the encoded value is used as the username; the client
|
||||
// > password is encoded using the same algorithm and used as the
|
||||
// > password.
|
||||
if (typeof this.client_secret !== 'string') {
|
||||
throw new TypeError(
|
||||
'client_secret_basic client authentication method requires a client_secret',
|
||||
);
|
||||
}
|
||||
const encoded = `${formUrlEncode(this.client_id)}:${formUrlEncode(this.client_secret)}`;
|
||||
const value = Buffer.from(encoded).toString('base64');
|
||||
return { headers: { Authorization: `Basic ${value}` } };
|
||||
}
|
||||
default: {
|
||||
throw new TypeError(`missing, or unsupported, ${endpoint}_endpoint_auth_method`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function resolveResponseType() {
|
||||
const { length, 0: value } = this.response_types;
|
||||
|
||||
if (length === 1) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function resolveRedirectUri() {
|
||||
const { length, 0: value } = this.redirect_uris || [];
|
||||
|
||||
if (length === 1) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async function authenticatedPost(
|
||||
endpoint,
|
||||
opts,
|
||||
{ clientAssertionPayload, endpointAuthMethod = endpoint, DPoP } = {},
|
||||
) {
|
||||
const auth = await authFor.call(this, endpointAuthMethod, { clientAssertionPayload });
|
||||
const requestOpts = merge(opts, auth);
|
||||
|
||||
const mTLS =
|
||||
this[`${endpointAuthMethod}_endpoint_auth_method`].includes('tls_client_auth') ||
|
||||
(endpoint === 'token' && this.tls_client_certificate_bound_access_tokens);
|
||||
|
||||
let targetUrl;
|
||||
if (mTLS && this.issuer.mtls_endpoint_aliases) {
|
||||
targetUrl = this.issuer.mtls_endpoint_aliases[`${endpoint}_endpoint`];
|
||||
}
|
||||
|
||||
targetUrl = targetUrl || this.issuer[`${endpoint}_endpoint`];
|
||||
|
||||
if ('form' in requestOpts) {
|
||||
for (const [key, value] of Object.entries(requestOpts.form)) {
|
||||
if (typeof value === 'undefined') {
|
||||
delete requestOpts.form[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return request.call(
|
||||
this,
|
||||
{
|
||||
...requestOpts,
|
||||
method: 'POST',
|
||||
url: targetUrl,
|
||||
headers: {
|
||||
...(endpoint !== 'revocation'
|
||||
? {
|
||||
Accept: 'application/json',
|
||||
}
|
||||
: undefined),
|
||||
...requestOpts.headers,
|
||||
},
|
||||
},
|
||||
{ mTLS, DPoP },
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
resolveResponseType,
|
||||
resolveRedirectUri,
|
||||
authFor,
|
||||
authenticatedPost,
|
||||
};
|
||||
7
node_modules/openid-client/lib/helpers/consts.js
generated
vendored
Normal file
7
node_modules/openid-client/lib/helpers/consts.js
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
const HTTP_OPTIONS = Symbol();
|
||||
const CLOCK_TOLERANCE = Symbol();
|
||||
|
||||
module.exports = {
|
||||
CLOCK_TOLERANCE,
|
||||
HTTP_OPTIONS,
|
||||
};
|
||||
27
node_modules/openid-client/lib/helpers/decode_jwt.js
generated
vendored
Normal file
27
node_modules/openid-client/lib/helpers/decode_jwt.js
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
const base64url = require('./base64url');
|
||||
|
||||
module.exports = (token) => {
|
||||
if (typeof token !== 'string' || !token) {
|
||||
throw new TypeError('JWT must be a string');
|
||||
}
|
||||
|
||||
const { 0: header, 1: payload, 2: signature, length } = token.split('.');
|
||||
|
||||
if (length === 5) {
|
||||
throw new TypeError('encrypted JWTs cannot be decoded');
|
||||
}
|
||||
|
||||
if (length !== 3) {
|
||||
throw new Error('JWTs must have three components');
|
||||
}
|
||||
|
||||
try {
|
||||
return {
|
||||
header: JSON.parse(base64url.decode(header)),
|
||||
payload: JSON.parse(base64url.decode(payload)),
|
||||
signature,
|
||||
};
|
||||
} catch (err) {
|
||||
throw new Error('JWT is malformed');
|
||||
}
|
||||
};
|
||||
1
node_modules/openid-client/lib/helpers/deep_clone.js
generated
vendored
Normal file
1
node_modules/openid-client/lib/helpers/deep_clone.js
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = globalThis.structuredClone || ((obj) => JSON.parse(JSON.stringify(obj)));
|
||||
27
node_modules/openid-client/lib/helpers/defaults.js
generated
vendored
Normal file
27
node_modules/openid-client/lib/helpers/defaults.js
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
const isPlainObject = require('./is_plain_object');
|
||||
|
||||
function defaults(deep, target, ...sources) {
|
||||
for (const source of sources) {
|
||||
if (!isPlainObject(source)) {
|
||||
continue;
|
||||
}
|
||||
for (const [key, value] of Object.entries(source)) {
|
||||
/* istanbul ignore if */
|
||||
if (key === '__proto__' || key === 'constructor') {
|
||||
continue;
|
||||
}
|
||||
if (typeof target[key] === 'undefined' && typeof value !== 'undefined') {
|
||||
target[key] = value;
|
||||
}
|
||||
|
||||
if (deep && isPlainObject(target[key]) && isPlainObject(value)) {
|
||||
defaults(true, target[key], value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
module.exports = defaults.bind(undefined, false);
|
||||
module.exports.deep = defaults.bind(undefined, true);
|
||||
14
node_modules/openid-client/lib/helpers/generators.js
generated
vendored
Normal file
14
node_modules/openid-client/lib/helpers/generators.js
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
const { createHash, randomBytes } = require('crypto');
|
||||
|
||||
const base64url = require('./base64url');
|
||||
|
||||
const random = (bytes = 32) => base64url.encode(randomBytes(bytes));
|
||||
|
||||
module.exports = {
|
||||
random,
|
||||
state: random,
|
||||
nonce: random,
|
||||
codeVerifier: random,
|
||||
codeChallenge: (codeVerifier) =>
|
||||
base64url.encode(createHash('sha256').update(codeVerifier).digest()),
|
||||
};
|
||||
4
node_modules/openid-client/lib/helpers/is_key_object.js
generated
vendored
Normal file
4
node_modules/openid-client/lib/helpers/is_key_object.js
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
const util = require('util');
|
||||
const crypto = require('crypto');
|
||||
|
||||
module.exports = util.types.isKeyObject || ((obj) => obj && obj instanceof crypto.KeyObject);
|
||||
1
node_modules/openid-client/lib/helpers/is_plain_object.js
generated
vendored
Normal file
1
node_modules/openid-client/lib/helpers/is_plain_object.js
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = (a) => !!a && a.constructor === Object;
|
||||
111
node_modules/openid-client/lib/helpers/issuer.js
generated
vendored
Normal file
111
node_modules/openid-client/lib/helpers/issuer.js
generated
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
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;
|
||||
298
node_modules/openid-client/lib/helpers/keystore.js
generated
vendored
Normal file
298
node_modules/openid-client/lib/helpers/keystore.js
generated
vendored
Normal file
@@ -0,0 +1,298 @@
|
||||
const jose = require('jose');
|
||||
|
||||
const clone = require('./deep_clone');
|
||||
const isPlainObject = require('./is_plain_object');
|
||||
|
||||
const internal = Symbol();
|
||||
|
||||
const keyscore = (key, { alg, use }) => {
|
||||
let score = 0;
|
||||
|
||||
if (alg && key.alg) {
|
||||
score++;
|
||||
}
|
||||
|
||||
if (use && key.use) {
|
||||
score++;
|
||||
}
|
||||
|
||||
return score;
|
||||
};
|
||||
|
||||
function getKtyFromAlg(alg) {
|
||||
switch (typeof alg === 'string' && alg.slice(0, 2)) {
|
||||
case 'RS':
|
||||
case 'PS':
|
||||
return 'RSA';
|
||||
case 'ES':
|
||||
return 'EC';
|
||||
case 'Ed':
|
||||
return 'OKP';
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function getAlgorithms(use, alg, kty, crv) {
|
||||
// Ed25519, Ed448, and secp256k1 always have "alg"
|
||||
// OKP always has "use"
|
||||
if (alg) {
|
||||
return new Set([alg]);
|
||||
}
|
||||
|
||||
switch (kty) {
|
||||
case 'EC': {
|
||||
let algs = [];
|
||||
|
||||
if (use === 'enc' || use === undefined) {
|
||||
algs = algs.concat(['ECDH-ES', 'ECDH-ES+A128KW', 'ECDH-ES+A192KW', 'ECDH-ES+A256KW']);
|
||||
}
|
||||
|
||||
if (use === 'sig' || use === undefined) {
|
||||
switch (crv) {
|
||||
case 'P-256':
|
||||
case 'P-384':
|
||||
algs = algs.concat([`ES${crv.slice(-3)}`]);
|
||||
break;
|
||||
case 'P-521':
|
||||
algs = algs.concat(['ES512']);
|
||||
break;
|
||||
case 'secp256k1':
|
||||
if (jose.cryptoRuntime === 'node:crypto') {
|
||||
algs = algs.concat(['ES256K']);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return new Set(algs);
|
||||
}
|
||||
case 'OKP': {
|
||||
return new Set(['ECDH-ES', 'ECDH-ES+A128KW', 'ECDH-ES+A192KW', 'ECDH-ES+A256KW']);
|
||||
}
|
||||
case 'RSA': {
|
||||
let algs = [];
|
||||
|
||||
if (use === 'enc' || use === undefined) {
|
||||
algs = algs.concat(['RSA-OAEP', 'RSA-OAEP-256', 'RSA-OAEP-384', 'RSA-OAEP-512']);
|
||||
if (jose.cryptoRuntime === 'node:crypto') {
|
||||
algs = algs.concat(['RSA1_5']);
|
||||
}
|
||||
}
|
||||
|
||||
if (use === 'sig' || use === undefined) {
|
||||
algs = algs.concat(['PS256', 'PS384', 'PS512', 'RS256', 'RS384', 'RS512']);
|
||||
}
|
||||
|
||||
return new Set(algs);
|
||||
}
|
||||
default:
|
||||
throw new Error('unreachable');
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = class KeyStore {
|
||||
#keys;
|
||||
|
||||
constructor(i, keys) {
|
||||
if (i !== internal) throw new Error('invalid constructor call');
|
||||
this.#keys = keys;
|
||||
}
|
||||
|
||||
toJWKS() {
|
||||
return {
|
||||
keys: this.map(({ jwk: { d, p, q, dp, dq, qi, ...jwk } }) => jwk),
|
||||
};
|
||||
}
|
||||
|
||||
all({ alg, kid, use } = {}) {
|
||||
if (!use || !alg) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
const kty = getKtyFromAlg(alg);
|
||||
|
||||
const search = { alg, use };
|
||||
return this.filter((key) => {
|
||||
let candidate = true;
|
||||
|
||||
if (candidate && kty !== undefined && key.jwk.kty !== kty) {
|
||||
candidate = false;
|
||||
}
|
||||
|
||||
if (candidate && kid !== undefined && key.jwk.kid !== kid) {
|
||||
candidate = false;
|
||||
}
|
||||
|
||||
if (candidate && use !== undefined && key.jwk.use !== undefined && key.jwk.use !== use) {
|
||||
candidate = false;
|
||||
}
|
||||
|
||||
if (candidate && key.jwk.alg && key.jwk.alg !== alg) {
|
||||
candidate = false;
|
||||
} else if (!key.algorithms.has(alg)) {
|
||||
candidate = false;
|
||||
}
|
||||
|
||||
return candidate;
|
||||
}).sort((first, second) => keyscore(second, search) - keyscore(first, search));
|
||||
}
|
||||
|
||||
get(...args) {
|
||||
return this.all(...args)[0];
|
||||
}
|
||||
|
||||
static async fromJWKS(jwks, { onlyPublic = false, onlyPrivate = false } = {}) {
|
||||
if (
|
||||
!isPlainObject(jwks) ||
|
||||
!Array.isArray(jwks.keys) ||
|
||||
jwks.keys.some((k) => !isPlainObject(k) || !('kty' in k))
|
||||
) {
|
||||
throw new TypeError('jwks must be a JSON Web Key Set formatted object');
|
||||
}
|
||||
|
||||
const keys = [];
|
||||
|
||||
for (let jwk of jwks.keys) {
|
||||
jwk = clone(jwk);
|
||||
const { kty, kid, crv } = jwk;
|
||||
|
||||
let { alg, use } = jwk;
|
||||
|
||||
if (typeof kty !== 'string' || !kty) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (use !== undefined && use !== 'sig' && use !== 'enc') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (typeof alg !== 'string' && alg !== undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (typeof kid !== 'string' && kid !== undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (kty === 'EC' && use === 'sig') {
|
||||
switch (crv) {
|
||||
case 'P-256':
|
||||
alg = 'ES256';
|
||||
break;
|
||||
case 'P-384':
|
||||
alg = 'ES384';
|
||||
break;
|
||||
case 'P-521':
|
||||
alg = 'ES512';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (crv === 'secp256k1') {
|
||||
use = 'sig';
|
||||
alg = 'ES256K';
|
||||
}
|
||||
|
||||
if (kty === 'OKP') {
|
||||
switch (crv) {
|
||||
case 'Ed25519':
|
||||
case 'Ed448':
|
||||
use = 'sig';
|
||||
alg = 'EdDSA';
|
||||
break;
|
||||
case 'X25519':
|
||||
case 'X448':
|
||||
use = 'enc';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (alg && !use) {
|
||||
switch (true) {
|
||||
case alg.startsWith('ECDH'):
|
||||
use = 'enc';
|
||||
break;
|
||||
case alg.startsWith('RSA'):
|
||||
use = 'enc';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (onlyPrivate && (jwk.kty === 'oct' || !jwk.d)) {
|
||||
throw new Error('jwks must only contain private keys');
|
||||
}
|
||||
|
||||
if (onlyPublic && (jwk.d || jwk.k)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
keys.push({
|
||||
jwk: { ...jwk, alg, use },
|
||||
async keyObject(alg) {
|
||||
if (this[alg]) {
|
||||
return this[alg];
|
||||
}
|
||||
|
||||
const keyObject = await jose.importJWK(this.jwk, alg);
|
||||
this[alg] = keyObject;
|
||||
return keyObject;
|
||||
},
|
||||
get algorithms() {
|
||||
Object.defineProperty(this, 'algorithms', {
|
||||
value: getAlgorithms(this.jwk.use, this.jwk.alg, this.jwk.kty, this.jwk.crv),
|
||||
enumerable: true,
|
||||
configurable: false,
|
||||
});
|
||||
return this.algorithms;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return new this(internal, keys);
|
||||
}
|
||||
|
||||
filter(...args) {
|
||||
return this.#keys.filter(...args);
|
||||
}
|
||||
|
||||
find(...args) {
|
||||
return this.#keys.find(...args);
|
||||
}
|
||||
|
||||
every(...args) {
|
||||
return this.#keys.every(...args);
|
||||
}
|
||||
|
||||
some(...args) {
|
||||
return this.#keys.some(...args);
|
||||
}
|
||||
|
||||
map(...args) {
|
||||
return this.#keys.map(...args);
|
||||
}
|
||||
|
||||
forEach(...args) {
|
||||
return this.#keys.forEach(...args);
|
||||
}
|
||||
|
||||
reduce(...args) {
|
||||
return this.#keys.reduce(...args);
|
||||
}
|
||||
|
||||
sort(...args) {
|
||||
return this.#keys.sort(...args);
|
||||
}
|
||||
|
||||
*[Symbol.iterator]() {
|
||||
for (const key of this.#keys) {
|
||||
yield key;
|
||||
}
|
||||
}
|
||||
};
|
||||
24
node_modules/openid-client/lib/helpers/merge.js
generated
vendored
Normal file
24
node_modules/openid-client/lib/helpers/merge.js
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
const isPlainObject = require('./is_plain_object');
|
||||
|
||||
function merge(target, ...sources) {
|
||||
for (const source of sources) {
|
||||
if (!isPlainObject(source)) {
|
||||
continue;
|
||||
}
|
||||
for (const [key, value] of Object.entries(source)) {
|
||||
/* istanbul ignore if */
|
||||
if (key === '__proto__' || key === 'constructor') {
|
||||
continue;
|
||||
}
|
||||
if (isPlainObject(target[key]) && isPlainObject(value)) {
|
||||
target[key] = merge(target[key], value);
|
||||
} else if (typeof value !== 'undefined') {
|
||||
target[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
module.exports = merge;
|
||||
9
node_modules/openid-client/lib/helpers/pick.js
generated
vendored
Normal file
9
node_modules/openid-client/lib/helpers/pick.js
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
module.exports = function pick(object, ...paths) {
|
||||
const obj = {};
|
||||
for (const path of paths) {
|
||||
if (object[path] !== undefined) {
|
||||
obj[path] = object[path];
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
71
node_modules/openid-client/lib/helpers/process_response.js
generated
vendored
Normal file
71
node_modules/openid-client/lib/helpers/process_response.js
generated
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
const { STATUS_CODES } = require('http');
|
||||
const { format } = require('util');
|
||||
|
||||
const { OPError } = require('../errors');
|
||||
const parseWwwAuthenticate = require('./www_authenticate_parser');
|
||||
|
||||
const throwAuthenticateErrors = (response) => {
|
||||
const params = parseWwwAuthenticate(response.headers['www-authenticate']);
|
||||
|
||||
if (params.error) {
|
||||
throw new OPError(params, response);
|
||||
}
|
||||
};
|
||||
|
||||
const isStandardBodyError = (response) => {
|
||||
let result = false;
|
||||
try {
|
||||
let jsonbody;
|
||||
if (typeof response.body !== 'object' || Buffer.isBuffer(response.body)) {
|
||||
jsonbody = JSON.parse(response.body);
|
||||
} else {
|
||||
jsonbody = response.body;
|
||||
}
|
||||
result = typeof jsonbody.error === 'string' && jsonbody.error.length;
|
||||
if (result) Object.defineProperty(response, 'body', { value: jsonbody, configurable: true });
|
||||
} catch (err) {}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
function processResponse(response, { statusCode = 200, body = true, bearer = false } = {}) {
|
||||
if (response.statusCode !== statusCode) {
|
||||
if (bearer) {
|
||||
throwAuthenticateErrors(response);
|
||||
}
|
||||
|
||||
if (isStandardBodyError(response)) {
|
||||
throw new OPError(response.body, response);
|
||||
}
|
||||
|
||||
throw new OPError(
|
||||
{
|
||||
error: format(
|
||||
'expected %i %s, got: %i %s',
|
||||
statusCode,
|
||||
STATUS_CODES[statusCode],
|
||||
response.statusCode,
|
||||
STATUS_CODES[response.statusCode],
|
||||
),
|
||||
},
|
||||
response,
|
||||
);
|
||||
}
|
||||
|
||||
if (body && !response.body) {
|
||||
throw new OPError(
|
||||
{
|
||||
error: format(
|
||||
'expected %i %s with body but no body was returned',
|
||||
statusCode,
|
||||
STATUS_CODES[statusCode],
|
||||
),
|
||||
},
|
||||
response,
|
||||
);
|
||||
}
|
||||
|
||||
return response.body;
|
||||
}
|
||||
|
||||
module.exports = processResponse;
|
||||
200
node_modules/openid-client/lib/helpers/request.js
generated
vendored
Normal file
200
node_modules/openid-client/lib/helpers/request.js
generated
vendored
Normal file
@@ -0,0 +1,200 @@
|
||||
const assert = require('assert');
|
||||
const querystring = require('querystring');
|
||||
const http = require('http');
|
||||
const https = require('https');
|
||||
const { once } = require('events');
|
||||
const { URL } = require('url');
|
||||
|
||||
const LRU = require('lru-cache');
|
||||
|
||||
const pkg = require('../../package.json');
|
||||
const { RPError } = require('../errors');
|
||||
|
||||
const pick = require('./pick');
|
||||
const { deep: defaultsDeep } = require('./defaults');
|
||||
const { HTTP_OPTIONS } = require('./consts');
|
||||
|
||||
let DEFAULT_HTTP_OPTIONS;
|
||||
const NQCHAR = /^[\x21\x23-\x5B\x5D-\x7E]+$/;
|
||||
|
||||
const allowed = [
|
||||
'agent',
|
||||
'ca',
|
||||
'cert',
|
||||
'crl',
|
||||
'headers',
|
||||
'key',
|
||||
'lookup',
|
||||
'passphrase',
|
||||
'pfx',
|
||||
'timeout',
|
||||
];
|
||||
|
||||
const setDefaults = (props, options) => {
|
||||
DEFAULT_HTTP_OPTIONS = defaultsDeep(
|
||||
{},
|
||||
props.length ? pick(options, ...props) : options,
|
||||
DEFAULT_HTTP_OPTIONS,
|
||||
);
|
||||
};
|
||||
|
||||
setDefaults([], {
|
||||
headers: {
|
||||
'User-Agent': `${pkg.name}/${pkg.version} (${pkg.homepage})`,
|
||||
'Accept-Encoding': 'identity',
|
||||
},
|
||||
timeout: 3500,
|
||||
});
|
||||
|
||||
function send(req, body, contentType) {
|
||||
if (contentType) {
|
||||
req.removeHeader('content-type');
|
||||
req.setHeader('content-type', contentType);
|
||||
}
|
||||
if (body) {
|
||||
req.removeHeader('content-length');
|
||||
req.setHeader('content-length', Buffer.byteLength(body));
|
||||
req.write(body);
|
||||
}
|
||||
req.end();
|
||||
}
|
||||
|
||||
const nonces = new LRU({ max: 100 });
|
||||
|
||||
module.exports = async function request(options, { accessToken, mTLS = false, DPoP } = {}) {
|
||||
let url;
|
||||
try {
|
||||
url = new URL(options.url);
|
||||
delete options.url;
|
||||
assert(/^(https?:)$/.test(url.protocol));
|
||||
} catch (err) {
|
||||
throw new TypeError('only valid absolute URLs can be requested');
|
||||
}
|
||||
const optsFn = this[HTTP_OPTIONS];
|
||||
let opts = options;
|
||||
|
||||
const nonceKey = `${url.origin}${url.pathname}`;
|
||||
if (DPoP && 'dpopProof' in this) {
|
||||
opts.headers = opts.headers || {};
|
||||
opts.headers.DPoP = await this.dpopProof(
|
||||
{
|
||||
htu: `${url.origin}${url.pathname}`,
|
||||
htm: options.method || 'GET',
|
||||
nonce: nonces.get(nonceKey),
|
||||
},
|
||||
DPoP,
|
||||
accessToken,
|
||||
);
|
||||
}
|
||||
|
||||
let userOptions;
|
||||
if (optsFn) {
|
||||
userOptions = pick(
|
||||
optsFn.call(this, url, defaultsDeep({}, opts, DEFAULT_HTTP_OPTIONS)),
|
||||
...allowed,
|
||||
);
|
||||
}
|
||||
opts = defaultsDeep({}, userOptions, opts, DEFAULT_HTTP_OPTIONS);
|
||||
|
||||
if (mTLS && !opts.pfx && !(opts.key && opts.cert)) {
|
||||
throw new TypeError('mutual-TLS certificate and key not set');
|
||||
}
|
||||
|
||||
if (opts.searchParams) {
|
||||
for (const [key, value] of Object.entries(opts.searchParams)) {
|
||||
url.searchParams.delete(key);
|
||||
url.searchParams.set(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
let responseType;
|
||||
let form;
|
||||
let json;
|
||||
let body;
|
||||
({ form, responseType, json, body, ...opts } = opts);
|
||||
|
||||
for (const [key, value] of Object.entries(opts.headers || {})) {
|
||||
if (value === undefined) {
|
||||
delete opts.headers[key];
|
||||
}
|
||||
}
|
||||
|
||||
let response;
|
||||
const req = (url.protocol === 'https:' ? https.request : http.request)(url.href, opts);
|
||||
return (async () => {
|
||||
if (json) {
|
||||
send(req, JSON.stringify(json), 'application/json');
|
||||
} else if (form) {
|
||||
send(req, querystring.stringify(form), 'application/x-www-form-urlencoded');
|
||||
} else if (body) {
|
||||
send(req, body);
|
||||
} else {
|
||||
send(req);
|
||||
}
|
||||
|
||||
[response] = await Promise.race([once(req, 'response'), once(req, 'timeout')]);
|
||||
|
||||
// timeout reached
|
||||
if (!response) {
|
||||
req.destroy();
|
||||
throw new RPError(`outgoing request timed out after ${opts.timeout}ms`);
|
||||
}
|
||||
|
||||
const parts = [];
|
||||
|
||||
for await (const part of response) {
|
||||
parts.push(part);
|
||||
}
|
||||
|
||||
if (parts.length) {
|
||||
switch (responseType) {
|
||||
case 'json': {
|
||||
Object.defineProperty(response, 'body', {
|
||||
get() {
|
||||
let value = Buffer.concat(parts);
|
||||
try {
|
||||
value = JSON.parse(value);
|
||||
} catch (err) {
|
||||
Object.defineProperty(err, 'response', { value: response });
|
||||
throw err;
|
||||
} finally {
|
||||
Object.defineProperty(response, 'body', { value, configurable: true });
|
||||
}
|
||||
return value;
|
||||
},
|
||||
configurable: true,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case undefined:
|
||||
case 'buffer': {
|
||||
Object.defineProperty(response, 'body', {
|
||||
get() {
|
||||
const value = Buffer.concat(parts);
|
||||
Object.defineProperty(response, 'body', { value, configurable: true });
|
||||
return value;
|
||||
},
|
||||
configurable: true,
|
||||
});
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new TypeError('unsupported responseType request option');
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
})()
|
||||
.catch((err) => {
|
||||
if (response) Object.defineProperty(err, 'response', { value: response });
|
||||
throw err;
|
||||
})
|
||||
.finally(() => {
|
||||
const dpopNonce = response && response.headers['dpop-nonce'];
|
||||
if (dpopNonce && NQCHAR.test(dpopNonce)) {
|
||||
nonces.set(nonceKey, dpopNonce);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.setDefaults = setDefaults.bind(undefined, allowed);
|
||||
1
node_modules/openid-client/lib/helpers/unix_timestamp.js
generated
vendored
Normal file
1
node_modules/openid-client/lib/helpers/unix_timestamp.js
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = () => Math.floor(Date.now() / 1000);
|
||||
1
node_modules/openid-client/lib/helpers/weak_cache.js
generated
vendored
Normal file
1
node_modules/openid-client/lib/helpers/weak_cache.js
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
module.exports.keystores = new WeakMap();
|
||||
71
node_modules/openid-client/lib/helpers/webfinger_normalize.js
generated
vendored
Normal file
71
node_modules/openid-client/lib/helpers/webfinger_normalize.js
generated
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
// Credit: https://github.com/rohe/pyoidc/blob/master/src/oic/utils/webfinger.py
|
||||
|
||||
// -- Normalization --
|
||||
// A string of any other type is interpreted as a URI either the form of scheme
|
||||
// "://" authority path-abempty [ "?" query ] [ "#" fragment ] or authority
|
||||
// path-abempty [ "?" query ] [ "#" fragment ] per RFC 3986 [RFC3986] and is
|
||||
// normalized according to the following rules:
|
||||
//
|
||||
// If the user input Identifier does not have an RFC 3986 [RFC3986] scheme
|
||||
// portion, the string is interpreted as [userinfo "@"] host [":" port]
|
||||
// path-abempty [ "?" query ] [ "#" fragment ] per RFC 3986 [RFC3986].
|
||||
// If the userinfo component is present and all of the path component, query
|
||||
// component, and port component are empty, the acct scheme is assumed. In this
|
||||
// case, the normalized URI is formed by prefixing acct: to the string as the
|
||||
// scheme. Per the 'acct' URI Scheme [I‑D.ietf‑appsawg‑acct‑uri], if there is an
|
||||
// at-sign character ('@') in the userinfo component, it needs to be
|
||||
// percent-encoded as described in RFC 3986 [RFC3986].
|
||||
// For all other inputs without a scheme portion, the https scheme is assumed,
|
||||
// and the normalized URI is formed by prefixing https:// to the string as the
|
||||
// scheme.
|
||||
// If the resulting URI contains a fragment portion, it MUST be stripped off
|
||||
// together with the fragment delimiter character "#".
|
||||
// The WebFinger [I‑D.ietf‑appsawg‑webfinger] Resource in this case is the
|
||||
// resulting URI, and the WebFinger Host is the authority component.
|
||||
//
|
||||
// Note: Since the definition of authority in RFC 3986 [RFC3986] is
|
||||
// [ userinfo "@" ] host [ ":" port ], it is legal to have a user input
|
||||
// identifier like userinfo@host:port, e.g., alice@example.com:8080.
|
||||
|
||||
const PORT = /^\d+$/;
|
||||
|
||||
function hasScheme(input) {
|
||||
if (input.includes('://')) return true;
|
||||
|
||||
const authority = input.replace(/(\/|\?)/g, '#').split('#')[0];
|
||||
if (authority.includes(':')) {
|
||||
const index = authority.indexOf(':');
|
||||
const hostOrPort = authority.slice(index + 1);
|
||||
if (!PORT.test(hostOrPort)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function acctSchemeAssumed(input) {
|
||||
if (!input.includes('@')) return false;
|
||||
const parts = input.split('@');
|
||||
const host = parts[parts.length - 1];
|
||||
return !(host.includes(':') || host.includes('/') || host.includes('?'));
|
||||
}
|
||||
|
||||
function normalize(input) {
|
||||
if (typeof input !== 'string') {
|
||||
throw new TypeError('input must be a string');
|
||||
}
|
||||
|
||||
let output;
|
||||
if (hasScheme(input)) {
|
||||
output = input;
|
||||
} else if (acctSchemeAssumed(input)) {
|
||||
output = `acct:${input}`;
|
||||
} else {
|
||||
output = `https://${input}`;
|
||||
}
|
||||
|
||||
return output.split('#')[0];
|
||||
}
|
||||
|
||||
module.exports = normalize;
|
||||
14
node_modules/openid-client/lib/helpers/www_authenticate_parser.js
generated
vendored
Normal file
14
node_modules/openid-client/lib/helpers/www_authenticate_parser.js
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
const REGEXP = /(\w+)=("[^"]*")/g;
|
||||
|
||||
module.exports = (wwwAuthenticate) => {
|
||||
const params = {};
|
||||
try {
|
||||
while (REGEXP.exec(wwwAuthenticate) !== null) {
|
||||
if (RegExp.$1 && RegExp.$2) {
|
||||
params[RegExp.$1] = RegExp.$2.slice(1, -1);
|
||||
}
|
||||
}
|
||||
} catch (err) {}
|
||||
|
||||
return params;
|
||||
};
|
||||
Reference in New Issue
Block a user