chore: cleanup after release

This commit is contained in:
github-actions[bot]
2024-10-23 14:39:52 +00:00
parent 5799bc922f
commit 35d23aa895
6 changed files with 0 additions and 3178 deletions
-1920
View File
File diff suppressed because it is too large Load Diff
-986
View File
@@ -1,986 +0,0 @@
import * as oauth from 'oauth4webapi';
import * as jose from 'jose';
import { JOSEError } from 'jose/errors';
let headers;
let USER_AGENT;
if (typeof navigator === 'undefined' || !navigator.userAgent?.startsWith?.('Mozilla/5.0 ')) {
const NAME = 'openid-client';
const VERSION = 'v6.1.2';
USER_AGENT = `${NAME}/${VERSION}`;
headers = { 'user-agent': USER_AGENT };
}
const int = (config) => {
return props.get(config);
};
let props;
export { AuthorizationResponseError, ResponseBodyError, WWWAuthenticateChallengeError, } from 'oauth4webapi';
export function ClientSecretPost(clientSecret) {
return oauth.ClientSecretPost(clientSecret);
}
export function ClientSecretBasic(clientSecret) {
return oauth.ClientSecretBasic(clientSecret);
}
export function ClientSecretJwt(clientSecret, options) {
return oauth.ClientSecretJwt(clientSecret, options);
}
export function None() {
return oauth.None();
}
export function PrivateKeyJwt(clientPrivateKey, options) {
return oauth.PrivateKeyJwt(clientPrivateKey, options);
}
export function TlsClientAuth() {
return oauth.TlsClientAuth();
}
export const skipStateCheck = oauth.skipStateCheck;
export const skipSubjectCheck = oauth.skipSubjectCheck;
export const customFetch = oauth.customFetch;
export const modifyAssertion = oauth.modifyAssertion;
export const clockSkew = oauth.clockSkew;
export const clockTolerance = oauth.clockTolerance;
const ERR_INVALID_ARG_VALUE = 'ERR_INVALID_ARG_VALUE';
const ERR_INVALID_ARG_TYPE = 'ERR_INVALID_ARG_TYPE';
function CodedTypeError(message, code, cause) {
const err = new TypeError(message, { cause });
Object.assign(err, { code });
return err;
}
export function calculatePKCECodeChallenge(codeVerifier) {
return oauth.calculatePKCECodeChallenge(codeVerifier);
}
export function randomPKCECodeVerifier() {
return oauth.generateRandomCodeVerifier();
}
export function randomNonce() {
return oauth.generateRandomNonce();
}
export function randomState() {
return oauth.generateRandomState();
}
export class ClientError extends Error {
code;
constructor(message, options) {
super(message, options);
this.name = this.constructor.name;
this.code = options?.code;
Error.captureStackTrace?.(this, this.constructor);
}
}
const decoder = new TextDecoder();
function e(msg, cause, code) {
return new ClientError(msg, { cause, code });
}
function errorHandler(err) {
if (err instanceof TypeError ||
err instanceof ClientError ||
err instanceof oauth.ResponseBodyError ||
err instanceof oauth.AuthorizationResponseError ||
err instanceof oauth.WWWAuthenticateChallengeError) {
throw err;
}
if (err instanceof oauth.OperationProcessingError) {
switch (err.code) {
case oauth.HTTP_REQUEST_FORBIDDEN:
throw e('only requests to HTTPS are allowed', err, err.code);
case oauth.REQUEST_PROTOCOL_FORBIDDEN:
throw e('only requests to HTTP or HTTPS are allowed', err, err.code);
case oauth.RESPONSE_IS_NOT_CONFORM:
throw e('unexpected HTTP response status code', err.cause, err.code);
case oauth.RESPONSE_IS_NOT_JSON:
throw e('unexpected response content-type', err.cause, err.code);
case oauth.PARSE_ERROR:
throw e('parsing error occured', err, err.code);
case oauth.INVALID_RESPONSE:
throw e('invalid response encountered', err, err.code);
case oauth.JWT_CLAIM_COMPARISON:
throw e('unexpected JWT claim value encountered', err, err.code);
case oauth.JSON_ATTRIBUTE_COMPARISON:
throw e('unexpected JSON attribute value encountered', err, err.code);
case oauth.JWT_TIMESTAMP_CHECK:
throw e('JWT timestamp claim value failed validation', err, err.code);
default:
throw e(err.message, err, err.code);
}
}
if (err instanceof oauth.UnsupportedOperationError) {
throw e('unsupported operation', err, err.code);
}
if (err instanceof DOMException) {
switch (err.name) {
case 'OperationError':
throw e('runtime operation error', err, oauth.UNSUPPORTED_OPERATION);
case 'NotSupportedError':
throw e('runtime unsupported operation', err, oauth.UNSUPPORTED_OPERATION);
case 'TimeoutError':
throw e('operation timed out', err, 'OAUTH_TIMEOUT');
case 'AbortError':
throw e('operation aborted', err, 'OAUTH_ABORT');
}
}
throw new ClientError('something went wrong', { cause: err });
}
export function randomDPoPKeyPair(alg, options) {
return oauth
.generateKeyPair(alg ?? 'ES256', {
extractable: options?.extractable,
})
.catch(errorHandler);
}
function handleEntraId(server, as, options) {
if ((server.href === 'https://login.microsoftonline.com/common/v2.0' ||
server.href === 'https://login.microsoftonline.com/organizations/v2.0') &&
(!options?.algorithm || options.algorithm === 'oidc')) {
as[kEntraId] = true;
return true;
}
return false;
}
export async function discovery(server, clientId, metadata, clientAuthentication, options) {
if (!(server instanceof URL)) {
throw CodedTypeError('"server" must be an instance of URL', ERR_INVALID_ARG_TYPE);
}
const resolve = !server.href.includes('/.well-known/');
const timeout = options?.timeout ?? 30;
const signal = AbortSignal.timeout(timeout * 1000);
const as = await (resolve
? oauth.discoveryRequest(server, {
algorithm: options?.algorithm,
[oauth.customFetch]: options?.[customFetch],
[oauth.allowInsecureRequests]: options?.execute?.includes(allowInsecureRequests),
signal,
headers: new Headers(headers),
})
: (options?.[customFetch] || fetch)((() => {
oauth.checkProtocol(server, options?.execute?.includes(allowInsecureRequests) ? false : true);
return server.href;
})(), {
headers: Object.fromEntries(new Headers({ accept: 'application/json', ...headers }).entries()),
body: undefined,
method: 'GET',
redirect: 'manual',
signal,
}))
.then((response) => oauth.processDiscoveryResponse(oauth._nodiscoverycheck, response))
.catch(errorHandler);
if (resolve && new URL(as.issuer).href !== server.href) {
handleEntraId(server, as, options) ||
(() => {
throw new ClientError('discovered metadata issuer does not match the expected issuer', {
code: oauth.JSON_ATTRIBUTE_COMPARISON,
cause: {
expected: server.href,
body: as,
attribute: 'issuer',
},
});
})();
}
const instance = new Configuration(as, clientId, metadata, clientAuthentication);
let internals = int(instance);
if (options?.[customFetch]) {
internals.fetch = options[customFetch];
}
if (options?.timeout) {
internals.timeout = options.timeout;
}
if (options?.execute) {
for (const extension of options.execute) {
extension(instance);
}
}
return instance;
}
function isRsaOaep(input) {
return input.name === 'RSA-OAEP';
}
function isEcdh(input) {
return input.name === 'ECDH';
}
const ecdhEs = 'ECDH-ES';
const ecdhEsA128Kw = 'ECDH-ES+A128KW';
const ecdhEsA192Kw = 'ECDH-ES+A192KW';
const ecdhEsA256Kw = 'ECDH-ES+A256KW';
function checkEcdhAlg(algs, alg, pk) {
switch (alg) {
case undefined:
algs.add(ecdhEs);
algs.add(ecdhEsA128Kw);
algs.add(ecdhEsA192Kw);
algs.add(ecdhEsA256Kw);
break;
case ecdhEs:
case ecdhEsA128Kw:
case ecdhEsA192Kw:
case ecdhEsA256Kw:
algs.add(alg);
break;
default:
throw CodedTypeError('invalid key alg', ERR_INVALID_ARG_VALUE, { pk });
}
}
export function enableDecryptingResponses(config, contentEncryptionAlgorithms = [
'A128GCM',
'A192GCM',
'A256GCM',
'A128CBC-HS256',
'A192CBC-HS384',
'A256CBC-HS512',
], ...keys) {
if (int(config).decrypt !== undefined) {
throw new TypeError('enableDecryptingResponses can only be called on a given Configuration instance once');
}
if (keys.length === 0) {
throw CodedTypeError('no keys were provided', ERR_INVALID_ARG_VALUE);
}
const algs = new Set();
const normalized = [];
for (const pk of keys) {
let key;
if ('key' in pk) {
key = { key: pk.key };
if (typeof pk.alg === 'string')
key.alg = pk.alg;
if (typeof pk.kid === 'string')
key.kid = pk.kid;
}
else {
key = { key: pk };
}
if (key.key.type !== 'private') {
throw CodedTypeError('only private keys must be provided', ERR_INVALID_ARG_VALUE);
}
if (isRsaOaep(key.key.algorithm)) {
switch (key.key.algorithm.hash.name) {
case 'SHA-1':
case 'SHA-256':
case 'SHA-384':
case 'SHA-512': {
let alg = 'RSA-OAEP';
let sha;
if ((sha = parseInt(key.key.algorithm.hash.name.slice(-3), 10))) {
alg = `${alg}-${sha}`;
}
key.alg ||= alg;
if (alg !== key.alg)
throw CodedTypeError('invalid key alg', ERR_INVALID_ARG_VALUE, {
pk,
});
algs.add(key.alg);
break;
}
default:
throw CodedTypeError('only SHA-512, SHA-384, SHA-256, and SHA-1 RSA-OAEP keys are supported', ERR_INVALID_ARG_VALUE);
}
}
else if (isEcdh(key.key.algorithm)) {
if (key.key.algorithm.namedCurve !== 'P-256') {
throw CodedTypeError('Only P-256 ECDH keys are supported', ERR_INVALID_ARG_VALUE);
}
checkEcdhAlg(algs, key.alg, pk);
}
else if (key.key.algorithm.name === 'X25519') {
checkEcdhAlg(algs, key.alg, pk);
}
else {
throw CodedTypeError('only RSA-OAEP, ECDH, or X25519 keys are supported', ERR_INVALID_ARG_VALUE);
}
normalized.push(key);
}
int(config).decrypt = async (jwe) => decrypt(normalized, jwe, contentEncryptionAlgorithms, [...algs]).catch(errorHandler);
}
function checkCryptoKey(key, alg, epk) {
if (alg.startsWith('RSA-OAEP')) {
return true;
}
if (alg.startsWith('ECDH-ES')) {
if (key.algorithm.name !== 'ECDH' && key.algorithm.name !== 'X25519') {
return false;
}
if (key.algorithm.name === 'ECDH') {
return epk?.crv === key.algorithm.namedCurve;
}
if (key.algorithm.name === 'X25519') {
return epk?.crv === 'X25519';
}
}
return false;
}
function selectCryptoKeyForDecryption(keys, alg, kid, epk) {
const { 0: key, length } = keys.filter((key) => {
if (kid !== key.kid) {
return false;
}
if (key.alg && alg !== key.alg) {
return false;
}
return checkCryptoKey(key.key, alg, epk);
});
if (!key) {
throw e('no applicable decryption key selected', undefined, 'OAUTH_DECRYPTION_FAILED');
}
if (length !== 1) {
throw e('multiple applicable decryption keys selected', undefined, 'OAUTH_DECRYPTION_FAILED');
}
return key.key;
}
async function decrypt(keys, jwe, contentEncryptionAlgorithms, keyManagementAlgorithms) {
return decoder.decode((await jose
.compactDecrypt(jwe, async (header) => {
const { kid, alg, epk } = header;
return selectCryptoKeyForDecryption(keys, alg, kid, epk);
}, { keyManagementAlgorithms, contentEncryptionAlgorithms })
.catch((err) => {
if (err instanceof JOSEError) {
throw e('decryption failed', err, 'OAUTH_DECRYPTION_FAILED');
}
errorHandler(err);
})).plaintext);
}
function getServerHelpers(metadata) {
return {
supportsPKCE: {
__proto__: null,
value(method = 'S256') {
return (metadata.code_challenge_methods_supported?.includes(method) === true);
},
},
};
}
function addServerHelpers(metadata) {
Object.defineProperties(metadata, getServerHelpers(metadata));
}
const kEntraId = Symbol();
export class Configuration {
constructor(server, clientId, metadata, clientAuthentication) {
if (typeof clientId !== 'string' || !clientId.length) {
throw CodedTypeError('"clientId" must be a non-empty string', ERR_INVALID_ARG_TYPE);
}
if (typeof metadata === 'string') {
metadata = { client_secret: metadata };
}
if (metadata?.client_id !== undefined && clientId !== metadata.client_id) {
throw CodedTypeError('"clientId" and "metadata.client_id" must be the same', ERR_INVALID_ARG_VALUE);
}
const client = {
...structuredClone(metadata),
client_id: clientId,
};
client[oauth.clockSkew] = metadata?.[oauth.clockSkew] ?? 0;
client[oauth.clockTolerance] = metadata?.[oauth.clockTolerance] ?? 30;
let auth;
if (clientAuthentication) {
auth = clientAuthentication;
}
else {
if (typeof client.client_secret === 'string' &&
client.client_secret.length) {
auth = ClientSecretPost(client.client_secret);
}
else {
auth = None();
}
}
let c = Object.freeze(client);
const clone = structuredClone(server);
if (kEntraId in server) {
clone[oauth._expectedIssuer] = ({ claims: { tid } }) => server.issuer.replace('{tenantid}', tid);
}
let as = Object.freeze(clone);
props ||= new WeakMap();
props.set(this, {
__proto__: null,
as,
c,
auth,
tlsOnly: true,
jwksCache: {},
});
}
serverMetadata() {
const metadata = structuredClone(int(this).as);
addServerHelpers(metadata);
return metadata;
}
get timeout() {
return int(this).timeout;
}
set timeout(value) {
int(this).timeout = value;
}
get [customFetch]() {
return int(this).fetch;
}
set [customFetch](value) {
int(this).fetch = value;
}
}
Object.freeze(Configuration.prototype);
function getHelpers(response) {
let exp = undefined;
if (response.expires_in !== undefined) {
const now = new Date();
now.setSeconds(now.getSeconds() + response.expires_in);
exp = now.getTime();
}
return {
expiresIn: {
__proto__: null,
value() {
if (exp) {
const now = Date.now();
if (exp > now) {
return Math.floor((exp - now) / 1000);
}
return 0;
}
return undefined;
},
},
claims: {
__proto__: null,
value() {
try {
return oauth.getValidatedIdTokenClaims(this);
}
catch {
return undefined;
}
},
},
};
}
function addHelpers(response) {
Object.defineProperties(response, getHelpers(response));
}
export function getDPoPHandle(config, keyPair, options) {
checkConfig(config);
return oauth.DPoP(int(config).c, keyPair, options);
}
function wait(interval) {
return new Promise((resolve) => {
setTimeout(resolve, interval * 1000);
});
}
export async function pollDeviceAuthorizationGrant(config, deviceAuthorizationResponse, parameters, options) {
checkConfig(config);
parameters = new URLSearchParams(parameters);
let interval = deviceAuthorizationResponse.interval ?? 5;
const pollingSignal = options?.signal ??
AbortSignal.timeout(deviceAuthorizationResponse.expires_in * 1000);
try {
pollingSignal.throwIfAborted();
}
catch (err) {
errorHandler(err);
}
await wait(interval);
const { as, c, auth, fetch, tlsOnly, nonRepudiation, timeout, decrypt } = int(config);
const response = await oauth
.deviceCodeGrantRequest(as, c, auth, deviceAuthorizationResponse.device_code, {
[oauth.customFetch]: fetch,
[oauth.allowInsecureRequests]: !tlsOnly,
additionalParameters: parameters,
DPoP: options?.DPoP,
headers: new Headers(headers),
signal: pollingSignal.aborted ? pollingSignal : signal(timeout),
})
.catch(errorHandler);
const p = oauth.processDeviceCodeResponse(as, c, response, {
[oauth.jweDecrypt]: decrypt,
});
let result;
try {
result = await p;
}
catch (err) {
if (retryable(err, options)) {
return pollDeviceAuthorizationGrant(config, {
...deviceAuthorizationResponse,
interval,
}, parameters, {
...options,
signal: pollingSignal,
flag: retry,
});
}
if (err instanceof oauth.ResponseBodyError) {
switch (err.error) {
case 'slow_down':
interval += 5;
case 'authorization_pending':
return pollDeviceAuthorizationGrant(config, {
...deviceAuthorizationResponse,
interval,
}, parameters, {
...options,
signal: pollingSignal,
flag: undefined,
});
}
}
errorHandler(err);
}
result.id_token && (await nonRepudiation?.(response));
addHelpers(result);
return result;
}
export async function initiateDeviceAuthorization(config, parameters) {
checkConfig(config);
const { as, c, auth, fetch, tlsOnly, timeout } = int(config);
return oauth
.deviceAuthorizationRequest(as, c, auth, parameters, {
[oauth.customFetch]: fetch,
[oauth.allowInsecureRequests]: !tlsOnly,
headers: new Headers(headers),
signal: signal(timeout),
})
.then((response) => oauth.processDeviceAuthorizationResponse(as, c, response))
.catch(errorHandler);
}
export function allowInsecureRequests(config) {
int(config).tlsOnly = false;
}
export function setJwksCache(config, jwksCache) {
int(config).jwksCache = structuredClone(jwksCache);
}
export function getJwksCache(config) {
const cache = int(config).jwksCache;
if (cache.uat) {
return cache;
}
return undefined;
}
export function enableNonRepudiationChecks(config) {
checkConfig(config);
int(config).nonRepudiation = (response) => {
const { as, fetch, tlsOnly, timeout, jwksCache } = int(config);
return oauth
.validateApplicationLevelSignature(as, response, {
[oauth.customFetch]: fetch,
[oauth.allowInsecureRequests]: !tlsOnly,
headers: new Headers(headers),
signal: signal(timeout),
[oauth.jwksCache]: jwksCache,
})
.catch(errorHandler);
};
}
export function useJwtResponseMode(config) {
checkConfig(config);
if (int(config).hybrid) {
throw e('JARM cannot be combined with a hybrid response mode', undefined, oauth.UNSUPPORTED_OPERATION);
}
int(config).jarm = (authorizationResponse, expectedState) => validateJARMResponse(config, authorizationResponse, expectedState);
}
export function enableDetachedSignatureResponseChecks(config) {
if (!int(config).hybrid) {
throw e('"code id_token" response type must be configured to be used first', undefined, oauth.UNSUPPORTED_OPERATION);
}
int(config).hybrid = (authorizationResponse, expectedNonce, expectedState, maxAge) => validateCodeIdTokenResponse(config, authorizationResponse, expectedNonce, expectedState, maxAge, true);
}
export function useCodeIdTokenResponseType(config) {
checkConfig(config);
if (int(config).jarm) {
throw e('"code id_token" response type cannot be combined with JARM', undefined, oauth.UNSUPPORTED_OPERATION);
}
int(config).hybrid = (authorizationResponse, expectedNonce, expectedState, maxAge) => validateCodeIdTokenResponse(config, authorizationResponse, expectedNonce, expectedState, maxAge, false);
}
function stripParams(url) {
url = new URL(url);
url.search = '';
url.hash = '';
return url.href;
}
function webInstanceOf(input, toStringTag) {
try {
return Object.getPrototypeOf(input)[Symbol.toStringTag] === toStringTag;
}
catch {
return false;
}
}
export async function authorizationCodeGrant(config, currentUrl, checks, tokenEndpointParameters, options) {
checkConfig(config);
if (options?.flag !== retry &&
!(currentUrl instanceof URL) &&
!webInstanceOf(currentUrl, 'Request')) {
throw CodedTypeError('"currentUrl" must be an instance of URL, or Request', ERR_INVALID_ARG_TYPE);
}
let authResponse;
let redirectUri;
const { as, c, auth, fetch, tlsOnly, jarm, hybrid, nonRepudiation, timeout, decrypt } = int(config);
if (options?.flag === retry) {
authResponse = options.authResponse;
redirectUri = options.redirectUri;
}
else {
let request;
if (!(currentUrl instanceof URL)) {
if (currentUrl.method === 'POST') {
request = currentUrl;
}
currentUrl = new URL(currentUrl.url);
}
redirectUri = stripParams(currentUrl);
switch (true) {
case !!jarm:
authResponse = await jarm(currentUrl, checks?.expectedState);
break;
case !!hybrid:
authResponse = await hybrid(request || currentUrl, checks?.expectedNonce, checks?.expectedState, checks?.maxAge);
break;
default:
try {
authResponse = oauth.validateAuthResponse(as, c, currentUrl.searchParams, checks?.expectedState);
}
catch (err) {
return errorHandler(err);
}
}
}
const response = await oauth
.authorizationCodeGrantRequest(as, c, auth, authResponse, redirectUri, checks?.pkceCodeVerifier || oauth._nopkce, {
additionalParameters: tokenEndpointParameters,
[oauth.customFetch]: fetch,
[oauth.allowInsecureRequests]: !tlsOnly,
DPoP: options?.DPoP,
headers: new Headers(headers),
signal: signal(timeout),
})
.catch(errorHandler);
if (typeof checks?.expectedNonce === 'string' ||
typeof checks?.maxAge === 'number') {
checks.idTokenExpected = true;
}
const p = oauth.processAuthorizationCodeResponse(as, c, response, {
expectedNonce: checks?.expectedNonce,
maxAge: checks?.maxAge,
requireIdToken: checks?.idTokenExpected,
[oauth.jweDecrypt]: decrypt,
});
let result;
try {
result = await p;
}
catch (err) {
if (retryable(err, options)) {
return authorizationCodeGrant(config, undefined, checks, tokenEndpointParameters, {
...options,
flag: retry,
authResponse: authResponse,
redirectUri: redirectUri,
});
}
errorHandler(err);
}
result.id_token && (await nonRepudiation?.(response));
addHelpers(result);
return result;
}
async function validateJARMResponse(config, authorizationResponse, expectedState) {
const { as, c, fetch, tlsOnly, timeout, decrypt, jwksCache } = int(config);
return oauth
.validateJwtAuthResponse(as, c, authorizationResponse, expectedState, {
[oauth.customFetch]: fetch,
[oauth.allowInsecureRequests]: !tlsOnly,
headers: new Headers(headers),
signal: signal(timeout),
[oauth.jweDecrypt]: decrypt,
[oauth.jwksCache]: jwksCache,
})
.catch(errorHandler);
}
async function validateCodeIdTokenResponse(config, authorizationResponse, expectedNonce, expectedState, maxAge, fapi) {
if (typeof expectedNonce !== 'string') {
throw CodedTypeError('"expectedNonce" must be a string', ERR_INVALID_ARG_TYPE);
}
if (expectedState !== undefined && typeof expectedState !== 'string') {
throw CodedTypeError('"expectedState" must be a string', ERR_INVALID_ARG_TYPE);
}
const { as, c, fetch, tlsOnly, timeout, decrypt, jwksCache } = int(config);
return (fapi
? oauth.validateDetachedSignatureResponse
: oauth.validateCodeIdTokenResponse)(as, c, authorizationResponse, expectedNonce, expectedState, maxAge, {
[oauth.customFetch]: fetch,
[oauth.allowInsecureRequests]: !tlsOnly,
headers: new Headers(headers),
signal: signal(timeout),
[oauth.jweDecrypt]: decrypt,
[oauth.jwksCache]: jwksCache,
}).catch(errorHandler);
}
export async function refreshTokenGrant(config, refreshToken, parameters, options) {
checkConfig(config);
parameters = new URLSearchParams(parameters);
const { as, c, auth, fetch, tlsOnly, nonRepudiation, timeout, decrypt } = int(config);
const response = await oauth
.refreshTokenGrantRequest(as, c, auth, refreshToken, {
[oauth.customFetch]: fetch,
[oauth.allowInsecureRequests]: !tlsOnly,
additionalParameters: parameters,
DPoP: options?.DPoP,
headers: new Headers(headers),
signal: signal(timeout),
})
.catch(errorHandler);
const p = oauth.processRefreshTokenResponse(as, c, response, {
[oauth.jweDecrypt]: decrypt,
});
let result;
try {
result = await p;
}
catch (err) {
if (retryable(err, options)) {
return refreshTokenGrant(config, refreshToken, parameters, {
...options,
flag: retry,
});
}
errorHandler(err);
}
result.id_token && (await nonRepudiation?.(response));
addHelpers(result);
return result;
}
export async function clientCredentialsGrant(config, parameters, options) {
checkConfig(config);
parameters = new URLSearchParams(parameters);
const { as, c, auth, fetch, tlsOnly, timeout } = int(config);
const response = await oauth
.clientCredentialsGrantRequest(as, c, auth, parameters, {
[oauth.customFetch]: fetch,
[oauth.allowInsecureRequests]: !tlsOnly,
DPoP: options?.DPoP,
headers: new Headers(headers),
signal: signal(timeout),
})
.catch(errorHandler);
const p = oauth.processClientCredentialsResponse(as, c, response);
let result;
try {
result = await p;
}
catch (err) {
if (retryable(err, options)) {
return clientCredentialsGrant(config, parameters, {
...options,
flag: retry,
});
}
errorHandler(err);
}
addHelpers(result);
return result;
}
export function buildAuthorizationUrl(config, parameters) {
checkConfig(config);
const { as, c, tlsOnly, hybrid, jarm } = int(config);
const authorizationEndpoint = oauth.resolveEndpoint(as, 'authorization_endpoint', false, tlsOnly);
parameters = new URLSearchParams(parameters);
if (!parameters.has('client_id')) {
parameters.set('client_id', c.client_id);
}
if (!parameters.has('request_uri') && !parameters.has('request')) {
if (!parameters.has('response_type')) {
parameters.set('response_type', hybrid ? 'code id_token' : 'code');
}
if (jarm) {
parameters.set('response_mode', 'jwt');
}
}
for (const [k, v] of parameters.entries()) {
authorizationEndpoint.searchParams.append(k, v);
}
return authorizationEndpoint;
}
export async function buildAuthorizationUrlWithJAR(config, parameters, signingKey, options) {
checkConfig(config);
const authorizationEndpoint = buildAuthorizationUrl(config, parameters);
parameters = authorizationEndpoint.searchParams;
if (!signingKey) {
throw CodedTypeError('"signingKey" must be provided', ERR_INVALID_ARG_VALUE);
}
const { as, c } = int(config);
const request = await oauth
.issueRequestObject(as, c, parameters, signingKey, options)
.catch(errorHandler);
return buildAuthorizationUrl(config, { request });
}
export async function buildAuthorizationUrlWithPAR(config, parameters, options) {
checkConfig(config);
const authorizationEndpoint = buildAuthorizationUrl(config, parameters);
const { as, c, auth, fetch, tlsOnly, timeout } = int(config);
const response = await oauth
.pushedAuthorizationRequest(as, c, auth, authorizationEndpoint.searchParams, {
[oauth.customFetch]: fetch,
[oauth.allowInsecureRequests]: !tlsOnly,
DPoP: options?.DPoP,
headers: new Headers(headers),
signal: signal(timeout),
})
.catch(errorHandler);
const p = oauth.processPushedAuthorizationResponse(as, c, response);
let result;
try {
result = await p;
}
catch (err) {
if (retryable(err, options)) {
return buildAuthorizationUrlWithPAR(config, parameters, {
...options,
flag: retry,
});
}
errorHandler(err);
}
return buildAuthorizationUrl(config, { request_uri: result.request_uri });
}
export function buildEndSessionUrl(config, parameters) {
checkConfig(config);
const { as, c, tlsOnly } = int(config);
const endSessionEndpoint = oauth.resolveEndpoint(as, 'end_session_endpoint', false, tlsOnly);
parameters = new URLSearchParams(parameters);
if (!parameters.has('client_id')) {
parameters.set('client_id', c.client_id);
}
for (const [k, v] of parameters.entries()) {
endSessionEndpoint.searchParams.append(k, v);
}
return endSessionEndpoint;
}
function checkConfig(input) {
if (!(input instanceof Configuration)) {
throw CodedTypeError('"config" must be an instance of Configuration', ERR_INVALID_ARG_TYPE);
}
if (Object.getPrototypeOf(input) !== Configuration.prototype) {
throw CodedTypeError('subclassing Configuration is not allowed', ERR_INVALID_ARG_VALUE);
}
}
function signal(timeout) {
return timeout ? AbortSignal.timeout(timeout * 1000) : undefined;
}
export async function fetchUserInfo(config, accessToken, expectedSubject, options) {
checkConfig(config);
const { as, c, fetch, tlsOnly, nonRepudiation, timeout, decrypt } = int(config);
const response = await oauth
.userInfoRequest(as, c, accessToken, {
[oauth.customFetch]: fetch,
[oauth.allowInsecureRequests]: !tlsOnly,
DPoP: options?.DPoP,
headers: new Headers(headers),
signal: signal(timeout),
})
.catch(errorHandler);
let exec = oauth.processUserInfoResponse(as, c, expectedSubject, response, {
[oauth.jweDecrypt]: decrypt,
});
let result;
try {
result = await exec;
}
catch (err) {
if (retryable(err, options)) {
return fetchUserInfo(config, accessToken, expectedSubject, {
...options,
flag: retry,
});
}
errorHandler(err);
}
getContentType(response) === 'application/jwt' &&
(await nonRepudiation?.(response));
return result;
}
function retryable(err, options) {
if (options?.DPoP && options.flag !== retry) {
return oauth.isDPoPNonceError(err);
}
return false;
}
export async function tokenIntrospection(config, token, parameters) {
checkConfig(config);
const { as, c, auth, fetch, tlsOnly, nonRepudiation, timeout, decrypt } = int(config);
const response = await oauth
.introspectionRequest(as, c, auth, token, {
[oauth.customFetch]: fetch,
[oauth.allowInsecureRequests]: !tlsOnly,
additionalParameters: new URLSearchParams(parameters),
headers: new Headers(headers),
signal: signal(timeout),
})
.catch(errorHandler);
const result = await oauth
.processIntrospectionResponse(as, c, response, {
[oauth.jweDecrypt]: decrypt,
})
.catch(errorHandler);
getContentType(response) === 'application/token-introspection+jwt' &&
(await nonRepudiation?.(response));
return result;
}
const retry = Symbol();
export async function genericGrantRequest(config, grantType, parameters, options) {
checkConfig(config);
const { as, c, auth, fetch, tlsOnly, timeout, decrypt } = int(config);
const result = await oauth
.genericTokenEndpointRequest(as, c, auth, grantType, new URLSearchParams(parameters), {
[oauth.customFetch]: fetch,
[oauth.allowInsecureRequests]: !tlsOnly,
DPoP: options?.DPoP,
headers: new Headers(headers),
signal: signal(timeout),
})
.then((response) => oauth.processGenericTokenEndpointResponse(as, c, response, {
[oauth.jweDecrypt]: decrypt,
}))
.catch(errorHandler);
addHelpers(result);
return result;
}
export async function tokenRevocation(config, token, parameters) {
checkConfig(config);
const { as, c, auth, fetch, tlsOnly, timeout } = int(config);
return oauth
.revocationRequest(as, c, auth, token, {
[oauth.customFetch]: fetch,
[oauth.allowInsecureRequests]: !tlsOnly,
additionalParameters: new URLSearchParams(parameters),
headers: new Headers(headers),
signal: signal(timeout),
})
.then(oauth.processRevocationResponse)
.catch(errorHandler);
}
export async function fetchProtectedResource(config, accessToken, url, method, body, headers, options) {
checkConfig(config);
headers ||= new Headers();
if (!headers.has('user-agent')) {
headers.set('user-agent', USER_AGENT);
}
const { fetch, tlsOnly, timeout } = int(config);
const exec = oauth.protectedResourceRequest(accessToken, method, url, headers, body, {
[oauth.customFetch]: fetch,
[oauth.allowInsecureRequests]: !tlsOnly,
DPoP: options?.DPoP,
signal: signal(timeout),
});
let result;
try {
result = await exec;
}
catch (err) {
if (retryable(err, options)) {
return fetchProtectedResource(config, accessToken, url, method, body, headers, {
...options,
flag: retry,
});
}
errorHandler(err);
}
return result;
}
function getContentType(response) {
return response.headers.get('content-type')?.split(';')[0];
}
//# sourceMappingURL=index.js.map
File diff suppressed because one or more lines are too long
-103
View File
@@ -1,103 +0,0 @@
import * as client from './index.js';
import type { PrivateKey } from 'oauth4webapi';
import type * as express from 'express';
import type passport from 'passport';
export type VerifyFunction = (
/**
* Parsed Token Endpoint Response returned by the authorization server with
* attached helpers.
*/
tokens: client.TokenEndpointResponse & client.TokenEndpointResponseHelpers, verified: passport.AuthenticateCallback) => void;
export type VerifyFunctionWithRequest = (req: express.Request,
/**
* Parsed Token Endpoint Response returned by the authorization server with
* attached helpers.
*/
tokens: client.TokenEndpointResponse & client.TokenEndpointResponseHelpers, verified: passport.AuthenticateCallback) => void;
/**
* Retrieve an openid-client DPoPHandle for a given request.
*/
export type getDPoPHandle = (req: express.Request) => client.DPoPHandle;
interface StrategyOptionsBase {
/**
* Openid-client Configuration instance.
*/
config: client.Configuration;
/**
* Name of the strategy, default is the host component of the authorization
* server's issuer identifier.
*/
name?: string;
/**
* Property in the session to use for storing the authorization request state,
* default is the host component of the authorization server's issuer
* identifier.
*/
sessionKey?: string;
/**
* Function used to retrieve an openid-client DPoPHandle for a given request,
* when provided the strategy will use DPoP where applicable.
*/
DPoP?: getDPoPHandle;
/**
* URL to which the authorization server will redirect the user after
* obtaining authorization. This will be used as the `redirect_uri`
* authorization request parameter unless specified elsewhere.
*/
callbackURL?: string;
/**
* Authorization Request Scope. This will be used as the `scope` authorization
* request parameter unless specified elsewhere.
*/
scope?: string;
/**
* Whether the strategy will use PAR. Default is `false`.
*/
usePAR?: boolean;
/**
* Whether the strategy will use JAR. Its value can be a private key to sign
* with or an array with the private key and a modify assertion function that
* will be used to modify the request object before it is signed. Default is
* `false`.
*/
useJAR?: false | client.CryptoKey | PrivateKey | [client.CryptoKey | PrivateKey, client.ModifyAssertionFunction];
/**
* Whether the verify function should get the `req` as first argument instead.
* Default is `false`.
*/
passReqToCallback?: boolean;
}
export interface StrategyOptions extends StrategyOptionsBase {
passReqToCallback?: false;
}
export interface StrategyOptionsWithRequest extends StrategyOptionsBase {
passReqToCallback: true;
}
export declare class Strategy implements passport.Strategy {
/**
* Name of the strategy
*/
readonly name: string;
constructor(options: StrategyOptions, verify: VerifyFunction);
constructor(options: StrategyOptionsWithRequest, verify: VerifyFunctionWithRequest);
/**
* Return extra parameters to be included an authorization request.
*/
authorizationRequestParams<TOptions extends passport.AuthenticateOptions = passport.AuthenticateOptions>(req: express.Request, options: TOptions): URLSearchParams | Record<string, string> | undefined;
/**
* Return extra parameters to be included in the authorization code grant
* token endpoint request.
*/
authorizationCodeGrantParameters<TOptions extends passport.AuthenticateOptions = passport.AuthenticateOptions>(req: express.Request, options: TOptions): URLSearchParams | Record<string, string> | undefined;
/**
* Return the current request URL.
*
* - Its `searchParams` are used as the authorization response parameters when
* the response type used by the client is `code`
* - Its value stripped of `searchParams` and `hash` is used as the
* `redirect_uri` authorization code grant token endpoint parameter
*/
currentUrl(req: express.Request): URL;
authenticate<TOptions extends passport.AuthenticateOptions = passport.AuthenticateOptions>(this: passport.StrategyCreated<Strategy, Strategy & passport.StrategyCreatedStatic>, req: express.Request, options: TOptions): void;
}
export {};
-167
View File
@@ -1,167 +0,0 @@
import * as client from './index.js';
export class Strategy {
name;
_config;
_verify;
_callbackURL;
_sessionKey;
_passReqToCallback;
_proxy;
_usePAR;
_useJAR;
_DPoP;
_scope;
constructor(options, verify) {
if (!(options?.config instanceof client.Configuration)) {
throw new TypeError();
}
if (typeof verify !== 'function') {
throw new TypeError();
}
const { host } = new URL(options.config.serverMetadata().issuer);
this.name = options.name ?? host;
this._sessionKey = options.sessionKey ?? host;
this._DPoP = options.DPoP;
this._config = options.config;
this._scope = options.scope;
this._useJAR = options.useJAR;
this._usePAR = options.usePAR;
this._verify = verify;
this._callbackURL = options.callbackURL;
this._passReqToCallback = options.passReqToCallback;
}
authorizationRequestParams(req, options) {
return {};
}
authorizationCodeGrantParameters(req, options) {
return {};
}
async authorizationRequest(req, options) {
try {
let redirectTo = client.buildAuthorizationUrl(this._config, new URLSearchParams(this.authorizationRequestParams(req, options)));
if (redirectTo.searchParams.get('response_type')?.includes('id_token')) {
redirectTo.searchParams.set('nonce', client.randomNonce());
}
const codeVerifier = client.randomPKCECodeVerifier();
const code_challenge = await client.calculatePKCECodeChallenge(codeVerifier);
redirectTo.searchParams.set('code_challenge', code_challenge);
redirectTo.searchParams.set('code_challenge_method', 'S256');
if (this._config
.serverMetadata()
.code_challenge_methods_supported?.includes('S256') !== true &&
!redirectTo.searchParams.has('nonce')) {
redirectTo.searchParams.set('state', client.randomState());
}
if (this._callbackURL && !redirectTo.searchParams.has('redirect_uri')) {
redirectTo.searchParams.set('redirect_uri', this._callbackURL);
}
if (this._scope && !redirectTo.searchParams.has('scope')) {
redirectTo.searchParams.set('scope', this._scope);
}
const sessionKey = this._sessionKey;
const stateData = { code_verifier: codeVerifier };
let nonce;
if ((nonce = redirectTo.searchParams.get('nonce'))) {
stateData.nonce = nonce;
}
let state;
if ((state = redirectTo.searchParams.get('state'))) {
stateData.state = state;
}
let max_age;
if ((max_age = redirectTo.searchParams.get('max_age'))) {
stateData.max_age = parseInt(max_age, 10);
}
;
req.session[sessionKey] = stateData;
if (this._useJAR) {
let key;
let modifyAssertion;
if (Array.isArray(this._useJAR)) {
;
[key, modifyAssertion] = this._useJAR;
}
else {
key = this._useJAR;
}
redirectTo = await client.buildAuthorizationUrlWithJAR(this._config, redirectTo.searchParams, key, { [client.modifyAssertion]: modifyAssertion });
}
if (this._usePAR) {
redirectTo = await client.buildAuthorizationUrlWithPAR(this._config, redirectTo.searchParams, { DPoP: this._DPoP?.(req) });
}
return this.redirect(redirectTo.href);
}
catch (err) {
return this.error(err);
}
}
async authorizationCodeGrant(req, currentUrl, options) {
try {
const sessionKey = this._sessionKey;
const stateData = req.session[sessionKey];
if (!stateData?.code_verifier) {
return this.fail({
message: 'Unable to verify authorization request state',
});
}
let input = currentUrl;
if (req.method === 'POST') {
input = new Request(currentUrl.href, {
method: 'POST',
headers: Object.entries(req.headersDistinct).reduce((acc, [key, values]) => {
for (const value of values) {
acc.append(key, value);
}
return acc;
}, new Headers()),
body: req,
duplex: 'half',
});
}
const tokens = await client.authorizationCodeGrant(this._config, input, {
pkceCodeVerifier: stateData.code_verifier,
expectedNonce: stateData.nonce,
expectedState: stateData.state,
maxAge: stateData.max_age,
}, this.authorizationCodeGrantParameters(req, options), { DPoP: this._DPoP?.(req) });
const verified = (err, user, info) => {
if (err)
return this.error(err);
if (!user)
return this.fail(info);
return this.success(user);
};
if (this._passReqToCallback) {
return this._verify(req, tokens, verified);
}
return this._verify(tokens, verified);
}
catch (err) {
if (err instanceof client.AuthorizationResponseError &&
err.error === 'access_denied') {
return this.fail({
message: err.error_description || err.error,
...Object.fromEntries(err.cause.entries()),
});
}
return this.error(err);
}
}
currentUrl(req) {
return new URL(`${req.protocol}://${req.host}${req.url}`);
}
authenticate(req, options) {
if (!req.session) {
return this.error(new Error('OAuth 2.0 authentication requires session support. Did you forget to use express-session middleware?'));
}
const currentUrl = this.currentUrl(req);
if ((req.method === 'GET' && currentUrl.searchParams.size === 0) ||
(currentUrl.searchParams.size === 1 && currentUrl.searchParams.has('iss'))) {
Strategy.prototype.authorizationRequest.call(this, req, options);
}
else {
Strategy.prototype.authorizationCodeGrant.call(this, req, currentUrl, options);
}
}
}
//# sourceMappingURL=passport.js.map
File diff suppressed because one or more lines are too long