Auto-commit 2026-04-29 16:31

This commit is contained in:
2026-04-29 16:31:27 -04:00
parent e8687bb6b2
commit 0495ee5bd2
19691 changed files with 3272886 additions and 138 deletions

1884
node_modules/openid-client/lib/client.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

125
node_modules/openid-client/lib/device_flow_handle.js generated vendored Normal file
View File

@@ -0,0 +1,125 @@
const { inspect } = require('util');
const { RPError, OPError } = require('./errors');
const now = require('./helpers/unix_timestamp');
class DeviceFlowHandle {
#aborted;
#client;
#clientAssertionPayload;
#DPoP;
#exchangeBody;
#expires_at;
#interval;
#maxAge;
#response;
constructor({ client, exchangeBody, clientAssertionPayload, response, maxAge, DPoP }) {
['verification_uri', 'user_code', 'device_code'].forEach((prop) => {
if (typeof response[prop] !== 'string' || !response[prop]) {
throw new RPError(
`expected ${prop} string to be returned by Device Authorization Response, got %j`,
response[prop],
);
}
});
if (!Number.isSafeInteger(response.expires_in)) {
throw new RPError(
'expected expires_in number to be returned by Device Authorization Response, got %j',
response.expires_in,
);
}
this.#expires_at = now() + response.expires_in;
this.#client = client;
this.#DPoP = DPoP;
this.#maxAge = maxAge;
this.#exchangeBody = exchangeBody;
this.#clientAssertionPayload = clientAssertionPayload;
this.#response = response;
this.#interval = response.interval * 1000 || 5000;
}
abort() {
this.#aborted = true;
}
async poll({ signal } = {}) {
if ((signal && signal.aborted) || this.#aborted) {
throw new RPError('polling aborted');
}
if (this.expired()) {
throw new RPError(
'the device code %j has expired and the device authorization session has concluded',
this.device_code,
);
}
await new Promise((resolve) => setTimeout(resolve, this.#interval));
let tokenset;
try {
tokenset = await this.#client.grant(
{
...this.#exchangeBody,
grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
device_code: this.device_code,
},
{ clientAssertionPayload: this.#clientAssertionPayload, DPoP: this.#DPoP },
);
} catch (err) {
switch (err instanceof OPError && err.error) {
case 'slow_down':
this.#interval += 5000;
case 'authorization_pending':
return this.poll({ signal });
default:
throw err;
}
}
if ('id_token' in tokenset) {
await this.#client.decryptIdToken(tokenset);
await this.#client.validateIdToken(tokenset, undefined, 'token', this.#maxAge);
}
return tokenset;
}
get device_code() {
return this.#response.device_code;
}
get user_code() {
return this.#response.user_code;
}
get verification_uri() {
return this.#response.verification_uri;
}
get verification_uri_complete() {
return this.#response.verification_uri_complete;
}
get expires_in() {
return Math.max.apply(null, [this.#expires_at - now(), 0]);
}
expired() {
return this.expires_in === 0;
}
/* istanbul ignore next */
[inspect.custom]() {
return `${this.constructor.name} ${inspect(this.#response, {
depth: Infinity,
colors: process.stdout.isTTY,
compact: false,
sorted: true,
})}`;
}
}
module.exports = DeviceFlowHandle;

55
node_modules/openid-client/lib/errors.js generated vendored Normal file
View File

@@ -0,0 +1,55 @@
const { format } = require('util');
class OPError extends Error {
constructor({ error_description, error, error_uri, session_state, state, scope }, response) {
super(!error_description ? error : `${error} (${error_description})`);
Object.assign(
this,
{ error },
error_description && { error_description },
error_uri && { error_uri },
state && { state },
scope && { scope },
session_state && { session_state },
);
if (response) {
Object.defineProperty(this, 'response', {
value: response,
});
}
this.name = this.constructor.name;
Error.captureStackTrace(this, this.constructor);
}
}
class RPError extends Error {
constructor(...args) {
if (typeof args[0] === 'string') {
super(format(...args));
} else {
const { message, printf, response, ...rest } = args[0];
if (printf) {
super(format(...printf));
} else {
super(message);
}
Object.assign(this, rest);
if (response) {
Object.defineProperty(this, 'response', {
value: response,
});
}
}
this.name = this.constructor.name;
Error.captureStackTrace(this, this.constructor);
}
}
module.exports = {
OPError,
RPError,
};

24
node_modules/openid-client/lib/helpers/assert.js generated vendored Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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()),
};

View File

@@ -0,0 +1,4 @@
const util = require('util');
const crypto = require('crypto');
module.exports = util.types.isKeyObject || ((obj) => obj && obj instanceof crypto.KeyObject);

View File

@@ -0,0 +1 @@
module.exports = (a) => !!a && a.constructor === Object;

111
node_modules/openid-client/lib/helpers/issuer.js generated vendored Normal file
View 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
View 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
View 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
View 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;
};

View 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
View 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);

