UNPKG

@sphereon/oid4vci-client

Version:

OpenID for Verifiable Credential Issuance (OpenID4VCI) client

1,151 lines (1,134 loc) • 136 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; 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 __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // lib/index.ts var index_exports = {}; __export(index_exports, { AccessTokenClient: () => AccessTokenClient, CredentialOfferClient: () => CredentialOfferClient, CredentialOfferClientV1_0_15: () => CredentialOfferClientV1_0_15, CredentialRequestClient: () => CredentialRequestClient, CredentialRequestClientBuilder: () => CredentialRequestClientBuilder, CredentialRequestClientBuilderV1_0_15: () => CredentialRequestClientBuilderV1_0_15, LOG: () => LOG2, MetadataClient: () => MetadataClient, MetadataClientV1_0_15: () => MetadataClientV1_0_15, OpenID4VCIClient: () => OpenID4VCIClient, OpenID4VCIClientV1_0_15: () => OpenID4VCIClientV1_0_15, ProofOfPossessionBuilder: () => ProofOfPossessionBuilder, acquireAuthorizationChallengeAuthCode: () => acquireAuthorizationChallengeAuthCode, acquireAuthorizationChallengeAuthCodeUsingRequest: () => acquireAuthorizationChallengeAuthCodeUsingRequest, buildProof: () => buildProof, constructBaseResponse: () => constructBaseResponse, createAuthorizationChallengeRequest: () => createAuthorizationChallengeRequest, createAuthorizationRequestUrl: () => createAuthorizationRequestUrl, createJwtBearerClientAssertion: () => createJwtBearerClientAssertion, createSignedAuthRequestWhenNeeded: () => createSignedAuthRequestWhenNeeded, generateMissingPKCEOpts: () => generateMissingPKCEOpts, handleCredentialOfferUri: () => handleCredentialOfferUri, isUriEncoded: () => isUriEncoded, retrieveWellknown: () => retrieveWellknown, sendAuthorizationChallengeRequest: () => sendAuthorizationChallengeRequest, sendNotification: () => sendNotification }); module.exports = __toCommonJS(index_exports); var import_oid4vci_common20 = 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/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; // private readonly version: OpenId4VCIVersion 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; if (jwt) { this.withJwt(jwt); } else { this.withTyp(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") { 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.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.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_15, 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 = __toESM(require("cross-fetch"), 1); 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.default)(decodedUri); if (!(response && response.status >= 200 && response.status < 400)) { return Promise.reject(Error(`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(Error("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/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/MetadataClientV1_0_15.ts var import_oid4vci_common8 = require("@sphereon/oid4vci-common"); var import_ssi_types3 = require("@sphereon/ssi-types"); var logger2 = import_ssi_types3.Loggers.DEFAULT.get("sphereon:oid4vci:metadata"); var MetadataClientV1_0_15 = class _MetadataClientV1_0_15 { static { __name(this, "MetadataClientV1_0_15"); } /** * Retrieve metadata using the Initiation obtained from a previous step * * @param credentialOffer */ static async retrieveAllMetadataFromCredentialOffer(credentialOffer) { return _MetadataClientV1_0_15.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_15.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 nonce_endpoint; let deferred_credential_endpoint; let notification_endpoint; let authorization_endpoint; let authorization_challenge_endpoint; let authorizationServerType = "OID4VCI"; let authorization_servers = [ issuer ]; const oid4vciResponse = await _MetadataClientV1_0_15.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; nonce_endpoint = credentialIssuerMetadata.nonce_endpoint; deferred_credential_endpoint = credentialIssuerMetadata.deferred_credential_endpoint; notification_endpoint = credentialIssuerMetadata.notification_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 { 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 (authMetadata.notification_endpoint) { if (notification_endpoint && authMetadata.notification_endpoint !== notification_endpoint) { logger2.debug(`Credential issuer has a different notification_endpoint (${notification_endpoint}) from the Authorization Server (${authMetadata.notification_endpoint}). Will use the issuer value`); } else { notification_endpoint = authMetadata.notification_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; } const ci = credentialIssuerMetadata ?? {}; const ciAuthorizationServers = Array.isArray(ci.authorization_servers) && ci.authorization_servers.length > 0 ? ci.authorization_servers : authorization_servers; const v15CredentialIssuerMetadata = { credential_issuer: ci.credential_issuer ?? issuer, credential_endpoint, authorization_servers: ciAuthorizationServers, credential_configurations_supported: ci.credential_configurations_supported ?? {}, display: ci.display ?? [], ...nonce_endpoint && { nonce_endpoint }, ...deferred_credential_endpoint && { deferred_credential_endpoint }, ...notification_endpoint && { notification_endpoint } }; logger2.debug(`Issuer ${issuer} token endpoint ${token_endpoint}, credential endpoint ${credential_endpoint}`); return { issuer, token_endpoint, credential_endpoint, authorization_challenge_endpoint, notification_endpoint, authorizationServerType, credentialIssuerMetadata: v15CredentialIssuerMetadata, 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/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_15.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/AuthorizationCodeClient.ts var import_oid4vci_common11 = require("@sphereon/oid4vci-common"); var import_ssi_types6 = require("@sphereon/ssi-types"); // lib/MetadataClient.ts 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:metadata"); var MetadataClient = class _MetadataClient { static { __name(this, "MetadataClient"); } /** * Retrieve metadata using the Initiation obtained from a previous step * * @param credentialOffer */ static async retrieveAllMetadataFromCredentialOffer(credentialOffer) { const openId4VCIVersion = (0, import_oid4vci_common10.determineSpecVersionFromOffer)(credentialOffer.credential_offer); if (openId4VCIVersion >= import_oid4vci_common10.OpenId4VCIVersion.VER_1_0_15) { return await MetadataClientV1_0_15.retrieveAllMetadataFromCredentialOffer(credentialOffer); } return Promise.reject(Error(`OpenId4VCIVersion ${openId4VCIVersion} is not supported in retrieveAllMetadataFromCredentialOffer`)); } /** * Retrieve the metada using the initiation request obtained from a previous step * @param request */ static async retrieveAllMetadataFromCredentialOfferRequest(request) { const issuer = (0, import_oid4vci_common10.getIssuerFromCredentialOfferPayload)(request); if (issuer) { const openId4VCIVersion = (0, import_oid4vci_common10.determineSpecVersionFromOffer)(request); if (openId4VCIVersion >= import_oid4vci_common10.OpenId4VCIVersion.VER_1_0_15) { return MetadataClientV1_0_15.retrieveAllMetadataFromCredentialOfferRequest(request); } else { return Promise.reject(Error(`OpenId4VCIVersion ${openId4VCIVersion} is not supported in retrieveAllMetadataFromCredentialOfferRequest`)); } } 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 ]; let authorization_server = void 0; const oid4vciResponse = await _MetadataClient.retrieveOpenID4VCIServerMetadata(issuer, { errorOnNotFound: false }); let credentialIssuerMetadata = oid4vciResponse?.successBody; if (credentialIssuerMetadata) { logger3.debug(`Issuer ${issuer} OID4VCI well-known server metadata\r ${JSON.stringify(credentialIssuerMetadata)}`); credential_endpoint = credentialIssuerMetadata.credential_endpoint; deferred_credential_endpoint = credentialIssuerMetadata.deferred_credential_endpoint ? credentialIssuerMetadata.deferred_credential_endpoint : void 0; 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; } else if (credentialIssuerMetadata.authorization_server) { authorization_server = credentialIssuerMetadata.authorization_server; authorization_servers = [ authorization_server ]; } } else { throw new Error(`Issuer ${issuer} does not expose /.well-known/openid-credential-issuer`); } let response = await retrieveWellknown(authorization_servers[0], import_oid4vci_common10.WellKnownEndpoints.OPENID_CONFIGURATION, { errorOnNotFound: false }); let authMetadata = response.successBody; if (authMetadata) { logger3.debug(`Issuer ${issuer} has OpenID Connect Server metadata in well-known location`); authorizationServerType = "OIDC"; } else { response = await retrieveWellknown(authorization_servers[0], import_oid4vci_common10.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"; } logger3.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) { logger3.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) { logger3.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) { logger3.debug(`Issuer ${issuer} does not expose authorization_endpoint, so only pre-auth will be supported`); } if (!token_endpoint) { logger3.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) { logger3.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) { return Promise.reject(Error(`No /.well-known/openid-credential-issuer at ${issuer}.`)); } logger3.debug(`Issuer ${issuer} token endpoint ${token_endpoint}, credential endpoint ${credential_endpoint}`); return { issuer, token_endpoint, credential_endpoint, deferred_credential_endpoint, nonce_endpoint: credentialIssuerMetadata.nonce_endpoint, authorization_servers: authorization_server ? [ authorization_server ] : authorization_servers ?? [ issuer ], 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_common10.WellKnownEndpoints.OPENID4VCI_ISSUER, { errorOnNotFound: opts?.errorOnNotFound === void 0 ? true : opts.errorOnNotFound }); } }; // lib/AuthorizationCodeClient.ts var logger4 = import_ssi_types6.Loggers.DEFAULT.get("sphereon:oid4vci"); async function createSignedAuthRequestWhenNeeded(requestObject, opts) { if (opts.requestObjectMode === import_oid4vci_common11.CreateRequestObjectMode.REQUEST_URI) { throw Error(`Request Object Mode ${opts.requestObjectMode} is not supported yet`); } else if (opts.requestObjectMode === import_oid4vci_common11.CreateRequestObjectMode.REQUEST_OBJECT) { if (typeof opts.signCallbacks?.signCallback !== "function") { throw Error(`No request object sign callback found, whilst request object mode was set to ${opts.requestObjectMode}`); } else if (!opts.kid) { throw Error(`No kid found, whilst request object mode was set to ${opts.requestObjectMode}`); } let client_metadata; if (opts.clientMetadata || opts.jwksUri) { client_metadata = opts.clientMetadata ?? {}; if (opts.jwksUri) { client_metadata["jwks_uri"] = opts.jwksUri; } } let authorization_details = requestObject["authorization_details"]; if (typeof authorization_details === "string") { authorization_details = JSON.parse(requestObject.authorization_details); } if (!requestObject.aud && opts.aud) { requestObject.aud = opts.aud; } const iss = requestObject.iss ?? opts.iss ?? requestObject.client_id; const jwt = { header: { alg: "ES256", kid: opts.kid, typ: "JWT" }, payload: { ...requestObject, iss, authorization_details, ...client_metadata && { client_metadata } } }; const pop = await ProofOfPossessionBuilder.fromJwt({ jwt, callbacks: opts.signCallbacks, version: import_oid4vci_common11.OpenId4VCIVersion.VER_1_0_15, mode: "JWT" }).build(); requestObject["request"] = pop.jwt; } } __name(createSignedAuthRequestWhenNeeded, "createSignedAuthRequestWhenNeeded"); function filterSupportedCredentials(credentialOffer, credentialsSupported) { if (!credentialOffer.credential_configuration_ids || !credentialsSupported) { return []; } return Object.entries(credentialsSupported).filter((entry) => credentialOffer.credential_configuration_ids?.includes(entry[0])).map((entry) => { return { ...entry[1], configuration_id: entry[0] }; }); } __name(filterSupportedCredentials, "filterSupportedCredentials"); var createAuthorizationRequestUrl = /* @__PURE__ */ __name(async ({ pkce, endpointMetadata, authorizationRequest, credentialOffer, credentialConfigurationSupported, clientId, version }) => { function removeDisplayAndValueTypes(obj) { if (Array.isArray(obj)) { return obj.map((item) => removeDisplayAndValueTypes(item)); } if (typeof obj !== "object" || obj === null) { return obj; } const newObj = { ...obj }; for (const prop in newObj) { if ([ "display", "value_type" ].includes(prop)) { delete newObj[prop]; } else if (typeof newObj[prop] === "object" && newObj[prop] !== null) { newObj[prop] = removeDisplayAndValueTypes(newObj[prop]); } } return newObj; } __name(removeDisplayAndValueTypes, "removeDisplayAndValueTypes"); const { redirectUri, requestObjectOpts = { requestObjectMode: import_oid4vci_common11.CreateRequestObjectMode.NONE } } = authorizationRequest; const client_id = clientId ?? authorizationRequest.clientId; const authorizationMetadata = endpointMetadata.authorizationServerMetadata ?? endpointMetadata.credentialIssuerMetadata; let { authorizationDetails } = authorizationRequest; const parMode = authorizationMetadata?.require_pushed_authorization_requests ? import_oid4vci_common11.PARMode.REQUIRE : authorizationRequest.parMode ?? (client_id ? import_oid4vci_common11.PARMode.AUTO : import_oid4vci_common11.PARMode.NEVER); if (!authorizationRequest.scope && !authorizationDetails) { if (!credentialOffer) { throw Error("Please provide a scope or authorization_details if no credential offer is present"); } if ("credentials" in credentialOffer.credential_offer) { throw new Error("CredentialOffer format is wrong."); } const ver = version ?? (0, import_oid4vci_common11.dete