mirror of
https://github.com/hanzoai/openid-client.git
synced 2026-09-24 17:01:10 +00:00
chore: cleanup after release
This commit is contained in:
Vendored
-1920
File diff suppressed because it is too large
Load Diff
-986
@@ -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
Vendored
-103
@@ -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 {};
|
||||
@@ -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
Reference in New Issue
Block a user