UNPKG

@sphereon/oid4vci-client

Version:

OpenID for Verifiable Credential Issuance (OpenID4VCI) client

1,153 lines (1,134 loc) • 195 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // lib/index.ts var index_exports = {}; __export(index_exports, { AccessTokenClient: () => AccessTokenClient, AccessTokenClientV1_0_11: () => AccessTokenClientV1_0_11, CredentialOfferClient: () => CredentialOfferClient, CredentialOfferClientV1_0_11: () => CredentialOfferClientV1_0_11, CredentialOfferClientV1_0_13: () => CredentialOfferClientV1_0_13, CredentialRequestClient: () => CredentialRequestClient, CredentialRequestClientBuilder: () => CredentialRequestClientBuilder, CredentialRequestClientBuilderV1_0_11: () => CredentialRequestClientBuilderV1_0_11, CredentialRequestClientBuilderV1_0_13: () => CredentialRequestClientBuilderV1_0_13, CredentialRequestClientV1_0_11: () => CredentialRequestClientV1_0_11, LOG: () => LOG2, MetadataClient: () => MetadataClient, MetadataClientV1_0_11: () => MetadataClientV1_0_11, MetadataClientV1_0_13: () => MetadataClientV1_0_13, OpenID4VCIClient: () => OpenID4VCIClient, OpenID4VCIClientV1_0_11: () => OpenID4VCIClientV1_0_11, OpenID4VCIClientV1_0_13: () => OpenID4VCIClientV1_0_13, ProofOfPossessionBuilder: () => ProofOfPossessionBuilder, acquireAuthorizationChallengeAuthCode: () => acquireAuthorizationChallengeAuthCode, acquireAuthorizationChallengeAuthCodeUsingRequest: () => acquireAuthorizationChallengeAuthCodeUsingRequest, buildProof: () => buildProof, constructBaseResponse: () => constructBaseResponse, createAuthorizationChallengeRequest: () => createAuthorizationChallengeRequest, createAuthorizationRequestUrl: () => createAuthorizationRequestUrl, createAuthorizationRequestUrlV1_0_11: () => createAuthorizationRequestUrlV1_0_11, createJwtBearerClientAssertion: () => createJwtBearerClientAssertion, createSignedAuthRequestWhenNeeded: () => createSignedAuthRequestWhenNeeded, generateMissingPKCEOpts: () => generateMissingPKCEOpts, handleCredentialOfferUri: () => handleCredentialOfferUri, isUriEncoded: () => isUriEncoded, retrieveWellknown: () => retrieveWellknown, sendAuthorizationChallengeRequest: () => sendAuthorizationChallengeRequest, sendNotification: () => sendNotification }); module.exports = __toCommonJS(index_exports); var import_oid4vci_common26 = require("@sphereon/oid4vci-common"); // lib/AccessTokenClient.ts var import_oid4vc_common3 = require("@sphereon/oid4vc-common"); var import_oid4vci_common9 = require("@sphereon/oid4vci-common"); var import_ssi_types4 = require("@sphereon/ssi-types"); // lib/MetadataClientV1_0_13.ts var import_oid4vci_common8 = require("@sphereon/oid4vci-common"); var import_ssi_types3 = require("@sphereon/ssi-types"); // lib/functions/AuthorizationUtil.ts var import_oid4vci_common = require("@sphereon/oid4vci-common"); var generateMissingPKCEOpts = /* @__PURE__ */ __name((pkce) => { if (pkce.disabled) { return pkce; } if (!pkce.codeChallengeMethod) { pkce.codeChallengeMethod = import_oid4vci_common.CodeChallengeMethod.S256; } if (!pkce.codeVerifier) { pkce.codeVerifier = (0, import_oid4vci_common.generateCodeVerifier)(); } (0, import_oid4vci_common.assertValidCodeVerifier)(pkce.codeVerifier); if (!pkce.codeChallenge) { pkce.codeChallenge = (0, import_oid4vci_common.createCodeChallenge)(pkce.codeVerifier, pkce.codeChallengeMethod); } return pkce; }, "generateMissingPKCEOpts"); // lib/functions/notifications.ts var import_oid4vci_common3 = require("@sphereon/oid4vci-common"); // lib/types/index.ts var import_oid4vci_common2 = require("@sphereon/oid4vci-common"); var import_ssi_types = require("@sphereon/ssi-types"); var LOG = import_oid4vci_common2.VCI_LOGGERS.options("sphereon:oid4vci:client", { methods: [ import_ssi_types.LogMethod.EVENT, import_ssi_types.LogMethod.DEBUG_PKG ] }).get("sphereon:oid4vci:client"); // lib/functions/notifications.ts async function sendNotification(credentialRequestOpts, request, accessToken) { LOG.info(`Sending status notification event '${request.event}' for id ${request.notification_id}`); if (!credentialRequestOpts.notificationEndpoint) { throw Error(`Cannot send notification when no notification endpoint is provided`); } const token = accessToken ?? credentialRequestOpts.token; const response = await (0, import_oid4vci_common3.post)(credentialRequestOpts.notificationEndpoint, JSON.stringify(request), { ...token && { bearerToken: token } }); const error = response.errorBody?.error !== void 0; const result = { error, response: error ? response.errorBody : void 0 }; if (error) { LOG.warning(`Notification endpoint returned an error for event '${request.event}' and id ${request.notification_id}: ${response.errorBody}`); } else { LOG.debug(`Notification endpoint returned success for event '${request.event}' and id ${request.notification_id}`); } return result; } __name(sendNotification, "sendNotification"); // lib/functions/OpenIDUtils.ts var import_oid4vci_common4 = require("@sphereon/oid4vci-common"); var import_ssi_types2 = require("@sphereon/ssi-types"); var logger = import_ssi_types2.Loggers.DEFAULT.get("sphereon:openid4vci:openid-utils"); var retrieveWellknown = /* @__PURE__ */ __name(async (host, endpointType, opts) => { const result = await (0, import_oid4vci_common4.getJson)(`${host.endsWith("/") ? host.slice(0, -1) : host}${endpointType}`, { exceptionOnHttpErrorStatus: opts?.errorOnNotFound }); if (result.origResponse.status >= 400) { logger.debug(`host ${host} with endpoint type ${endpointType} status: ${result.origResponse.status}, ${result.origResponse.statusText}`); } return result; }, "retrieveWellknown"); // lib/functions/AccessTokenUtil.ts var import_oid4vc_common = require("@sphereon/oid4vc-common"); var import_oid4vci_common6 = require("@sphereon/oid4vci-common"); // lib/ProofOfPossessionBuilder.ts var import_oid4vci_common5 = require("@sphereon/oid4vci-common"); var ProofOfPossessionBuilder = class _ProofOfPossessionBuilder { static { __name(this, "ProofOfPossessionBuilder"); } proof; callbacks; version; mode = "pop"; kid; jwk; aud; clientId; issuer; jwt; alg; jti; cNonce; typ; constructor({ proof, callbacks, jwt, accessTokenResponse, version, mode = "pop" }) { this.mode = mode; this.proof = proof; this.callbacks = callbacks; this.version = version; if (jwt) { this.withJwt(jwt); } else { this.withTyp(version < import_oid4vci_common5.OpenId4VCIVersion.VER_1_0_11 || mode === "JWT" ? "JWT" : "openid4vci-proof+jwt"); } if (accessTokenResponse) { this.withAccessTokenResponse(accessTokenResponse); } } static manual({ jwt, callbacks, version, mode = "JWT" }) { return new _ProofOfPossessionBuilder({ callbacks, jwt, version, mode }); } static fromJwt({ jwt, callbacks, version, mode = "pop" }) { return new _ProofOfPossessionBuilder({ callbacks, jwt, version, mode }); } static fromAccessTokenResponse({ accessTokenResponse, callbacks, version, mode = "pop" }) { return new _ProofOfPossessionBuilder({ callbacks, accessTokenResponse, version, mode }); } static fromProof(proof, version) { return new _ProofOfPossessionBuilder({ proof, version }); } withAud(aud) { this.aud = aud; return this; } withClientId(clientId) { this.clientId = clientId; return this; } withKid(kid) { this.kid = kid; return this; } withJWK(jwk) { this.jwk = jwk; return this; } withIssuer(issuer) { this.issuer = issuer; return this; } withAlg(alg) { this.alg = alg; return this; } withJti(jti) { this.jti = jti; return this; } withTyp(typ) { if (this.mode === "pop" && this.version >= import_oid4vci_common5.OpenId4VCIVersion.VER_1_0_11) { if (!!typ && typ !== "openid4vci-proof+jwt") { throw Error(`typ must be openid4vci-proof+jwt for version 1.0.11 and up. Provided: ${typ}`); } } else { if (!!typ && typ !== "JWT") { throw Error(`typ must be jwt for version 1.0.10 and below. Provided: ${typ}`); } } this.typ = typ; return this; } withAccessTokenNonce(cNonce) { this.cNonce = cNonce; return this; } withAccessTokenResponse(accessToken) { if (accessToken.c_nonce) { this.withAccessTokenNonce(accessToken.c_nonce); } return this; } withEndpointMetadata(endpointMetadata) { this.withIssuer(endpointMetadata.issuer); return this; } withJwt(jwt) { if (!jwt) { throw new Error(import_oid4vci_common5.NO_JWT_PROVIDED); } this.jwt = jwt; if (!jwt.header) { throw Error(`No JWT header present`); } else if (!jwt.payload) { throw Error(`No JWT payload present`); } if (jwt.header.kid) { this.withKid(jwt.header.kid); } if (jwt.header.typ) { this.withTyp(jwt.header.typ); } if (!this.typ && this.version >= import_oid4vci_common5.OpenId4VCIVersion.VER_1_0_11) { this.withTyp("openid4vci-proof+jwt"); } this.withAlg(jwt.header.alg); if (Array.isArray(jwt.payload.aud)) { throw Error("We cannot handle multiple aud values currently"); } if (jwt.payload) { if (jwt.payload.iss) this.mode === "pop" ? this.withClientId(jwt.payload.iss) : this.withIssuer(jwt.payload.iss); if (jwt.payload.aud) this.mode === "pop" ? this.withIssuer(jwt.payload.aud) : this.withAud(jwt.payload.aud); if (jwt.payload.jti) this.withJti(jwt.payload.jti); if (jwt.payload.nonce) this.withAccessTokenNonce(jwt.payload.nonce); } return this; } async build() { if (this.proof) { return Promise.resolve(this.proof); } else if (this.callbacks) { return await (0, import_oid4vci_common5.createProofOfPossession)(this.mode, this.callbacks, { typ: this.typ ?? (this.version < import_oid4vci_common5.OpenId4VCIVersion.VER_1_0_11 || this.mode === "JWT" ? "JWT" : "openid4vci-proof+jwt"), kid: this.kid, jwk: this.jwk, jti: this.jti, alg: this.alg, aud: this.aud, issuer: this.issuer, clientId: this.clientId, ...this.cNonce && { nonce: this.cNonce } }, this.jwt); } throw new Error(import_oid4vci_common5.PROOF_CANT_BE_CONSTRUCTED); } }; // lib/functions/AccessTokenUtil.ts var createJwtBearerClientAssertion = /* @__PURE__ */ __name(async (request, opts) => { const { asOpts, credentialIssuer } = opts; if (asOpts?.clientOpts?.clientAssertionType === "urn:ietf:params:oauth:client-assertion-type:jwt-bearer") { const { clientId = request.client_id, signCallbacks, alg } = asOpts.clientOpts; let { kid } = asOpts.clientOpts; if (!clientId) { return Promise.reject(Error(`Not client_id supplied, but client-assertion jwt-bearer requested.`)); } else if (!kid) { return Promise.reject(Error(`No kid supplied, but client-assertion jwt-bearer requested.`)); } else if (typeof signCallbacks?.signCallback !== "function") { return Promise.reject(Error(`No sign callback supplied, but client-assertion jwt-bearer requested.`)); } else if (!credentialIssuer) { return Promise.reject(Error(`No credential issuer supplied, but client-assertion jwt-bearer requested.`)); } if (clientId.startsWith("http") && kid.includes("#")) { kid = kid.split("#")[1]; } const jwt = { header: { typ: "JWT", kid, alg: alg ?? "ES256" }, payload: { iss: clientId, sub: clientId, aud: credentialIssuer, jti: (0, import_oid4vc_common.uuidv4)(), exp: Math.floor(Date.now()) / 1e3 + 60, iat: Math.floor(Date.now()) / 1e3 - 60 } }; const pop = await ProofOfPossessionBuilder.fromJwt({ jwt, callbacks: signCallbacks, version: opts.version ?? import_oid4vci_common6.OpenId4VCIVersion.VER_1_0_13, mode: "JWT" }).build(); request.client_assertion_type = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"; request.client_assertion = pop.jwt; } }, "createJwtBearerClientAssertion"); // lib/functions/CredentialOfferCommons.ts var import_oid4vci_common7 = require("@sphereon/oid4vci-common"); var import_cross_fetch = require("cross-fetch"); function isUriEncoded(str) { const pattern = /%[0-9A-F]{2}/i; return pattern.test(str); } __name(isUriEncoded, "isUriEncoded"); async function handleCredentialOfferUri(uri) { const uriObj = (0, import_oid4vci_common7.getURIComponentsAsArray)(uri); const credentialOfferUri = decodeURIComponent(uriObj["credential_offer_uri"]); const decodedUri = isUriEncoded(credentialOfferUri) ? decodeURIComponent(credentialOfferUri) : credentialOfferUri; const response = await (0, import_cross_fetch.fetch)(decodedUri); if (!(response && response.status >= 200 && response.status < 400)) { return Promise.reject(`the credential offer URI endpoint call was not successful. http code ${response.status} - reason ${response.statusText}`); } if (response.headers.get("Content-Type")?.startsWith("application/json") === false) { return Promise.reject("the credential offer URI endpoint did not return content type application/json"); } return { credential_offer: (0, import_oid4vci_common7.decodeJsonProperties)(await response.json()) }; } __name(handleCredentialOfferUri, "handleCredentialOfferUri"); function constructBaseResponse(request, scheme, baseUrl) { const clientId = (0, import_oid4vci_common7.getClientIdFromCredentialOfferPayload)(request.credential_offer); const grants = request.credential_offer?.grants; return { scheme, baseUrl, ...clientId && { clientId }, ...request, ...grants?.authorization_code?.issuer_state && { issuerState: grants.authorization_code.issuer_state }, ...grants?.[import_oid4vci_common7.PRE_AUTH_GRANT_LITERAL]?.[import_oid4vci_common7.PRE_AUTH_CODE_LITERAL] && { preAuthorizedCode: grants[import_oid4vci_common7.PRE_AUTH_GRANT_LITERAL][import_oid4vci_common7.PRE_AUTH_CODE_LITERAL] }, ...request.credential_offer?.grants?.[import_oid4vci_common7.PRE_AUTH_GRANT_LITERAL]?.tx_code && { txCode: request.credential_offer.grants[import_oid4vci_common7.PRE_AUTH_GRANT_LITERAL].tx_code } }; } __name(constructBaseResponse, "constructBaseResponse"); // lib/MetadataClientV1_0_13.ts var logger2 = import_ssi_types3.Loggers.DEFAULT.get("sphereon:oid4vci:metadata"); var MetadataClientV1_0_13 = class _MetadataClientV1_0_13 { static { __name(this, "MetadataClientV1_0_13"); } /** * Retrieve metadata using the Initiation obtained from a previous step * * @param credentialOffer */ static async retrieveAllMetadataFromCredentialOffer(credentialOffer) { return _MetadataClientV1_0_13.retrieveAllMetadataFromCredentialOfferRequest(credentialOffer.credential_offer); } /** * Retrieve the metada using the initiation request obtained from a previous step * @param request */ static async retrieveAllMetadataFromCredentialOfferRequest(request) { const issuer = (0, import_oid4vci_common8.getIssuerFromCredentialOfferPayload)(request); if (issuer) { return _MetadataClientV1_0_13.retrieveAllMetadata(issuer); } throw new Error("can't retrieve metadata from CredentialOfferRequest. No issuer field is present"); } /** * Retrieve all metadata from an issuer * @param issuer The issuer URL * @param opts */ static async retrieveAllMetadata(issuer, opts) { let token_endpoint; let credential_endpoint; let deferred_credential_endpoint; let authorization_endpoint; let authorization_challenge_endpoint; let authorizationServerType = "OID4VCI"; let authorization_servers = [ issuer ]; const oid4vciResponse = await _MetadataClientV1_0_13.retrieveOpenID4VCIServerMetadata(issuer, { errorOnNotFound: false }); let credentialIssuerMetadata = oid4vciResponse?.successBody; if (credentialIssuerMetadata) { logger2.debug(`Issuer ${issuer} OID4VCI well-known server metadata\r ${JSON.stringify(credentialIssuerMetadata)}`); credential_endpoint = credentialIssuerMetadata.credential_endpoint; deferred_credential_endpoint = credentialIssuerMetadata.deferred_credential_endpoint; if (credentialIssuerMetadata.token_endpoint) { token_endpoint = credentialIssuerMetadata.token_endpoint; } authorization_challenge_endpoint = credentialIssuerMetadata.authorization_challenge_endpoint; if (credentialIssuerMetadata.authorization_servers) { authorization_servers = credentialIssuerMetadata.authorization_servers; } } let response = await retrieveWellknown(authorization_servers[0], import_oid4vci_common8.WellKnownEndpoints.OPENID_CONFIGURATION, { errorOnNotFound: false }); let authMetadata = response.successBody; if (authMetadata) { logger2.debug(`Issuer ${issuer} has OpenID Connect Server metadata in well-known location`); authorizationServerType = "OIDC"; } else { response = await retrieveWellknown(authorization_servers[0], import_oid4vci_common8.WellKnownEndpoints.OAUTH_AS, { errorOnNotFound: false }); authMetadata = response.successBody; } if (!authMetadata) { if (!authorization_servers.includes(issuer)) { throw Error(`Issuer ${issuer} provided a separate authorization server ${authorization_servers}, but that server did not provide metadata`); } } else { if (!authorizationServerType) { authorizationServerType = "OAuth 2.0"; } logger2.debug(`Issuer ${issuer} has ${authorizationServerType} Server metadata in well-known location`); if (!authMetadata.authorization_endpoint) { console.warn(`Issuer ${issuer} of type ${authorizationServerType} has no authorization_endpoint! Will use ${authorization_endpoint}. This only works for pre-authorized flows`); } else if (authorization_endpoint && authMetadata.authorization_endpoint !== authorization_endpoint) { throw Error(`Credential issuer has a different authorization_endpoint (${authorization_endpoint}) from the Authorization Server (${authMetadata.authorization_endpoint})`); } authorization_endpoint = authMetadata.authorization_endpoint; if (authorization_challenge_endpoint && authMetadata.authorization_challenge_endpoint !== authorization_challenge_endpoint) { throw Error(`Credential issuer has a different authorization_challenge_endpoint (${authorization_challenge_endpoint}) from the Authorization Server (${authMetadata.authorization_challenge_endpoint})`); } authorization_challenge_endpoint = authMetadata.authorization_challenge_endpoint; if (!authMetadata.token_endpoint) { throw Error(`Authorization Server ${authorization_servers} did not provide a token_endpoint`); } else if (token_endpoint && authMetadata.token_endpoint !== token_endpoint) { throw Error(`Credential issuer has a different token_endpoint (${token_endpoint}) from the Authorization Server (${authMetadata.token_endpoint})`); } token_endpoint = authMetadata.token_endpoint; if (authMetadata.credential_endpoint) { if (credential_endpoint && authMetadata.credential_endpoint !== credential_endpoint) { logger2.debug(`Credential issuer has a different credential_endpoint (${credential_endpoint}) from the Authorization Server (${authMetadata.credential_endpoint}). Will use the issuer value`); } else { credential_endpoint = authMetadata.credential_endpoint; } } if (authMetadata.deferred_credential_endpoint) { if (deferred_credential_endpoint && authMetadata.deferred_credential_endpoint !== deferred_credential_endpoint) { logger2.debug(`Credential issuer has a different deferred_credential_endpoint (${deferred_credential_endpoint}) from the Authorization Server (${authMetadata.deferred_credential_endpoint}). Will use the issuer value`); } else { deferred_credential_endpoint = authMetadata.deferred_credential_endpoint; } } } if (!authorization_endpoint) { logger2.debug(`Issuer ${issuer} does not expose authorization_endpoint, so only pre-auth will be supported`); } if (!token_endpoint) { logger2.debug(`Issuer ${issuer} does not have a token_endpoint listed in well-known locations!`); if (opts?.errorOnNotFound) { throw Error(`Could not deduce the token_endpoint for ${issuer}`); } else { token_endpoint = `${issuer}${issuer.endsWith("/") ? "token" : "/token"}`; } } if (!credential_endpoint) { logger2.debug(`Issuer ${issuer} does not have a credential_endpoint listed in well-known locations!`); if (opts?.errorOnNotFound) { throw Error(`Could not deduce the credential endpoint for ${issuer}`); } else { credential_endpoint = `${issuer}${issuer.endsWith("/") ? "credential" : "/credential"}`; } } if (!credentialIssuerMetadata && authMetadata) { credentialIssuerMetadata = authMetadata; } logger2.debug(`Issuer ${issuer} token endpoint ${token_endpoint}, credential endpoint ${credential_endpoint}`); return { issuer, token_endpoint, credential_endpoint, deferred_credential_endpoint, authorization_server: authorization_servers[0], authorization_endpoint, authorization_challenge_endpoint, authorizationServerType, credentialIssuerMetadata, authorizationServerMetadata: authMetadata }; } /** * Retrieve only the OID4VCI metadata for the issuer. So no OIDC/OAuth2 metadata * * @param issuerHost The issuer hostname * @param opts */ static async retrieveOpenID4VCIServerMetadata(issuerHost, opts) { return retrieveWellknown(issuerHost, import_oid4vci_common8.WellKnownEndpoints.OPENID4VCI_ISSUER, { errorOnNotFound: opts?.errorOnNotFound === void 0 ? true : opts.errorOnNotFound }); } }; // lib/functions/dpopUtil.ts var import_oid4vc_common2 = require("@sphereon/oid4vc-common"); function shouldRetryTokenRequestWithDPoPNonce(response) { if (!response.errorBody || response.errorBody.error !== import_oid4vc_common2.dpopTokenRequestNonceError) { return { ok: false }; } const dPoPNonce = response.origResponse.headers.get("DPoP-Nonce"); if (!dPoPNonce) { throw new Error("Missing required DPoP-Nonce header."); } return { ok: true, dpopNonce: dPoPNonce }; } __name(shouldRetryTokenRequestWithDPoPNonce, "shouldRetryTokenRequestWithDPoPNonce"); function shouldRetryResourceRequestWithDPoPNonce(response) { if (!response.errorBody || response.origResponse.status !== 401) { return { ok: false }; } const wwwAuthenticateHeader = response.origResponse.headers.get("WWW-Authenticate"); if (!wwwAuthenticateHeader?.includes(import_oid4vc_common2.dpopTokenRequestNonceError)) { return { ok: false }; } const dPoPNonce = response.origResponse.headers.get("DPoP-Nonce"); if (!dPoPNonce) { throw new Error("Missing required DPoP-Nonce header."); } return { ok: true, dpopNonce: dPoPNonce }; } __name(shouldRetryResourceRequestWithDPoPNonce, "shouldRetryResourceRequestWithDPoPNonce"); // lib/AccessTokenClient.ts var AccessTokenClient = class _AccessTokenClient { static { __name(this, "AccessTokenClient"); } async acquireAccessToken(opts) { const { asOpts, pin, codeVerifier, code, redirectUri, metadata, createDPoPOpts } = opts; const credentialOffer = opts.credentialOffer ? await (0, import_oid4vci_common9.assertedUniformCredentialOffer)(opts.credentialOffer) : void 0; const pinMetadata = credentialOffer && this.getPinMetadata(credentialOffer.credential_offer); const issuer = opts.credentialIssuer ?? (credentialOffer ? (0, import_oid4vci_common9.getIssuerFromCredentialOfferPayload)(credentialOffer.credential_offer) : metadata?.issuer); if (!issuer) { throw Error("Issuer required at this point"); } const issuerOpts = { issuer }; return await this.acquireAccessTokenUsingRequest({ accessTokenRequest: await this.createAccessTokenRequest({ credentialOffer, asOpts, codeVerifier, code, redirectUri, pin, credentialIssuer: issuer, metadata, additionalParams: opts.additionalParams, pinMetadata }), pinMetadata, metadata, asOpts, issuerOpts, createDPoPOpts }); } async acquireAccessTokenUsingRequest({ accessTokenRequest, pinMetadata, metadata, asOpts, issuerOpts, createDPoPOpts }) { this.validate(accessTokenRequest, pinMetadata); const requestTokenURL = _AccessTokenClient.determineTokenURL({ asOpts, issuerOpts, metadata: metadata ? metadata : issuerOpts?.fetchMetadata ? await MetadataClientV1_0_13.retrieveAllMetadata(issuerOpts.issuer, { errorOnNotFound: false }) : void 0 }); const useDpop = createDPoPOpts?.dPoPSigningAlgValuesSupported && createDPoPOpts.dPoPSigningAlgValuesSupported.length > 0; let dPoP = useDpop ? await (0, import_oid4vc_common3.createDPoP)((0, import_oid4vc_common3.getCreateDPoPOptions)(createDPoPOpts, requestTokenURL)) : void 0; let response = await this.sendAuthCode(requestTokenURL, accessTokenRequest, dPoP ? { headers: { dpop: dPoP } } : void 0); let nextDPoPNonce = createDPoPOpts?.jwtPayloadProps.nonce; const retryWithNonce = shouldRetryTokenRequestWithDPoPNonce(response); if (retryWithNonce.ok && createDPoPOpts) { createDPoPOpts.jwtPayloadProps.nonce = retryWithNonce.dpopNonce; dPoP = await (0, import_oid4vc_common3.createDPoP)((0, import_oid4vc_common3.getCreateDPoPOptions)(createDPoPOpts, requestTokenURL)); response = await this.sendAuthCode(requestTokenURL, accessTokenRequest, dPoP ? { headers: { dpop: dPoP } } : void 0); const successDPoPNonce = response.origResponse.headers.get("DPoP-Nonce"); nextDPoPNonce = successDPoPNonce ?? retryWithNonce.dpopNonce; } if (response.successBody && createDPoPOpts && response.successBody.token_type !== "DPoP") { throw new Error("Invalid token type returned. Expected DPoP. Received: " + response.successBody.token_type); } return { ...response, ...nextDPoPNonce && { params: { dpop: { dpopNonce: nextDPoPNonce } } } }; } async createAccessTokenRequest(opts) { const { asOpts, pin, codeVerifier, code, redirectUri } = opts; const credentialOfferRequest = opts.credentialOffer ? await (0, import_oid4vci_common9.toUniformCredentialOfferRequest)(opts.credentialOffer) : void 0; const request = { ...opts.additionalParams }; if (asOpts?.clientOpts?.clientId) { request.client_id = asOpts.clientOpts.clientId; } const credentialIssuer = opts.credentialIssuer ?? credentialOfferRequest?.credential_offer?.credential_issuer ?? opts.metadata?.issuer; await createJwtBearerClientAssertion(request, { ...opts, credentialIssuer }); if (!credentialOfferRequest || credentialOfferRequest.supportedFlows.includes(import_oid4vci_common9.AuthzFlowType.AUTHORIZATION_CODE_FLOW)) { request.grant_type = import_oid4vci_common9.GrantTypes.AUTHORIZATION_CODE; request.code = code; request.redirect_uri = redirectUri; if (codeVerifier) { request.code_verifier = codeVerifier; } return request; } if (credentialOfferRequest?.supportedFlows.includes(import_oid4vci_common9.AuthzFlowType.PRE_AUTHORIZED_CODE_FLOW)) { this.assertAlphanumericPin(opts.pinMetadata, pin); request.user_pin = pin; request.tx_code = pin; request.grant_type = import_oid4vci_common9.GrantTypes.PRE_AUTHORIZED_CODE; request[import_oid4vci_common9.PRE_AUTH_CODE_LITERAL] = credentialOfferRequest?.credential_offer.grants?.[import_oid4vci_common9.PRE_AUTH_GRANT_LITERAL]?.[import_oid4vci_common9.PRE_AUTH_CODE_LITERAL]; return request; } throw new Error("Credential offer request follows neither pre-authorized code nor authorization code flow requirements."); } assertPreAuthorizedGrantType(grantType) { if (import_oid4vci_common9.GrantTypes.PRE_AUTHORIZED_CODE !== grantType) { throw new Error("grant type must be 'urn:ietf:params:oauth:grant-type:pre-authorized_code'"); } } assertAuthorizationGrantType(grantType) { if (import_oid4vci_common9.GrantTypes.AUTHORIZATION_CODE !== grantType) { throw new Error("grant type must be 'authorization_code'"); } } getPinMetadata(requestPayload) { if (!requestPayload) { throw new Error(import_oid4vci_common9.TokenErrorResponse.invalid_request); } const issuer = (0, import_oid4vci_common9.getIssuerFromCredentialOfferPayload)(requestPayload); const grantDetails = requestPayload.grants?.[import_oid4vci_common9.PRE_AUTH_GRANT_LITERAL]; const isPinRequired = !!(grantDetails?.tx_code ?? false); LOG.warning(`Pin required for issuer ${issuer}: ${isPinRequired}`); return { txCode: grantDetails?.tx_code, isPinRequired }; } assertAlphanumericPin(pinMeta, pin) { if (pinMeta && pinMeta.isPinRequired) { let regex; if (pinMeta.txCode) { const { input_mode, length } = pinMeta.txCode; if (input_mode === "numeric") { regex = length ? new RegExp(`^\\d{1,${length}}$`) : /^\d+$/; } else if (input_mode === "text") { regex = length ? new RegExp(`^[a-zA-Z0-9]{1,${length}}$`) : /^[a-zA-Z0-9]+$/; } } regex = regex || /^[a-zA-Z0-9]+$|^[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+$/; if (!pin || !regex.test(pin)) { LOG.warning(`Pin is not valid. Expected format: ${pinMeta?.txCode?.input_mode || "alphanumeric"}, Length: up to ${pinMeta?.txCode?.length || "any number of"} characters`); throw new Error("A valid pin must be present according to the specified transaction code requirements."); } } else if (pin) { LOG.warning("Pin set, whilst not required"); throw new Error("Cannot set a pin when the pin is not required."); } } assertNonEmptyPreAuthorizedCode(accessTokenRequest) { if (!accessTokenRequest[import_oid4vci_common9.PRE_AUTH_CODE_LITERAL]) { LOG.warning(`No pre-authorized code present, whilst it is required`, accessTokenRequest); throw new Error("Pre-authorization must be proven by presenting the pre-authorized code. Code must be present."); } } assertNonEmptyCodeVerifier(accessTokenRequest) { if (!accessTokenRequest.code_verifier) { LOG.warning("No code_verifier present, whilst it is required", accessTokenRequest); throw new Error("Authorization flow requires the code_verifier to be present"); } } assertNonEmptyCode(accessTokenRequest) { if (!accessTokenRequest.code) { LOG.warning("No code present, whilst it is required"); throw new Error("Authorization flow requires the code to be present"); } } validate(accessTokenRequest, pinMeta) { if (accessTokenRequest.grant_type === import_oid4vci_common9.GrantTypes.PRE_AUTHORIZED_CODE) { this.assertPreAuthorizedGrantType(accessTokenRequest.grant_type); this.assertNonEmptyPreAuthorizedCode(accessTokenRequest); this.assertAlphanumericPin(pinMeta, accessTokenRequest.tx_code ?? accessTokenRequest.user_pin); } else if (accessTokenRequest.grant_type === import_oid4vci_common9.GrantTypes.AUTHORIZATION_CODE) { this.assertAuthorizationGrantType(accessTokenRequest.grant_type); this.assertNonEmptyCodeVerifier(accessTokenRequest); this.assertNonEmptyCode(accessTokenRequest); } else { this.throwNotSupportedFlow(); } } async sendAuthCode(requestTokenURL, accessTokenRequest, opts) { return await (0, import_oid4vci_common9.formPost)(requestTokenURL, (0, import_oid4vci_common9.convertJsonToURI)(accessTokenRequest, { mode: import_oid4vci_common9.JsonURIMode.X_FORM_WWW_URLENCODED }), { customHeaders: opts?.headers ? opts.headers : void 0 }); } static determineTokenURL({ asOpts, issuerOpts, metadata }) { if (!asOpts && !metadata?.token_endpoint && !issuerOpts) { throw new Error("Cannot determine token URL if no issuer, metadata and no Authorization Server values are present"); } let url; if (asOpts && asOpts.as) { url = this.creatTokenURLFromURL(asOpts.as, asOpts?.allowInsecureEndpoints, asOpts.tokenEndpoint); } else if (metadata?.token_endpoint) { url = metadata.token_endpoint; } else { if (!issuerOpts?.issuer) { throw Error("Either authorization server options, a token endpoint or issuer options are required at this point"); } url = this.creatTokenURLFromURL(issuerOpts.issuer, asOpts?.allowInsecureEndpoints, issuerOpts.tokenEndpoint); } if (!url || !import_ssi_types4.ObjectUtils.isString(url)) { throw new Error("No authorization server token URL present. Cannot acquire access token"); } LOG.debug(`Token endpoint determined to be ${url}`); return url; } static creatTokenURLFromURL(url, allowInsecureEndpoints, tokenEndpoint) { if (allowInsecureEndpoints !== true && url.startsWith("http:")) { throw Error(`Unprotected token endpoints are not allowed ${url}. Use the 'allowInsecureEndpoints' param if you really need this for dev/testing!`); } const hostname = url.replace(/https?:\/\//, "").replace(/\/$/, ""); const endpoint = tokenEndpoint ? tokenEndpoint.startsWith("/") ? tokenEndpoint : tokenEndpoint.substring(1) : "/token"; const scheme = url.split("://")[0]; return `${scheme ? scheme + "://" : "https://"}${hostname}${endpoint}`; } throwNotSupportedFlow() { LOG.warning(`Only pre-authorized or authorization code flows supported.`); throw new Error("Only pre-authorized-code or authorization code flows are supported"); } }; // lib/AccessTokenClientV1_0_11.ts var import_oid4vc_common4 = require("@sphereon/oid4vc-common"); var import_oid4vci_common10 = require("@sphereon/oid4vci-common"); var import_ssi_types5 = require("@sphereon/ssi-types"); var logger3 = import_ssi_types5.Loggers.DEFAULT.get("sphereon:oid4vci:token"); var AccessTokenClientV1_0_11 = class _AccessTokenClientV1_0_11 { static { __name(this, "AccessTokenClientV1_0_11"); } async acquireAccessToken(opts) { const { asOpts, pin, codeVerifier, code, redirectUri, metadata, createDPoPOpts } = opts; const credentialOffer = opts.credentialOffer ? await (0, import_oid4vci_common10.assertedUniformCredentialOffer)(opts.credentialOffer) : void 0; const isPinRequired = credentialOffer && this.isPinRequiredValue(credentialOffer.credential_offer); const issuer = opts.credentialIssuer ?? (credentialOffer ? (0, import_oid4vci_common10.getIssuerFromCredentialOfferPayload)(credentialOffer.credential_offer) : metadata?.issuer); if (!issuer) { throw Error("Issuer required at this point"); } const issuerOpts = { issuer }; return await this.acquireAccessTokenUsingRequest({ accessTokenRequest: await this.createAccessTokenRequest({ credentialOffer, asOpts, codeVerifier, code, redirectUri, pin, credentialIssuer: issuer, metadata, additionalParams: opts.additionalParams, pinMetadata: opts.pinMetadata }), isPinRequired, metadata, asOpts, issuerOpts, createDPoPOpts }); } async acquireAccessTokenUsingRequest({ accessTokenRequest, isPinRequired, metadata, asOpts, createDPoPOpts, issuerOpts }) { this.validate(accessTokenRequest, isPinRequired); const requestTokenURL = _AccessTokenClientV1_0_11.determineTokenURL({ asOpts, issuerOpts, metadata: metadata ? metadata : issuerOpts?.fetchMetadata ? await MetadataClientV1_0_13.retrieveAllMetadata(issuerOpts.issuer, { errorOnNotFound: false }) : void 0 }); const useDpop = createDPoPOpts?.dPoPSigningAlgValuesSupported && createDPoPOpts.dPoPSigningAlgValuesSupported.length > 0; let dPoP = useDpop ? await (0, import_oid4vc_common4.createDPoP)((0, import_oid4vc_common4.getCreateDPoPOptions)(createDPoPOpts, requestTokenURL)) : void 0; let response = await this.sendAuthCode(requestTokenURL, accessTokenRequest, dPoP ? { headers: { dpop: dPoP } } : void 0); let nextDPoPNonce = createDPoPOpts?.jwtPayloadProps.nonce; const retryWithNonce = shouldRetryTokenRequestWithDPoPNonce(response); if (retryWithNonce.ok && createDPoPOpts) { createDPoPOpts.jwtPayloadProps.nonce = retryWithNonce.dpopNonce; dPoP = await (0, import_oid4vc_common4.createDPoP)((0, import_oid4vc_common4.getCreateDPoPOptions)(createDPoPOpts, requestTokenURL)); response = await this.sendAuthCode(requestTokenURL, accessTokenRequest, dPoP ? { headers: { dpop: dPoP } } : void 0); const successDPoPNonce = response.origResponse.headers.get("DPoP-Nonce"); nextDPoPNonce = successDPoPNonce ?? retryWithNonce.dpopNonce; } if (response.successBody && createDPoPOpts && response.successBody.token_type !== "DPoP") { throw new Error("Invalid token type returned. Expected DPoP. Received: " + response.successBody.token_type); } return { ...response, ...nextDPoPNonce && { params: { dpop: { dpopNonce: nextDPoPNonce } } } }; } async createAccessTokenRequest(opts) { const { asOpts, pin, codeVerifier, code, redirectUri } = opts; const credentialOfferRequest = opts.credentialOffer ? await (0, import_oid4vci_common10.toUniformCredentialOfferRequest)(opts.credentialOffer) : void 0; const request = { ...opts.additionalParams }; const credentialIssuer = opts.credentialIssuer ?? credentialOfferRequest?.credential_offer?.credential_issuer ?? opts.metadata?.issuer; if (asOpts?.clientOpts?.clientId) { request.client_id = asOpts.clientOpts.clientId; } await createJwtBearerClientAssertion(request, { ...opts, version: import_oid4vci_common10.OpenId4VCIVersion.VER_1_0_11, credentialIssuer }); if (!credentialOfferRequest || credentialOfferRequest.supportedFlows.includes(import_oid4vci_common10.AuthzFlowType.AUTHORIZATION_CODE_FLOW)) { request.grant_type = import_oid4vci_common10.GrantTypes.AUTHORIZATION_CODE; request.code = code; request.redirect_uri = redirectUri; if (codeVerifier) { request.code_verifier = codeVerifier; } return request; } if (credentialOfferRequest?.supportedFlows.includes(import_oid4vci_common10.AuthzFlowType.PRE_AUTHORIZED_CODE_FLOW)) { this.assertNumericPin(this.isPinRequiredValue(credentialOfferRequest.credential_offer), pin); request.user_pin = pin; request.grant_type = import_oid4vci_common10.GrantTypes.PRE_AUTHORIZED_CODE; request[import_oid4vci_common10.PRE_AUTH_CODE_LITERAL] = credentialOfferRequest?.credential_offer.grants?.[import_oid4vci_common10.PRE_AUTH_GRANT_LITERAL]?.[import_oid4vci_common10.PRE_AUTH_CODE_LITERAL]; return request; } throw new Error("Credential offer request does not follow neither pre-authorized code nor authorization code flow requirements."); } assertPreAuthorizedGrantType(grantType) { if (import_oid4vci_common10.GrantTypes.PRE_AUTHORIZED_CODE !== grantType) { throw new Error("grant type must be PRE_AUTH_GRANT_LITERAL"); } } assertAuthorizationGrantType(grantType) { if (import_oid4vci_common10.GrantTypes.AUTHORIZATION_CODE !== grantType) { throw new Error("grant type must be 'authorization_code'"); } } isPinRequiredValue(requestPayload) { let isPinRequired = false; if (!requestPayload) { throw new Error(import_oid4vci_common10.TokenErrorResponse.invalid_request); } const issuer = (0, import_oid4vci_common10.getIssuerFromCredentialOfferPayload)(requestPayload); if (requestPayload.grants?.[import_oid4vci_common10.PRE_AUTH_GRANT_LITERAL]) { isPinRequired = requestPayload.grants[import_oid4vci_common10.PRE_AUTH_GRANT_LITERAL]?.user_pin_required ?? false; } logger3.debug(`Pin required for issuer ${issuer}: ${isPinRequired}`); return isPinRequired; } assertNumericPin(isPinRequired, pin) { if (isPinRequired) { if (!pin || !/^\d{1,8}$/.test(pin)) { logger3.debug(`Pin is not 1 to 8 digits long`); throw new Error("A valid pin consisting of maximal 8 numeric characters must be present."); } } else if (pin) { logger3.debug(`Pin set, whilst not required`); throw new Error("Cannot set a pin, when the pin is not required."); } } assertNonEmptyPreAuthorizedCode(accessTokenRequest) { if (!accessTokenRequest[import_oid4vci_common10.PRE_AUTH_CODE_LITERAL]) { logger3.debug(`No pre-authorized code present, whilst it is required`); throw new Error("Pre-authorization must be proven by presenting the pre-authorized code. Code must be present."); } } assertNonEmptyCodeVerifier(accessTokenRequest) { if (!accessTokenRequest.code_verifier) { logger3.debug("No code_verifier present, whilst it is required"); throw new Error("Authorization flow requires the code_verifier to be present"); } } assertNonEmptyCode(accessTokenRequest) { if (!accessTokenRequest.code) { logger3.debug("No code present, whilst it is required"); throw new Error("Authorization flow requires the code to be present"); } } validate(accessTokenRequest, isPinRequired) { if (accessTokenRequest.grant_type === import_oid4vci_common10.GrantTypes.PRE_AUTHORIZED_CODE) { this.assertPreAuthorizedGrantType(accessTokenRequest.grant_type); this.assertNonEmptyPreAuthorizedCode(accessTokenRequest); this.assertNumericPin(isPinRequired, accessTokenRequest.user_pin); } else if (accessTokenRequest.grant_type === import_oid4vci_common10.GrantTypes.AUTHORIZATION_CODE) { this.assertAuthorizationGrantType(accessTokenRequest.grant_type); this.assertNonEmptyCodeVerifier(accessTokenRequest); this.assertNonEmptyCode(accessTokenRequest); } else { this.throwNotSupportedFlow(); } } async sendAuthCode(requestTokenURL, accessTokenRequest, opts) { return await (0, import_oid4vci_common10.formPost)(requestTokenURL, (0, import_oid4vci_common10.convertJsonToURI)(accessTokenRequest, { mode: import_oid4vci_common10.JsonURIMode.X_FORM_WWW_URLENCODED }), { customHeaders: opts?.headers ? opts.headers : void 0 }); } static determineTokenURL({ asOpts, issuerOpts, metadata }) { if (!asOpts && !metadata?.token_endpoint && !issuerOpts) { throw new Error("Cannot determine token URL if no issuer, metadata and no Authorization Server values are present"); } let url; if (asOpts && asOpts.as) { url = this.creatTokenURLFromURL(asOpts.as, asOpts?.allowInsecureEndpoints, asOpts.tokenEndpoint); } else if (metadata?.token_endpoint) { url = metadata.token_endpoint; } else { if (!issuerOpts?.issuer) { throw Error("Either authorization server options, a token endpoint or issuer options are required at this point"); } url = this.creatTokenURLFromURL(issuerOpts.issuer, asOpts?.allowInsecureEndpoints, issuerOpts.tokenEndpoint); } if (!url || !import_ssi_types5.ObjectUtils.isString(url)) { throw new Error("No authorization server token URL present. Cannot acquire access token"); } logger3.debug(`Token endpoint determined to be ${url}`); return url; } static creatTokenURLFromURL(url, allowInsecureEndpoints, tokenEndpoint) { if (allowInsecureEndpoints !== true && url.startsWith("http:")) { throw Error(`Unprotected token endpoints are not allowed ${url}. Use the 'allowInsecureEndpoints' param if you really need this for dev/testing!`); } const hostname = url.replace(/https?:\/\//, "").replace(/\/$/, ""); const endpoint = tokenEndpoint ? tokenEndpoint.startsWith("/") ? tokenEndpoint : tokenEndpoint.substring(1) : "/token"; const scheme = url.split("://")[0]; return `${scheme ? scheme + "://" : "https://"}${hostname}${endpoint}`; } throwNotSupportedFlow() { logger3.debug(`Only pre-authorized or authorization code flows supported.`); throw new Error("Only pre-authorized-code or authorization code flows are supported"); } }; // lib/AuthorizationCodeClient.ts var import_oid4vci_common13 = require("@sphereon/oid4vci-common"); var import_ssi_types8 = require("@sphereon/ssi-types"); // lib/MetadataClient.ts var import_oid4vci_common12 = require("@sphereon/oid4vci-common"); var import_ssi_types7 = require("@sphereon/ssi-types"); // lib/MetadataClientV1_0_11.ts var import_oid4vci_common11 = require("@sphereon/oid4vci-common"); var import_ssi_types6 = require("@sphereon/ssi-types"); var logger4 = import_ssi_types6.Loggers.DEFAULT.get("sphereon:oid4vci:metadata"); var MetadataClientV1_0_11 = class _MetadataClientV1_0_11 { static { __name(this, "MetadataClientV1_0_11"); } /** * Retrieve metadata using the Initiation obtained from a previous step * * @param credentialOffer */ static async retrieveAllMetadataFromCredentialOffer(credentialOffer) { return _MetadataClientV1_0_11.retrieveAllMetadataFromCredentialOfferRequest(credentialOffer.credential_offer); } /** * Retrieve the metada using the initiation request obtained from a previous step * @param request */ static async retrieveAllMetadataFromCredentialOfferRequest(request) { const issuer = (0, import_oid4vci_common11.getIssuerFromCredentialOfferPayload)(request); if (issuer) { return _MetadataClientV1_0_11.retrieveAllMetadata(issuer); } throw new Error("can't retrieve metadata from CredentialOfferRequest. No issuer field is present"); } /** * Retrieve all metadata from an issuer * @param issuer The issuer URL * @param opts */ static async retrieveAllMetadata(issuer, opts) { let token_endpoint; let credential_endpoint; let deferred_credential_endpoint; let authorization_endpoint; let authorization_challenge_endpoint; let authorizationServerType = "OID4VCI"; let authorization_server = issuer; const oid4vciResponse = await _MetadataClientV1_0_11.retrieveOpenID4VCIServerMetadata(issuer, { errorOnNotFound: false }); let credentialIssuerMetadata = oid4vciResponse?.successBody; if (credentialIssuerMetadata) { logger4.debug(`Issuer ${issuer} OID4VCI well-known server metadata\r ${JSON.stringify(credentialIssuerMetadata)}`); credential_endpoint = credentialIssuerMetadata.credential_endpoint; deferred_credential_endpoint = credentialIssuerMetadata.deferred_credential_endpoint; if (credentialIssuerMetadata.token_endpoint) { token_endpoint = credentialIssuerMetadata.token_endpoint; } authorization_challenge_endpoint = credentialIssuerMetadata.authorization_challenge_endpoint; if (credentialIssuerMetadata.authorization_server) { authorization_server = credentialIssuerMetadata.authorization_server; } if (credentialIssuerMetadata.authorization_endpoint) { authorization_endpoint = credentialIssuerMetadata.authorization_endpoint; } } let response = await retrieveWellknown(authorization_server, import_oid4vci_common11.WellKnownEndpoints.OPENID_CONFIGURATION, { errorOnNotFound: false }); let authMetadata = response.successBody; if (authMetadata) { logger4.debug(`Issuer ${issuer} has OpenID Connect Server metadata in well-known location`); authorizationServerType = "OIDC"; } else { response = await retrieveWellknown(authorization_server, import_oid4vci_common11.WellKnownEndpoints.OAUTH_AS, { errorOnNotFound: false }); authMetadata = response.successBody; } if (!authMetadata) { if (issuer !== authorization_server) { throw Error(`Issuer ${issuer} provided a separate authorization server ${authorization_server}, but that server did not provide metadata`); } } else { if (!authorizationServerType) { authorizationServerType = "OAuth 2.0"; } logger4.debug(`Issuer ${issuer} has ${authorizationServerType} Server metadata in well-known location`); if (!authMetadata.authorization_endpoint) { console.warn(`Issuer ${issuer} of type ${authorizationServerType} has no authorization_endpoint! Will use ${authoriza