View 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
View File

@@ -0,0 +1 @@
module.exports.keystores = new WeakMap();

View 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 [ID.ietfappsawgaccturi], 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 [ID.ietfappsawgwebfinger] 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;

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

23
node_modules/openid-client/lib/index.js generated vendored Normal file
View File

@@ -0,0 +1,23 @@
const Issuer = require('./issuer');
const { OPError, RPError } = require('./errors');
const Strategy = require('./passport_strategy');
const TokenSet = require('./token_set');
const { CLOCK_TOLERANCE, HTTP_OPTIONS } = require('./helpers/consts');
const generators = require('./helpers/generators');
const { setDefaults } = require('./helpers/request');
module.exports = {
Issuer,
Strategy,
TokenSet,
errors: {
OPError,
RPError,
},
custom: {
setHttpOptionsDefaults: setDefaults,
http_options: HTTP_OPTIONS,
clock_tolerance: CLOCK_TOLERANCE,
},
generators,
};

9
node_modules/openid-client/lib/index.mjs generated vendored Normal file
View File

@@ -0,0 +1,9 @@
import mod from './index.js';
export default mod;
export const Issuer = mod.Issuer;
export const Strategy = mod.Strategy;
export const TokenSet = mod.TokenSet;
export const errors = mod.errors;
export const custom = mod.custom;
export const generators = mod.generators;

192
node_modules/openid-client/lib/issuer.js generated vendored Normal file
View File

