@sphereon/oid4vci-client
Version:
OpenID for Verifiable Credential Issuance (OpenID4VCI) client
1,155 lines (1,136 loc) • 192 kB
JavaScript
var __defProp = Object.defineProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
// lib/index.ts
import { VCI_LOGGERS as VCI_LOGGERS2 } from "@sphereon/oid4vci-common";
// lib/AccessTokenClient.ts
import { createDPoP, getCreateDPoPOptions } from "@sphereon/oid4vc-common";
import { assertedUniformCredentialOffer, AuthzFlowType, convertJsonToURI, formPost, getIssuerFromCredentialOfferPayload as getIssuerFromCredentialOfferPayload2, GrantTypes, JsonURIMode, PRE_AUTH_CODE_LITERAL as PRE_AUTH_CODE_LITERAL2, PRE_AUTH_GRANT_LITERAL as PRE_AUTH_GRANT_LITERAL2, TokenErrorResponse, toUniformCredentialOfferRequest } from "@sphereon/oid4vci-common";
import { ObjectUtils } from "@sphereon/ssi-types";
// lib/MetadataClientV1_0_13.ts
import { getIssuerFromCredentialOfferPayload, WellKnownEndpoints } from "@sphereon/oid4vci-common";
import { Loggers as Loggers2 } from "@sphereon/ssi-types";
// lib/functions/AuthorizationUtil.ts
import { assertValidCodeVerifier, CodeChallengeMethod, createCodeChallenge, generateCodeVerifier } from "@sphereon/oid4vci-common";
var generateMissingPKCEOpts = /* @__PURE__ */ __name((pkce) => {
if (pkce.disabled) {
return pkce;
}
if (!pkce.codeChallengeMethod) {
pkce.codeChallengeMethod = CodeChallengeMethod.S256;
}
if (!pkce.codeVerifier) {
pkce.codeVerifier = generateCodeVerifier();
}
assertValidCodeVerifier(pkce.codeVerifier);
if (!pkce.codeChallenge) {
pkce.codeChallenge = createCodeChallenge(pkce.codeVerifier, pkce.codeChallengeMethod);
}
return pkce;
}, "generateMissingPKCEOpts");
// lib/functions/notifications.ts
import { post } from "@sphereon/oid4vci-common";
// lib/types/index.ts
import { VCI_LOGGERS } from "@sphereon/oid4vci-common";
import { LogMethod } from "@sphereon/ssi-types";
var LOG = VCI_LOGGERS.options("sphereon:oid4vci:client", {
methods: [
LogMethod.EVENT,
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 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
import { getJson } from "@sphereon/oid4vci-common";
import { Loggers } from "@sphereon/ssi-types";
var logger = Loggers.DEFAULT.get("sphereon:openid4vci:openid-utils");
var retrieveWellknown = /* @__PURE__ */ __name(async (host, endpointType, opts) => {
const result = await 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
import { uuidv4 } from "@sphereon/oid4vc-common";
import { OpenId4VCIVersion as OpenId4VCIVersion2 } from "@sphereon/oid4vci-common";
// lib/ProofOfPossessionBuilder.ts
import { createProofOfPossession, NO_JWT_PROVIDED, OpenId4VCIVersion, PROOF_CANT_BE_CONSTRUCTED } from "@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 < 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 >= 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(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 >= 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 createProofOfPossession(this.mode, this.callbacks, {
typ: this.typ ?? (this.version < 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(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: 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 ?? OpenId4VCIVersion2.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
import { decodeJsonProperties, getClientIdFromCredentialOfferPayload, getURIComponentsAsArray, PRE_AUTH_CODE_LITERAL, PRE_AUTH_GRANT_LITERAL } from "@sphereon/oid4vci-common";
import { fetch } from "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 = getURIComponentsAsArray(uri);
const credentialOfferUri = decodeURIComponent(uriObj["credential_offer_uri"]);
const decodedUri = isUriEncoded(credentialOfferUri) ? decodeURIComponent(credentialOfferUri) : credentialOfferUri;
const response = await 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: decodeJsonProperties(await response.json())
};
}
__name(handleCredentialOfferUri, "handleCredentialOfferUri");
function constructBaseResponse(request, scheme, baseUrl) {
const clientId = 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?.[PRE_AUTH_GRANT_LITERAL]?.[PRE_AUTH_CODE_LITERAL] && {
preAuthorizedCode: grants[PRE_AUTH_GRANT_LITERAL][PRE_AUTH_CODE_LITERAL]
},
...request.credential_offer?.grants?.[PRE_AUTH_GRANT_LITERAL]?.tx_code && {
txCode: request.credential_offer.grants[PRE_AUTH_GRANT_LITERAL].tx_code
}
};
}
__name(constructBaseResponse, "constructBaseResponse");
// lib/MetadataClientV1_0_13.ts
var logger2 = Loggers2.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 = 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], 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], 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, WellKnownEndpoints.OPENID4VCI_ISSUER, {
errorOnNotFound: opts?.errorOnNotFound === void 0 ? true : opts.errorOnNotFound
});
}
};
// lib/functions/dpopUtil.ts
import { dpopTokenRequestNonceError } from "@sphereon/oid4vc-common";
function shouldRetryTokenRequestWithDPoPNonce(response) {
if (!response.errorBody || response.errorBody.error !== 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(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 assertedUniformCredentialOffer(opts.credentialOffer) : void 0;
const pinMetadata = credentialOffer && this.getPinMetadata(credentialOffer.credential_offer);
const issuer = opts.credentialIssuer ?? (credentialOffer ? getIssuerFromCredentialOfferPayload2(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 createDPoP(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 createDPoP(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 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(AuthzFlowType.AUTHORIZATION_CODE_FLOW)) {
request.grant_type = GrantTypes.AUTHORIZATION_CODE;
request.code = code;
request.redirect_uri = redirectUri;
if (codeVerifier) {
request.code_verifier = codeVerifier;
}
return request;
}
if (credentialOfferRequest?.supportedFlows.includes(AuthzFlowType.PRE_AUTHORIZED_CODE_FLOW)) {
this.assertAlphanumericPin(opts.pinMetadata, pin);
request.user_pin = pin;
request.tx_code = pin;
request.grant_type = GrantTypes.PRE_AUTHORIZED_CODE;
request[PRE_AUTH_CODE_LITERAL2] = credentialOfferRequest?.credential_offer.grants?.[PRE_AUTH_GRANT_LITERAL2]?.[PRE_AUTH_CODE_LITERAL2];
return request;
}
throw new Error("Credential offer request follows neither pre-authorized code nor authorization code flow requirements.");
}
assertPreAuthorizedGrantType(grantType) {
if (GrantTypes.PRE_AUTHORIZED_CODE !== grantType) {
throw new Error("grant type must be 'urn:ietf:params:oauth:grant-type:pre-authorized_code'");
}
}
assertAuthorizationGrantType(grantType) {
if (GrantTypes.AUTHORIZATION_CODE !== grantType) {
throw new Error("grant type must be 'authorization_code'");
}
}
getPinMetadata(requestPayload) {
if (!requestPayload) {
throw new Error(TokenErrorResponse.invalid_request);
}
const issuer = getIssuerFromCredentialOfferPayload2(requestPayload);
const grantDetails = requestPayload.grants?.[PRE_AUTH_GRANT_LITERAL2];
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[PRE_AUTH_CODE_LITERAL2]) {
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 === 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 === GrantTypes.AUTHORIZATION_CODE) {
this.assertAuthorizationGrantType(accessTokenRequest.grant_type);
this.assertNonEmptyCodeVerifier(accessTokenRequest);
this.assertNonEmptyCode(accessTokenRequest);
} else {
this.throwNotSupportedFlow();
}
}
async sendAuthCode(requestTokenURL, accessTokenRequest, opts) {
return await formPost(requestTokenURL, convertJsonToURI(accessTokenRequest, {
mode: 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 || !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
import { createDPoP as createDPoP2, getCreateDPoPOptions as getCreateDPoPOptions2 } from "@sphereon/oid4vc-common";
import { assertedUniformCredentialOffer as assertedUniformCredentialOffer2, AuthzFlowType as AuthzFlowType2, convertJsonToURI as convertJsonToURI2, formPost as formPost2, getIssuerFromCredentialOfferPayload as getIssuerFromCredentialOfferPayload3, GrantTypes as GrantTypes2, JsonURIMode as JsonURIMode2, OpenId4VCIVersion as OpenId4VCIVersion3, PRE_AUTH_CODE_LITERAL as PRE_AUTH_CODE_LITERAL3, PRE_AUTH_GRANT_LITERAL as PRE_AUTH_GRANT_LITERAL3, TokenErrorResponse as TokenErrorResponse2, toUniformCredentialOfferRequest as toUniformCredentialOfferRequest2 } from "@sphereon/oid4vci-common";
import { Loggers as Loggers3, ObjectUtils as ObjectUtils2 } from "@sphereon/ssi-types";
var logger3 = Loggers3.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 assertedUniformCredentialOffer2(opts.credentialOffer) : void 0;
const isPinRequired = credentialOffer && this.isPinRequiredValue(credentialOffer.credential_offer);
const issuer = opts.credentialIssuer ?? (credentialOffer ? getIssuerFromCredentialOfferPayload3(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 createDPoP2(getCreateDPoPOptions2(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 createDPoP2(getCreateDPoPOptions2(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 toUniformCredentialOfferRequest2(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: OpenId4VCIVersion3.VER_1_0_11,
credentialIssuer
});
if (!credentialOfferRequest || credentialOfferRequest.supportedFlows.includes(AuthzFlowType2.AUTHORIZATION_CODE_FLOW)) {
request.grant_type = GrantTypes2.AUTHORIZATION_CODE;
request.code = code;
request.redirect_uri = redirectUri;
if (codeVerifier) {
request.code_verifier = codeVerifier;
}
return request;
}
if (credentialOfferRequest?.supportedFlows.includes(AuthzFlowType2.PRE_AUTHORIZED_CODE_FLOW)) {
this.assertNumericPin(this.isPinRequiredValue(credentialOfferRequest.credential_offer), pin);
request.user_pin = pin;
request.grant_type = GrantTypes2.PRE_AUTHORIZED_CODE;
request[PRE_AUTH_CODE_LITERAL3] = credentialOfferRequest?.credential_offer.grants?.[PRE_AUTH_GRANT_LITERAL3]?.[PRE_AUTH_CODE_LITERAL3];
return request;
}
throw new Error("Credential offer request does not follow neither pre-authorized code nor authorization code flow requirements.");
}
assertPreAuthorizedGrantType(grantType) {
if (GrantTypes2.PRE_AUTHORIZED_CODE !== grantType) {
throw new Error("grant type must be PRE_AUTH_GRANT_LITERAL");
}
}
assertAuthorizationGrantType(grantType) {
if (GrantTypes2.AUTHORIZATION_CODE !== grantType) {
throw new Error("grant type must be 'authorization_code'");
}
}
isPinRequiredValue(requestPayload) {
let isPinRequired = false;
if (!requestPayload) {
throw new Error(TokenErrorResponse2.invalid_request);
}
const issuer = getIssuerFromCredentialOfferPayload3(requestPayload);
if (requestPayload.grants?.[PRE_AUTH_GRANT_LITERAL3]) {
isPinRequired = requestPayload.grants[PRE_AUTH_GRANT_LITERAL3]?.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[PRE_AUTH_CODE_LITERAL3]) {
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 === GrantTypes2.PRE_AUTHORIZED_CODE) {
this.assertPreAuthorizedGrantType(accessTokenRequest.grant_type);
this.assertNonEmptyPreAuthorizedCode(accessTokenRequest);
this.assertNumericPin(isPinRequired, accessTokenRequest.user_pin);
} else if (accessTokenRequest.grant_type === GrantTypes2.AUTHORIZATION_CODE) {
this.assertAuthorizationGrantType(accessTokenRequest.grant_type);
this.assertNonEmptyCodeVerifier(accessTokenRequest);
this.assertNonEmptyCode(accessTokenRequest);
} else {
this.throwNotSupportedFlow();
}
}
async sendAuthCode(requestTokenURL, accessTokenRequest, opts) {
return await formPost2(requestTokenURL, convertJsonToURI2(accessTokenRequest, {
mode: JsonURIMode2.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 || !ObjectUtils2.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
import { CodeChallengeMethod as CodeChallengeMethod2, convertJsonToURI as convertJsonToURI3, CreateRequestObjectMode, determineSpecVersionFromOffer as determineSpecVersionFromOffer2, formPost as formPost3, isW3cCredentialSupported, JsonURIMode as JsonURIMode3, OpenId4VCIVersion as OpenId4VCIVersion5, PARMode, ResponseType } from "@sphereon/oid4vci-common";
import { Loggers as Loggers6 } from "@sphereon/ssi-types";
// lib/MetadataClient.ts
import { determineSpecVersionFromOffer, getIssuerFromCredentialOfferPayload as getIssuerFromCredentialOfferPayload5, OpenId4VCIVersion as OpenId4VCIVersion4, WellKnownEndpoints as WellKnownEndpoints3 } from "@sphereon/oid4vci-common";
import { Loggers as Loggers5 } from "@sphereon/ssi-types";
// lib/MetadataClientV1_0_11.ts
import { getIssuerFromCredentialOfferPayload as getIssuerFromCredentialOfferPayload4, WellKnownEndpoints as WellKnownEndpoints2 } from "@sphereon/oid4vci-common";
import { Loggers as Loggers4 } from "@sphereon/ssi-types";
var logger4 = Loggers4.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 = getIssuerFromCredentialOfferPayload4(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, WellKnownEndpoints2.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, WellKnownEndpoints2.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 ${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_server} 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) {
logger4.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) {
logger4.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) {
logger4.debug(`Issuer ${issuer} does not expose authorization_endpoint, so only pre-auth will be supported`);
}
if (!token_endpoint) {
logger4.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) {
logger4.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;
}
logger4.debug(`Issuer ${issuer} token endpoint ${token_endpoint}, credential endpoint ${credential_endpoint}`);
return {
issuer,
token_endpoint,
credential_endp