@@ -0,0 +1,192 @@
const { inspect } = require('util');
const url = require('url');
const { RPError } = require('./errors');
const getClient = require('./client');
const registry = require('./issuer_registry');
const processResponse = require('./helpers/process_response');
const webfingerNormalize = require('./helpers/webfinger_normalize');
const request = require('./helpers/request');
const clone = require('./helpers/deep_clone');
const { keystore } = require('./helpers/issuer');
const AAD_MULTITENANT_DISCOVERY = [
'https://login.microsoftonline.com/common/.well-known/openid-configuration',
'https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration',
'https://login.microsoftonline.com/organizations/v2.0/.well-known/openid-configuration',
'https://login.microsoftonline.com/consumers/v2.0/.well-known/openid-configuration',
];
const AAD_MULTITENANT = Symbol();
const ISSUER_DEFAULTS = {
claim_types_supported: ['normal'],
claims_parameter_supported: false,
grant_types_supported: ['authorization_code', 'implicit'],
request_parameter_supported: false,
request_uri_parameter_supported: true,
require_request_uri_registration: false,
response_modes_supported: ['query', 'fragment'],
token_endpoint_auth_methods_supported: ['client_secret_basic'],
};
class Issuer {
#metadata;
constructor(meta = {}) {
const aadIssValidation = meta[AAD_MULTITENANT];
delete meta[AAD_MULTITENANT];
['introspection', 'revocation'].forEach((endpoint) => {
// if intro/revocation endpoint auth specific meta is missing use the token ones if they
// are defined
if (
meta[`${endpoint}_endpoint`] &&
meta[`${endpoint}_endpoint_auth_methods_supported`] === undefined &&
meta[`${endpoint}_endpoint_auth_signing_alg_values_supported`] === undefined
) {
if (meta.token_endpoint_auth_methods_supported) {
meta[`${endpoint}_endpoint_auth_methods_supported`] =
meta.token_endpoint_auth_methods_supported;
}
if (meta.token_endpoint_auth_signing_alg_values_supported) {
meta[`${endpoint}_endpoint_auth_signing_alg_values_supported`] =
meta.token_endpoint_auth_signing_alg_values_supported;
}
}
});
this.#metadata = new Map();
Object.entries(meta).forEach(([key, value]) => {
this.#metadata.set(key, value);
if (!this[key]) {
Object.defineProperty(this, key, {
get() {
return this.#metadata.get(key);
},
enumerable: true,
});
}
});
registry.set(this.issuer, this);
const Client = getClient(this, aadIssValidation);
Object.defineProperties(this, {
Client: { value: Client, enumerable: true },
FAPI1Client: { value: class FAPI1Client extends Client {}, enumerable: true },
FAPI2Client: { value: class FAPI2Client extends Client {}, enumerable: true },
});
}
get metadata() {
return clone(Object.fromEntries(this.#metadata.entries()));
}
static async webfinger(input) {
const resource = webfingerNormalize(input);
const { host } = url.parse(resource);
const webfingerUrl = `https://${host}/.well-known/webfinger`;
const response = await request.call(this, {
method: 'GET',
url: webfingerUrl,
responseType: 'json',
searchParams: { resource, rel: 'http://openid.net/specs/connect/1.0/issuer' },
headers: {
Accept: 'application/json',
},
});
const body = processResponse(response);
const location =
Array.isArray(body.links) &&
body.links.find(
(link) =>
typeof link === 'object' &&
link.rel === 'http://openid.net/specs/connect/1.0/issuer' &&
link.href,
);
if (!location) {
throw new RPError({
message: 'no issuer found in webfinger response',
body,
});
}
if (typeof location.href !== 'string' || !location.href.startsWith('https://')) {
throw new RPError({
printf: ['invalid issuer location %s', location.href],
body,
});
}
const expectedIssuer = location.href;
if (registry.has(expectedIssuer)) {
return registry.get(expectedIssuer);
}
const issuer = await this.discover(expectedIssuer);
if (issuer.issuer !== expectedIssuer) {
registry.del(issuer.issuer);
throw new RPError(
'discovered issuer mismatch, expected %s, got: %s',
expectedIssuer,
issuer.issuer,
);
}
return issuer;
}
static async discover(uri) {
const wellKnownUri = resolveWellKnownUri(uri);
const response = await request.call(this, {
method: 'GET',
responseType: 'json',
url: wellKnownUri,
headers: {
Accept: 'application/json',
},
});
const body = processResponse(response);
return new Issuer({
...ISSUER_DEFAULTS,
...body,
[AAD_MULTITENANT]: !!AAD_MULTITENANT_DISCOVERY.find((discoveryURL) =>
wellKnownUri.startsWith(discoveryURL),
),
});
}
async reloadJwksUri() {
await keystore.call(this, true);
}
/* istanbul ignore next */
[inspect.custom]() {
return `${this.constructor.name} ${inspect(this.metadata, {
depth: Infinity,
colors: process.stdout.isTTY,
compact: false,
sorted: true,
})}`;
}
}
function resolveWellKnownUri(uri) {
const parsed = url.parse(uri);
if (parsed.pathname.includes('/.well-known/')) {
return uri;
} else {
let pathname;
if (parsed.pathname.endsWith('/')) {
pathname = `${parsed.pathname}.well-known/openid-configuration`;
} else {
pathname = `${parsed.pathname}/.well-known/openid-configuration`;
}
return url.format({ ...parsed, pathname });
}
}
module.exports = Issuer;

3
node_modules/openid-client/lib/issuer_registry.js generated vendored Normal file
View File

@@ -0,0 +1,3 @@
const LRU = require('lru-cache');
module.exports = new LRU({ max: 100 });

205
node_modules/openid-client/lib/passport_strategy.js generated vendored Normal file
View File

@@ -0,0 +1,205 @@
const url = require('url');
const { format } = require('util');
const cloneDeep = require('./helpers/deep_clone');
const { RPError, OPError } = require('./errors');
const { BaseClient } = require('./client');
const { random, codeChallenge } = require('./helpers/generators');
const pick = require('./helpers/pick');
const { resolveResponseType, resolveRedirectUri } = require('./helpers/client');
function verified(err, user, info = {}) {
if (err) {
this.error(err);
} else if (!user) {
this.fail(info);
} else {
this.success(user, info);
}
}
function OpenIDConnectStrategy(
{ client, params = {}, passReqToCallback = false, sessionKey, usePKCE = true, extras = {} } = {},
verify,
) {
if (!(client instanceof BaseClient)) {
throw new TypeError('client must be an instance of openid-client Client');
}
if (typeof verify !== 'function') {
throw new TypeError('verify callback must be a function');
}
if (!client.issuer || !client.issuer.issuer) {
throw new TypeError('client must have an issuer with an identifier');
}
this._client = client;
this._issuer = client.issuer;
this._verify = verify;
this._passReqToCallback = passReqToCallback;
this._usePKCE = usePKCE;
this._key = sessionKey || `oidc:${url.parse(this._issuer.issuer).hostname}`;
this._params = cloneDeep(params);
// state and nonce are handled in authenticate()
delete this._params.state;
delete this._params.nonce;
this._extras = cloneDeep(extras);
if (!this._params.response_type) this._params.response_type = resolveResponseType.call(client);
if (!this._params.redirect_uri) this._params.redirect_uri = resolveRedirectUri.call(client);
if (!this._params.scope) this._params.scope = 'openid';
if (this._usePKCE === true) {
const supportedMethods = Array.isArray(this._issuer.code_challenge_methods_supported)
? this._issuer.code_challenge_methods_supported
: false;
if (supportedMethods && supportedMethods.includes('S256')) {
this._usePKCE = 'S256';
} else if (supportedMethods && supportedMethods.includes('plain')) {
this._usePKCE = 'plain';
} else if (supportedMethods) {
throw new TypeError(
'neither code_challenge_method supported by the client is supported by the issuer',
);
} else {
this._usePKCE = 'S256';
}
} else if (typeof this._usePKCE === 'string' && !['plain', 'S256'].includes(this._usePKCE)) {
throw new TypeError(`${this._usePKCE} is not valid/implemented PKCE code_challenge_method`);
}
this.name = url.parse(client.issuer.issuer).hostname;
}
OpenIDConnectStrategy.prototype.authenticate = function authenticate(req, options) {
(async () => {
const client = this._client;
if (!req.session) {
throw new TypeError('authentication requires session support');
}
const reqParams = client.callbackParams(req);
const sessionKey = this._key;
const { 0: parameter, length } = Object.keys(reqParams);
/**
* Start authentication request if this has no authorization response parameters or
* this might a login initiated from a third party as per
* https://openid.net/specs/openid-connect-core-1_0.html#ThirdPartyInitiatedLogin.
*/
if (length === 0 || (length === 1 && parameter === 'iss')) {
// provide options object with extra authentication parameters
const params = {
state: random(),
...this._params,
...options,
};
if (!params.nonce && params.response_type.includes('id_token')) {
params.nonce = random();
}
req.session[sessionKey] = pick(params, 'nonce', 'state', 'max_age', 'response_type');
if (this._usePKCE && params.response_type.includes('code')) {
const verifier = random();
req.session[sessionKey].code_verifier = verifier;
switch (this._usePKCE) {
case 'S256':
params.code_challenge = codeChallenge(verifier);
params.code_challenge_method = 'S256';
break;
case 'plain':
params.code_challenge = verifier;
break;
}
}
this.redirect(client.authorizationUrl(params));
return;
}
/* end authentication request */
/* start authentication response */
const session = req.session[sessionKey];
if (Object.keys(session || {}).length === 0) {
throw new Error(
format(
'did not find expected authorization request details in session, req.session["%s"] is %j',
sessionKey,
session,
),
);
}
const {
state,
nonce,
max_age: maxAge,
code_verifier: codeVerifier,
response_type: responseType,
} = session;
try {
delete req.session[sessionKey];
} catch (err) {}
const opts = {
redirect_uri: this._params.redirect_uri,
...options,
};
const checks = {
state,
nonce,
max_age: maxAge,
code_verifier: codeVerifier,
response_type: responseType,
};
const tokenset = await client.callback(opts.redirect_uri, reqParams, checks, this._extras);
const passReq = this._passReqToCallback;
const loadUserinfo = this._verify.length > (passReq ? 3 : 2) && client.issuer.userinfo_endpoint;
const args = [tokenset, verified.bind(this)];
if (loadUserinfo) {
if (!tokenset.access_token) {
throw new RPError({
message:
'expected access_token to be returned when asking for userinfo in verify callback',
tokenset,
});
}
const userinfo = await client.userinfo(tokenset);
args.splice(1, 0, userinfo);
}
if (passReq) {
args.unshift(req);
}
this._verify(...args);
/* end authentication response */
})().catch((error) => {
if (
(error instanceof OPError &&
error.error !== 'server_error' &&
!error.error.startsWith('invalid')) ||
error instanceof RPError
) {
this.fail(error);
} else {
this.error(error);
}
});
};
module.exports = OpenIDConnectStrategy;

35
node_modules/openid-client/lib/token_set.js generated vendored Normal file
View File

@@ -0,0 +1,35 @@
const base64url = require('./helpers/base64url');
const now = require('./helpers/unix_timestamp');
class TokenSet {
constructor(values) {
Object.assign(this, values);
const { constructor, ...properties } = Object.getOwnPropertyDescriptors(
this.constructor.prototype,
);
Object.defineProperties(this, properties);
}
set expires_in(value) {
this.expires_at = now() + Number(value);
}
get expires_in() {
return Math.max.apply(null, [this.expires_at - now(), 0]);
}
expired() {
return this.expires_in === 0;
}
claims() {
if (!this.id_token) {
throw new TypeError('id_token not present in TokenSet');
}
return JSON.parse(base64url.decode(this.id_token.split('.')[1]));
}
}
module.exports = TokenSet;