UNPKG

@sphereon/oid4vci-issuer

Version:

OpenID 4 Verifiable Credential Issuance issuer REST endpoints

1,385 lines (1,374 loc) • 70 kB
var __defProp = Object.defineProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); // lib/index.ts import { VCI_LOGGERS } from "@sphereon/oid4vci-common"; // lib/builder/CredentialSupportedBuilderV1_13.ts import { TokenErrorResponse } from "@sphereon/oid4vci-common"; var CredentialSupportedBuilderV1_13 = class { static { __name(this, "CredentialSupportedBuilderV1_13"); } format; scope; credentialName; credentialDefinition; cryptographicBindingMethodsSupported; credentialSigningAlgValuesSupported; proofTypesSupported; display; credentialSubject; withFormat(credentialFormat) { this.format = credentialFormat; return this; } withCredentialName(credentialName) { this.credentialName = credentialName; return this; } withCredentialDefinition(credentialDefinition) { if (!credentialDefinition.type) { throw new Error("credentialDefinition should contain a type array"); } this.credentialDefinition = credentialDefinition; return this; } withScope(scope) { this.scope = scope; return this; } addCryptographicBindingMethod(method) { if (!Array.isArray(method)) { this.cryptographicBindingMethodsSupported = this.cryptographicBindingMethodsSupported ? [ ...this.cryptographicBindingMethodsSupported, method ] : [ method ]; } else { this.cryptographicBindingMethodsSupported = this.cryptographicBindingMethodsSupported ? [ ...this.cryptographicBindingMethodsSupported, ...method ] : method; } return this; } withCryptographicBindingMethod(method) { this.cryptographicBindingMethodsSupported = Array.isArray(method) ? method : [ method ]; return this; } addCredentialSigningAlgValuesSupported(algValues) { if (!Array.isArray(algValues)) { this.credentialSigningAlgValuesSupported = this.credentialSigningAlgValuesSupported ? [ ...this.credentialSigningAlgValuesSupported, algValues ] : [ algValues ]; } else { this.credentialSigningAlgValuesSupported = this.credentialSigningAlgValuesSupported ? [ ...this.credentialSigningAlgValuesSupported, ...algValues ] : algValues; } return this; } withCredentialSigningAlgValuesSupported(algValues) { this.credentialSigningAlgValuesSupported = Array.isArray(algValues) ? algValues : [ algValues ]; return this; } addProofTypesSupported(keyProofType, proofType) { if (!this.proofTypesSupported) { this.proofTypesSupported = {}; } this.proofTypesSupported[keyProofType] = proofType; return this; } withProofTypesSupported(proofTypesSupported) { this.proofTypesSupported = proofTypesSupported; return this; } addCredentialSupportedDisplay(credentialDisplay) { if (!Array.isArray(credentialDisplay)) { this.display = this.display ? [ ...this.display, credentialDisplay ] : [ credentialDisplay ]; } else { this.display = this.display ? [ ...this.display, ...credentialDisplay ] : credentialDisplay; } return this; } withCredentialSupportedDisplay(credentialDisplay) { this.display = Array.isArray(credentialDisplay) ? credentialDisplay : [ credentialDisplay ]; return this; } withCredentialSubject(credentialSubject) { this.credentialSubject = credentialSubject; return this; } addCredentialSubjectPropertyDisplay(subjectProperty, issuerCredentialSubjectDisplay) { if (!this.credentialSubject) { this.credentialSubject = {}; } this.credentialSubject[subjectProperty] = issuerCredentialSubjectDisplay; return this; } build() { if (!this.format) { throw new Error(TokenErrorResponse.invalid_request); } const credentialSupported = { format: this.format }; if (!this.credentialDefinition) { throw new Error("credentialDefinition is required"); } credentialSupported.credential_definition = this.credentialDefinition; if (this.scope) { credentialSupported.scope = this.scope; } if (!this.credentialName) { throw new Error("A unique credential name is required"); } if (this.credentialSigningAlgValuesSupported) { credentialSupported.credential_signing_alg_values_supported = this.credentialSigningAlgValuesSupported; } if (this.cryptographicBindingMethodsSupported) { credentialSupported.cryptographic_binding_methods_supported = this.cryptographicBindingMethodsSupported; } if (this.display) { credentialSupported.display = this.display; } const supportedConfiguration = {}; supportedConfiguration[this.credentialName] = credentialSupported; return supportedConfiguration; } }; // lib/builder/VcIssuerBuilder.ts import { TokenErrorResponse as TokenErrorResponse3 } from "@sphereon/oid4vci-common"; // lib/VcIssuer.ts import { uuidv4 as uuidv42 } from "@sphereon/oid4vc-common"; import { ALG_ERROR, AUD_ERROR, CREDENTIAL_MISSING_ERROR, CredentialEventNames, CredentialOfferEventNames, DID_NO_DIDDOC_ERROR, EVENTS, IAT_ERROR, ISSUER_CONFIG_ERROR, IssueStatus, JWT_VERIFY_CONFIG_ERROR, KID_DID_NO_DID_ERROR, KID_JWK_X5C_ERROR, NO_ISS_IN_AUTHORIZATION_CODE_CONTEXT, OpenId4VCIVersion, PRE_AUTH_GRANT_LITERAL as PRE_AUTH_GRANT_LITERAL2, TokenErrorResponse as TokenErrorResponse2, toUniformCredentialOfferRequest, TYP_ERROR } from "@sphereon/oid4vci-common"; import { CredentialMapper, InitiatorType, SubSystem, System } from "@sphereon/ssi-types"; import ShortUUID from "short-uuid"; // lib/functions/CredentialOfferUtils.ts import { uuidv4 } from "@sphereon/oid4vc-common"; import { PIN_NOT_MATCH_ERROR, PRE_AUTH_GRANT_LITERAL } from "@sphereon/oid4vci-common"; function createCredentialOfferGrants(inputGrants) { if (!inputGrants || Object.keys(inputGrants).length === 0) { return void 0; } const grants = {}; if (inputGrants?.[PRE_AUTH_GRANT_LITERAL]) { const grant = { ...inputGrants[PRE_AUTH_GRANT_LITERAL], "pre-authorized_code": inputGrants[PRE_AUTH_GRANT_LITERAL]["pre-authorized_code"] ?? uuidv4() }; if (grant.tx_code && !grant.tx_code.length) { grant.tx_code.length = 4; } grants[PRE_AUTH_GRANT_LITERAL] = grant; } if (inputGrants?.authorization_code) { grants.authorization_code = { ...inputGrants.authorization_code, // TODO: it should be possible to create offer without issuer_state // this is added to avoid breaking changes. issuer_state: inputGrants.authorization_code.issuer_state ?? uuidv4() }; } return grants; } __name(createCredentialOfferGrants, "createCredentialOfferGrants"); function parseCredentialOfferSchemeAndBaseUri(scheme, baseUri, credentialIssuer) { const newScheme = scheme?.replace("://", "") ?? (baseUri?.includes("://") ? baseUri.split("://")[0] : "openid-credential-offer"); let newBaseUri; if (baseUri) { newBaseUri = baseUri; } else if (newScheme.startsWith("http")) { if (credentialIssuer) { newBaseUri = credentialIssuer; if (!newBaseUri.startsWith(`${newScheme}://`)) { throw Error(`scheme ${newScheme} is different from base uri ${newBaseUri}`); } } else { throw Error(`A '${newScheme}' scheme requires a URI to be present as baseUri`); } } else { newBaseUri = ""; } newBaseUri = newBaseUri?.replace(`${newScheme}://`, ""); return { scheme: newScheme, baseUri: newBaseUri }; } __name(parseCredentialOfferSchemeAndBaseUri, "parseCredentialOfferSchemeAndBaseUri"); function createCredentialOfferObject(issuerMetadata, opts) { if (!issuerMetadata && !opts?.credentialOffer && !opts?.credentialOfferUri) { throw new Error("You have to provide issuerMetadata or credentialOffer object for creating a deeplink"); } const grants = createCredentialOfferGrants(opts?.grants); let credential_offer; if (opts?.credentialOffer) { credential_offer = { ...opts.credentialOffer }; } else { if (!issuerMetadata?.credential_configurations_supported) { throw new Error("credential_configurations_supported is mandatory in the metadata"); } credential_offer = { credential_issuer: issuerMetadata.credential_issuer, credential_configuration_ids: Object.keys(issuerMetadata.credential_configurations_supported) }; } if (grants) { credential_offer.grants = grants; } if (opts?.client_id) { credential_offer.client_id = opts.client_id; } return { credential_offer, credential_offer_uri: opts?.credentialOfferUri }; } __name(createCredentialOfferObject, "createCredentialOfferObject"); function createCredentialOfferObjectv1_0_11(issuerMetadata, opts) { if (!issuerMetadata && !opts?.credentialOffer && !opts?.credentialOfferUri) { throw new Error("You have to provide issuerMetadata or credentialOffer object for creating a deeplink"); } const grants = createCredentialOfferGrants(opts?.grants); if (grants?.[PRE_AUTH_GRANT_LITERAL]?.tx_code) { const { tx_code, ...rest } = grants[PRE_AUTH_GRANT_LITERAL]; grants[PRE_AUTH_GRANT_LITERAL] = { user_pin_required: true, ...rest }; } let credential_offer; if (opts?.credentialOffer) { credential_offer = { ...opts.credentialOffer, credentials: opts.credentialOffer?.credentials ?? issuerMetadata?.credentials_supported.map((s) => s.id).filter((i) => i !== void 0) }; } else { if (!issuerMetadata) { throw new Error("Issuer metadata is required when no credential offer is provided"); } credential_offer = { credential_issuer: issuerMetadata.credential_issuer, credentials: issuerMetadata?.credentials_supported.map((s) => s.id).filter((i) => i !== void 0) }; } return { credential_offer, credential_offer_uri: opts?.credentialOfferUri }; } __name(createCredentialOfferObjectv1_0_11, "createCredentialOfferObjectv1_0_11"); function createCredentialOfferURIFromObject(credentialOffer, offerMode, opts) { const { scheme, baseUri } = parseCredentialOfferSchemeAndBaseUri(opts?.scheme, opts?.baseUri, credentialOffer.credential_offer?.credential_issuer); if (offerMode === "REFERENCE") { if (!credentialOffer.credential_offer_uri) { throw Error(`credential_offer_uri must be set for offerMode ${offerMode}`); } if (credentialOffer.credential_offer_uri.includes("credential_offer_uri=")) { return credentialOffer.credential_offer_uri; } return `${scheme}://${baseUri}?credential_offer_uri=${encodeURIComponent(credentialOffer.credential_offer_uri)}`; } else if (offerMode === "VALUE") { return `${scheme}://${baseUri}?credential_offer=${encodeURIComponent(JSON.stringify(credentialOffer.credential_offer))}`; } throw Error(`unsupported offerMode ${offerMode}`); } __name(createCredentialOfferURIFromObject, "createCredentialOfferURIFromObject"); function createCredentialOfferURI(offerMode, issuerMetadata, opts) { const credentialOffer = createCredentialOfferObject(issuerMetadata, opts); return createCredentialOfferURIFromObject(credentialOffer, offerMode, opts); } __name(createCredentialOfferURI, "createCredentialOfferURI"); function createCredentialOfferURIv1_0_11(offerMode, issuerMetadata, opts) { const credentialOffer = createCredentialOfferObjectv1_0_11(issuerMetadata, opts); return createCredentialOfferURIFromObject(credentialOffer, offerMode, opts); } __name(createCredentialOfferURIv1_0_11, "createCredentialOfferURIv1_0_11"); var isPreAuthorizedCodeExpired = /* @__PURE__ */ __name((state, expirationDurationInSeconds) => { const now = +/* @__PURE__ */ new Date(); const expirationTime = state.createdAt + expirationDurationInSeconds * 1e3; return now >= expirationTime; }, "isPreAuthorizedCodeExpired"); var assertValidPinNumber = /* @__PURE__ */ __name((pin, pinLength) => { if (pin && !RegExp(`[\\d\\D]{${pinLength ?? 6}}`).test(pin)) { throw Error(`${PIN_NOT_MATCH_ERROR}`); } }, "assertValidPinNumber"); // lib/functions/ASOidcClient.ts import { decodeJwt, decodeProtectedHeader } from "@sphereon/oid4vc-common"; import { oidcDiscoverIssuer, oidcGetClient } from "@sphereon/ssi-express-support"; function oidcAccessTokenVerifyCallback(opts) { const clientMetadata = opts.clientMetadata ?? { client_id: opts.credentialIssuer }; return async (args) => { const oidcIssuer = await oidcDiscoverIssuer({ issuerUrl: opts.authorizationServer }); const oidcClient = await oidcGetClient(oidcIssuer.issuer, clientMetadata); const introspection = await oidcClient.introspect(args.jwt); if (!introspection.active) { return Promise.reject(Error("Access token is not active or invalid")); } const jwt = { header: decodeProtectedHeader(args.jwt), payload: decodeJwt(args.jwt) }; return { jwt, alg: jwt.header.alg, ...jwt.header.jwk && { jwk: jwt.header.jwk }, ...jwt.header.x5c && { x5c: jwt.header.x5c }, ...jwt.header.kid && { kid: jwt.header.kid } }; }; } __name(oidcAccessTokenVerifyCallback, "oidcAccessTokenVerifyCallback"); // lib/state-manager/MemoryStates.ts import { STATE_MISSING_ERROR } from "@sphereon/oid4vci-common"; var MemoryStates = class { static { __name(this, "MemoryStates"); } expiresInMS; states; cleanupIntervalId; constructor(opts) { this.expiresInMS = opts?.expiresInSec !== void 0 ? opts?.expiresInSec * 1e3 : 18e4; this.states = /* @__PURE__ */ new Map(); } async clearAll() { this.states.clear(); } async clearExpired(timestamp) { const states = Array.from(this.states.entries()); const ts = timestamp ?? +/* @__PURE__ */ new Date(); for (const [id, state] of states) { if (state.expiresAt && state.expiresAt < ts) { this.states.delete(id); } else if (!state.expiresAt) { if (state.createdAt + this.expiresInMS < ts) { this.states.delete(id); } } } } async delete(id) { if (!id) { throw Error("No id supplied"); } return this.states.delete(id); } async getAsserted(id) { if (!id) { throw Error("No id supplied"); } let result; if (await this.has(id)) { result = await this.get(id); } if (!result) { throw new Error(STATE_MISSING_ERROR + ` (${id})`); } return result; } async get(id) { return this.states.get(id); } async has(id) { if (!id) { throw Error("No id supplied"); } return this.states.has(id); } async set(id, stateValue) { if (!id) { throw Error("No id supplied"); } this.states.set(id, stateValue); } async startCleanupRoutine(timeout) { if (!this.cleanupIntervalId) { this.cleanupIntervalId = setInterval(() => this.clearExpired(), timeout ?? 3e4); } } async stopCleanupRoutine() { if (this.cleanupIntervalId) { clearInterval(this.cleanupIntervalId); } } }; // lib/state-manager/LookupStateManager.ts async function lookupStateManagerMultiGetAsserted(args) { const value = await lookupStateManagerMultiGet(args); if (value) { return value; } return Promise.reject(Error(`no value found for id ${args.id}`)); } __name(lookupStateManagerMultiGetAsserted, "lookupStateManagerMultiGetAsserted"); async function lookupStateManagerMultiGet({ id, lookups, keyValueMapper, valueStateManager }) { for (const lookup of lookups) { try { const value = await new LookupStateManager(keyValueMapper, valueStateManager, lookup).get(id); if (value) { return value; } } catch (e) { } } return valueStateManager.get(id); } __name(lookupStateManagerMultiGet, "lookupStateManagerMultiGet"); var LookupStateManager = class { static { __name(this, "LookupStateManager"); } keyValueMapper; valueStateManager; lookup; constructor(keyValueMapper, valueStateManager, lookup) { this.keyValueMapper = keyValueMapper; this.valueStateManager = valueStateManager; this.lookup = lookup; } startCleanupRoutine(timeout) { this.keyValueMapper.startCleanupRoutine(timeout); return this.valueStateManager.startCleanupRoutine(timeout); } stopCleanupRoutine() { this.keyValueMapper.stopCleanupRoutine(); return this.valueStateManager.stopCleanupRoutine(); } async clearAll() { this.keyValueMapper.clearAll(); this.valueStateManager.clearAll(); } async clearExpired(timestamp) { this.keyValueMapper.clearExpired(timestamp); this.valueStateManager.clearExpired(timestamp); } async assertedValueId(key) { const prop = this.lookup; const valueId = await this.keyValueMapper.getAsserted(key).then((keyState) => keyState && prop in keyState ? keyState[prop] : void 0); if (typeof valueId !== "string") { throw Error("no value id could be derived for key" + key); } return valueId; } async valueId(key) { const prop = this.lookup; return await this.keyValueMapper.get(key).then((keyState) => keyState && prop in keyState ? keyState[prop] : void 0); } async delete(id) { return await this.assertedValueId(id).then(async (value) => { await this.keyValueMapper.delete(id); return await this.valueStateManager.delete(value); }); } async get(id) { return this.valueId(id).then((value) => value ? this.valueStateManager.get(value) : void 0); } async has(id) { return this.valueId(id).then((value) => value ? this.valueStateManager.has(value) : false); } // eslint-disable-next-line @typescript-eslint/no-unused-vars async set(_id, _stateValue) { throw Error(`Please use the setMappedMethod that accepts both and id, value and object`); } async setMapped(valueKey, keyObject, stateValue) { const keys = keyObject; if (!(this.lookup in keys) || !keys[this.lookup]) { return Promise.reject(new Error(`keyValue ${keyObject} does not contain the lookup property ${this.lookup}`)); } const key = keys[this.lookup]; await this.keyValueMapper.set(key, keyObject); await this.valueStateManager.set(valueKey, stateValue); } async getAsserted(id) { return this.assertedValueId(id).then((value) => this.valueStateManager.getAsserted(value)); } }; // lib/state-manager/CredentialOfferStateBuilder.ts var CredentialOfferStateBuilder = class { static { __name(this, "CredentialOfferStateBuilder"); } credentialOfferState; constructor() { this.credentialOfferState = {}; } credentialOffer(credentialOffer) { this.credentialOfferState.credentialOffer = credentialOffer; return this; } createdAt(timestamp) { this.credentialOfferState.createdAt = timestamp; return this; } build() { if (!this.credentialOfferState.createdAt) { this.credentialOfferState.createdAt = +/* @__PURE__ */ new Date(); } if (!this.credentialOfferState.credentialOffer) { throw new Error("Not all properties are present to build an IssuerState object"); } return this.credentialOfferState; } }; // lib/VcIssuer.ts var shortUUID = ShortUUID(); var VcIssuer = class { static { __name(this, "VcIssuer"); } _issuerMetadata; _authorizationServerMetadata; _defaultCredentialOfferBaseUri; _credentialSignerCallback; _jwtVerifyCallback; _credentialDataSupplier; _credentialOfferSessions; _cNonces; _uris; _cNonceExpiresIn; _asClientOpts; constructor(issuerMetadata, authorizationServerMetadata, args) { this._issuerMetadata = issuerMetadata; this._authorizationServerMetadata = authorizationServerMetadata; this._defaultCredentialOfferBaseUri = args.defaultCredentialOfferBaseUri; this._credentialOfferSessions = args.credentialOfferSessions ?? new MemoryStates(); this._uris = args.uris ?? new MemoryStates(); this._cNonces = args.cNonces; this._credentialSignerCallback = args?.credentialSignerCallback; this._jwtVerifyCallback = args?.jwtVerifyCallback; this._credentialDataSupplier = args?.credentialDataSupplier; this._cNonceExpiresIn = args?.cNonceExpiresIn ?? (process.env.C_NONCE_EXPIRES_IN ? parseInt(process.env.C_NONCE_EXPIRES_IN) : 300); this._asClientOpts = args?.asClientOpts; } async getCredentialOfferSessionById(id, lookups = [ "preAuthorizedCode", "issuerState", "correlationId" ]) { if (Array.isArray(lookups) && lookups.length > 0) { if (!this.uris) { return Promise.reject(Error("Cannot lookup credential offer by id if URI state manager is not set")); } return lookupStateManagerMultiGetAsserted({ id, keyValueMapper: this._uris, valueStateManager: this._credentialOfferSessions, lookups: [ "preAuthorizedCode", "issuerState", "correlationId" ] }); } const session = await this._credentialOfferSessions.get(id); if (!session) { return Promise.reject(Error(`No session found for id ${id}`)); } return session; } async deleteCredentialOfferSessionById(id, lookups = [ "preAuthorizedCode", "issuerState" ]) { const session = await this.getCredentialOfferSessionById(id, lookups); if (session) { if (session.preAuthorizedCode && await this._credentialOfferSessions.has(session.preAuthorizedCode)) { await this._credentialOfferSessions.delete(session.preAuthorizedCode); } if (session.issuerState && await this._credentialOfferSessions.has(session.issuerState)) { await this._credentialOfferSessions.delete(session.issuerState); } } return session; } async processNotification({ preAuthorizedCode, issuerState, notification }) { const sessionId = preAuthorizedCode ?? issuerState; const session = sessionId ? await this.getCredentialOfferSessionById(sessionId) : void 0; if (!session || !sessionId) { LOG.error(`No session or session id found ${sessionId}`); return Error("invalid_notification_request"); } if (notification.notification_id !== session.notification_id) { LOG.error(`Notification id ${notification.notification_id} not found in session. session notification id ${session.notification_id}`); return Error("invalid_notification_id"); } else if (session.notification) { LOG.info(`Overwriting existing notification, as a new notification came in ${session.notification_id}`); } await this.updateSession({ preAuthorizedCode, issuerState, notification }); LOG.info(`Processed notification ${notification} for ${session.notification_id}`); return session; } async createCredentialOfferURI(opts) { const { offerMode = "VALUE", correlationId = shortUUID.generate(), credential_configuration_ids, statusListOpts, credentialOfferUri, redirectUri } = opts; if (offerMode === "REFERENCE" && !credentialOfferUri) { return Promise.reject(Error("credentialOfferUri must be supplied for offerMode REFERENCE!")); } const grants = opts.grants ? { ...opts.grants } : {}; if (opts.pinLength !== void 0) { if (grants[PRE_AUTH_GRANT_LITERAL2]) { grants[PRE_AUTH_GRANT_LITERAL2].tx_code = { ...grants[PRE_AUTH_GRANT_LITERAL2].tx_code, length: grants[PRE_AUTH_GRANT_LITERAL2].tx_code?.length ?? opts.pinLength }; } } if (grants[PRE_AUTH_GRANT_LITERAL2]?.tx_code && !grants[PRE_AUTH_GRANT_LITERAL2]?.tx_code?.length) { grants[PRE_AUTH_GRANT_LITERAL2].tx_code.length = 4; } const baseUri = opts?.baseUri ?? this.defaultCredentialOfferBaseUri; const credentialOfferObject = createCredentialOfferObject(this._issuerMetadata, { ...opts, grants, credentialOffer: credential_configuration_ids ? { credential_issuer: this._issuerMetadata.credential_issuer, credential_configuration_ids } : void 0 }); const preAuthGrant = credentialOfferObject.credential_offer.grants?.[PRE_AUTH_GRANT_LITERAL2]; const authGrant = credentialOfferObject.credential_offer.grants?.authorization_code; const preAuthorizedCode = preAuthGrant?.["pre-authorized_code"]; const issuerState = authGrant?.issuer_state; const txCode = preAuthGrant?.tx_code; let userPin; if (preAuthGrant?.tx_code) { const pinLength = preAuthGrant.tx_code.length ?? 4; userPin = ("" + Math.round((Math.pow(10, pinLength) - 1) * Math.random())).padStart(pinLength, "0"); assertValidPinNumber(userPin, pinLength); } const createdAt = +/* @__PURE__ */ new Date(); const lastUpdatedAt = createdAt; const expirationInMs = (opts.sessionLifeTimeInSec ?? 10 * 60) * 1e3; const expiresAt = createdAt + Math.abs(expirationInMs); if (offerMode === "REFERENCE") { if (!this.uris) { throw Error("No URI state manager set, whilst apparently credential offer by reference is being used"); } const offerUri = opts.credentialOfferUri?.replace(":id", correlationId); if (!offerUri) { return Promise.reject(Error("credentialOfferUri must be supplied for offerMode REFERENCE!")); } credentialOfferObject.credential_offer_uri = offerUri; await this.uris.set(correlationId, { uri: offerUri, createdAt, expiresAt, preAuthorizedCode, issuerState, correlationId }); } const credentialOffer = await toUniformCredentialOfferRequest({ credential_offer: credentialOfferObject.credential_offer, credential_offer_uri: credentialOfferObject.credential_offer_uri }, { version: OpenId4VCIVersion.VER_1_0_13, resolve: false }); const status = IssueStatus.OFFER_CREATED; const session = { redirectUri, preAuthorizedCode, issuerState, createdAt, lastUpdatedAt, expiresAt, status, notification_id: uuidv42(), ...opts.client_id && { clientId: opts.client_id }, ...userPin && { txCode: userPin }, ...opts.credentialDataSupplierInput && { credentialDataSupplierInput: opts.credentialDataSupplierInput }, credentialOffer, statusLists: statusListOpts }; const uri = createCredentialOfferURIFromObject(credentialOffer, offerMode, { ...opts, baseUri }); if (preAuthorizedCode) { const lookupManager = new LookupStateManager(this.uris, this._credentialOfferSessions, "correlationId"); await lookupManager.setMapped(preAuthorizedCode, { preAuthorizedCode, uri, createdAt, expiresAt, correlationId, issuerState }, session); } if (issuerState) { const lookupManager = new LookupStateManager(this.uris, this._credentialOfferSessions, "correlationId"); await lookupManager.setMapped(issuerState, { preAuthorizedCode, uri, createdAt, expiresAt, correlationId, issuerState }, session); } let qrCodeDataUri; if (opts.qrCodeOpts) { const { AwesomeQR } = await import("awesome-qr"); const qrCode = new AwesomeQR({ ...opts.qrCodeOpts, text: uri }); qrCodeDataUri = `data:image/png;base64,${(await qrCode.draw()).toString("base64")}`; } const credentialOfferResult = { session, uri, qrCodeDataUri, correlationId, txCode, ...userPin !== void 0 && { userPin, pinLength: userPin?.length ?? 0 } }; EVENTS.emit(CredentialOfferEventNames.OID4VCI_OFFER_CREATED, { eventName: CredentialOfferEventNames.OID4VCI_OFFER_CREATED, id: correlationId, data: credentialOfferResult, initiator: "<Unknown>", initiatorType: InitiatorType.EXTERNAL, system: System.OID4VCI, issuer: this.issuerMetadata.credential_issuer, subsystem: SubSystem.API, createdAt, expiresAt }); return credentialOfferResult; } /** * issueCredentialFromIssueRequest * @param opts issuerRequestParams * - issueCredentialsRequest the credential request * - issuerState the state of the issuer * - jwtVerifyCallback callback that verifies the Proof of Possession JWT * - issuerCallback callback to issue a Verifiable Credential * - cNonce an existing c_nonce */ async issueCredential(opts) { const credentialRequest = opts.credentialRequest; let preAuthorizedCode; let issuerState; try { if (!("credential_identifier" in credentialRequest) && !credentialRequest.format) { throw new Error("credential request should either have a credential_identifier or format and type"); } if (credentialRequest.format && !this.isMetadataSupportCredentialRequestFormat(credentialRequest.format)) { throw new Error(TokenErrorResponse2.invalid_request); } const validated = await this.validateCredentialRequestProof({ ...opts, tokenExpiresIn: opts.tokenExpiresIn ?? 180 }); preAuthorizedCode = validated.preAuthorizedCode; issuerState = validated.issuerState; const { preAuthSession, authSession, cNonceState, jwtVerifyResult } = validated; const did = jwtVerifyResult.did; const jwk = jwtVerifyResult.jwk; const kid = jwtVerifyResult.kid; const newcNonce = opts.newCNonce ? opts.newCNonce : uuidv42(); const newcNonceState = { cNonce: newcNonce, createdAt: +/* @__PURE__ */ new Date(), ...authSession?.issuerState && { issuerState: authSession.issuerState }, ...preAuthSession && { preAuthorizedCode: preAuthSession.preAuthorizedCode } }; await this.cNonces.set(newcNonce, newcNonceState); if (!opts.credential && this._credentialDataSupplier === void 0 && opts.credentialDataSupplier === void 0) { throw Error(`Either a credential needs to be supplied or a credentialDataSupplier`); } let credential; let format = credentialRequest.format; let signerCallback = opts.credentialSignerCallback; const session = preAuthorizedCode && preAuthSession ? preAuthSession : authSession; if (opts.credential) { credential = opts.credential; } else { const credentialDataSupplier = typeof opts.credentialDataSupplier === "function" ? opts.credentialDataSupplier : this._credentialDataSupplier; if (typeof credentialDataSupplier !== "function") { throw Error("Data supplier is mandatory if no credential is supplied"); } if (!session) { throw Error("Either a preAuth or Auth session is required, none found"); } const credentialOffer = session.credentialOffer; if (!credentialOffer) { throw Error("Credential Offer missing"); } const credentialDataSupplierInput = opts.credentialDataSupplierInput ?? session.credentialDataSupplierInput; const result = await credentialDataSupplier({ ...cNonceState ? { ...cNonceState } : { ...authSession }, credentialRequest: opts.credentialRequest, credentialSupplierConfig: this._issuerMetadata.credential_supplier_config, credentialOffer, ...credentialDataSupplierInput && { credentialDataSupplierInput } }); credential = result.credential; if (result.format) { format = result.format; } if (typeof result.signCallback === "function") { signerCallback = result.signCallback; } } if (!credential) { throw Error("A credential needs to be supplied at this point"); } if (CredentialMapper.isSdJwtDecodedCredentialPayload(credential) && (kid || jwk) && !credential.cnf) { if (kid) { credential.cnf = { kid }; } if (jwk) { credential.cnf = { jwk }; } } else if (did && !CredentialMapper.isSdJwtDecodedCredentialPayload(credential) && credential.credentialSubject !== void 0) { const credentialSubjects = Array.isArray(credential.credentialSubject) ? credential.credentialSubject : [ credential.credentialSubject ]; credentialSubjects.map((subject) => { if (!subject.id) { subject.id = did; } return subject; }); credential.credentialSubject = Array.isArray(credential.credentialSubject) ? credentialSubjects : credentialSubjects[0]; } else { } let issuer = void 0; if (credential.iss) { issuer = credential.iss; } else if (credential.issuer) { if (typeof credential.issuer === "string") { issuer = credential.issuer; } else if (typeof credential.issuer === "object" && "id" in credential.issuer && typeof credential.issuer.id === "string") { issuer = credential.issuer.id; } } const verifiableCredential = await this.issueCredentialImpl({ credentialRequest: opts.credentialRequest, format, credential, jwtVerifyResult, issuer, ...session && { statusLists: session.statusLists } }, signerCallback); if (!verifiableCredential) { throw new Error(CREDENTIAL_MISSING_ERROR); } if (cNonceState) { await this.cNonces.delete(cNonceState.cNonce); } let notification_id; if (preAuthorizedCode && preAuthSession) { preAuthSession.lastUpdatedAt = +/* @__PURE__ */ new Date(); preAuthSession.status = IssueStatus.CREDENTIAL_ISSUED; notification_id = preAuthSession.notification_id; await this._credentialOfferSessions.set(preAuthorizedCode, preAuthSession); } else if (issuerState && authSession) { authSession.lastUpdatedAt = +/* @__PURE__ */ new Date(); authSession.status = IssueStatus.CREDENTIAL_ISSUED; notification_id = authSession.notification_id; await this._credentialOfferSessions.set(issuerState, authSession); } const response = { credential: verifiableCredential, // format: credentialRequest.format, c_nonce: newcNonce, c_nonce_expires_in: this._cNonceExpiresIn, ...notification_id && { notification_id } }; const experimentalSubjectIssuance = opts.credentialRequest.credential_subject_issuance; if (experimentalSubjectIssuance?.subject_proof_mode) { if (experimentalSubjectIssuance.subject_proof_mode !== "proof_replace") { throw Error("Only proof replace is supported currently"); } response.transaction_id = authSession?.issuerState; response.credential_subject_issuance = experimentalSubjectIssuance; } return response; } catch (error) { await this.updateSession({ preAuthorizedCode, issuerState, error }); throw error; } } async updateSession({ preAuthorizedCode, error, issuerState, notification }) { let issueState = void 0; if (error) { issueState = IssueStatus.ERROR; } else if (notification) { if (notification.event == "credential_accepted") { issueState = IssueStatus.NOTIFICATION_CREDENTIAL_ACCEPTED; } else if (notification.event == "credential_deleted") { issueState = IssueStatus.NOTIFICATION_CREDENTIAL_DELETED; } else if (notification.event == "credential_failure") { issueState = IssueStatus.NOTIFICATION_CREDENTIAL_FAILURE; } } if (preAuthorizedCode) { const preAuthSession = await this._credentialOfferSessions.get(preAuthorizedCode); if (preAuthSession) { preAuthSession.lastUpdatedAt = +/* @__PURE__ */ new Date(); if (issueState) { preAuthSession.status = issueState; } if (error) { preAuthSession.error = error instanceof Error ? error.message : error?.toString(); } preAuthSession.notification_id; if (notification) { preAuthSession.notification = notification; } await this._credentialOfferSessions.set(preAuthorizedCode, preAuthSession); } } if (issuerState) { const authSession = await this._credentialOfferSessions.get(issuerState); if (authSession) { authSession.lastUpdatedAt = +/* @__PURE__ */ new Date(); if (issueState) { authSession.status = issueState; } if (error) { authSession.error = error instanceof Error ? error.message : error?.toString(); } if (notification) { authSession.notification = notification; } await this._credentialOfferSessions.set(issuerState, authSession); } } } /* private async retrieveGrantsAndCredentialOfferSession(id: string): Promise<{ clientId?: string; grants?: Grant, session: CredentialOfferSession }> { const session: CredentialOfferSession | undefined = await this._credentialOfferSessions.getAsserted(id) const clientId = session?.clientId const grants = session?.credentialOffer?.credential_offer?.grants if (!grants?.authorization_code?.issuer_state && !grants?.[PRE_AUTH_GRANT_LITERAL]?.[PRE_AUTH_CODE_LITERAL]) { throw new Error(GRANTS_MUST_NOT_BE_UNDEFINED) } return { session, clientId, grants } }*/ async validateCredentialRequestProof({ credentialRequest, jwtVerifyCallback, tokenExpiresIn }) { let preAuthorizedCode; let issuerState; const supportedIssuanceFormats = [ "jwt_vc_json", "jwt_vc_json-ld", "vc+sd-jwt", "ldp_vc", "mso_mdoc" ]; try { if (credentialRequest.format && !supportedIssuanceFormats.includes(credentialRequest.format)) { throw Error(`Format ${credentialRequest.format} not supported yet`); } else if (typeof this._jwtVerifyCallback !== "function" && typeof jwtVerifyCallback !== "function") { throw new Error(JWT_VERIFY_CONFIG_ERROR); } else if (!credentialRequest.proof) { throw Error("Proof of possession is required. No proof value present in credential request"); } const jwtVerifyResult = jwtVerifyCallback ? await jwtVerifyCallback(credentialRequest.proof) : await this._jwtVerifyCallback(credentialRequest.proof); const { didDocument, did, jwt } = jwtVerifyResult; const { header, payload } = jwt; const { iss, aud, iat, nonce } = payload; const issuer_state = "issuer_state" in credentialRequest && credentialRequest.issuer_state ? credentialRequest.issuer_state : void 0; if (!nonce && !issuer_state) { throw Error("No nonce was found in the Proof of Possession"); } let createdAt; let cNonceState; if (nonce) { cNonceState = await this.cNonces.getAsserted(nonce); preAuthorizedCode = cNonceState.preAuthorizedCode; issuerState = cNonceState.issuerState; createdAt = cNonceState.createdAt; } else if (issuer_state) { const session = await this._credentialOfferSessions.getAsserted(issuer_state); issuerState = issuer_state; createdAt = session.createdAt; } else { throw Error("No nonce or issuer_state was found in the Proof of Possession"); } const alg = jwtVerifyResult.alg ?? header.alg; const kid = jwtVerifyResult.kid ?? header.kid; const jwk = jwtVerifyResult.jwk ?? header.jwk; const x5c = jwtVerifyResult.x5c ?? header.x5c; const typ = header.typ; if (typ !== "openid4vci-proof+jwt") { throw Error(TYP_ERROR); } else if (!alg) { throw Error(ALG_ERROR); } else if (x5c && (kid || jwk)) { throw Error(KID_JWK_X5C_ERROR); } else if (kid && !did) { if (!jwk && !x5c) { throw Error(KID_DID_NO_DID_ERROR); } else { console.log(`KID present but no DID, using JWK or x5c`); } } else if (did && !didDocument) { throw Error(DID_NO_DIDDOC_ERROR); } const preAuthSession = preAuthorizedCode ? await this.credentialOfferSessions.get(preAuthorizedCode) : void 0; const authSession = issuerState ? await this.credentialOfferSessions.get(issuerState) : void 0; if (!preAuthSession && !authSession) { throw Error("Either a pre-authorized code or issuer state needs to be present"); } if (preAuthSession) { if (!preAuthSession.preAuthorizedCode || preAuthSession.preAuthorizedCode !== preAuthorizedCode) { throw Error("Invalid pre-authorized code"); } preAuthSession.lastUpdatedAt = +/* @__PURE__ */ new Date(); preAuthSession.status = IssueStatus.CREDENTIAL_REQUEST_RECEIVED; await this._credentialOfferSessions.set(preAuthorizedCode, preAuthSession); } if (authSession) { if (!authSession.issuerState || authSession.issuerState !== issuerState) { throw Error("Invalid issuer state"); } authSession.lastUpdatedAt = +/* @__PURE__ */ new Date(); authSession.status = IssueStatus.CREDENTIAL_REQUEST_RECEIVED; } if (!iss && authSession?.credentialOffer.credential_offer?.grants?.authorization_code) { throw new Error(NO_ISS_IN_AUTHORIZATION_CODE_CONTEXT); } if (!aud || aud !== this._issuerMetadata.credential_issuer) { throw new Error(AUD_ERROR); } if (!iat) { throw new Error(IAT_ERROR); } else if (iat > Math.round(createdAt / 1e3) + tokenExpiresIn) { throw new Error(IAT_ERROR); } return { jwtVerifyResult, preAuthorizedCode, preAuthSession, issuerState, authSession, cNonceState }; } catch (error) { await this.updateSession({ preAuthorizedCode, issuerState, error }); throw error; } } isMetadataSupportCredentialRequestFormat(requestFormat) { if (!this._issuerMetadata.credential_configurations_supported) { return false; } for (const credentialSupported of Object.values(this._issuerMetadata["credential_configurations_supported"])) { if (!Array.isArray(requestFormat) && credentialSupported.format === requestFormat) { return true; } else if (Array.isArray(requestFormat)) { for (const format of requestFormat) { if (credentialSupported.format === format) { return true; } } } } return false; } async issueCredentialImpl(opts, issuerCallback) { if (!opts.credential && !opts.credentialRequest || !this._credentialSignerCallback) { throw new Error(ISSUER_CONFIG_ERROR); } const credential = issuerCallback ? await issuerCallback(opts) : await this._credentialSignerCallback(opts); EVENTS.emit(CredentialEventNames.OID4VCI_CREDENTIAL_ISSUED, { eventName: CredentialEventNames.OID4VCI_CREDENTIAL_ISSUED, id: uuidv42(), data: credential, // TODO: Format, request etc initiator: opts.issuer ?? "<unknown>", initiatorType: InitiatorType.EXTERNAL, system: System.OID4VCI, subsystem: SubSystem.VC_ISSUER }); return credential; } get credentialSignerCallback() { return this._credentialSignerCallback; } get jwtVerifyCallback() { return this._jwtVerifyCallback; } get credentialDataSupplier() { return this._credentialDataSupplier; } get uris() { return this._uris; } get cNonceExpiresIn() { return this._cNonceExpiresIn; } get credentialOfferSessions() { return this._credentialOfferSessions; } get cNonces() { return this._cNonces; } get defaultCredentialOfferBaseUri() { return this._defaultCredentialOfferBaseUri; } get issuerMetadata() { return this._issuerMetadata; } get authorizationServerMetadata() { return this._authorizationServerMetadata; } get asClientOpts() { return this._asClientOpts; } }; // lib/builder/VcIssuerBuilder.ts var VcIssuerBuilder = class { static { __name(this, "VcIssuerBuilder"); } issuerMetadataBuilder; issuerMetadata = {}; authorizationServerMetadata = {}; asClientOpts; txCode; defaultCredentialOfferBaseUri; userPinRequired; cNonceExpiresIn; credentialOfferStateManager; credentialOfferURIManager; cNonceStateManager; credentialSignerCallback; jwtVerifyCallback; credentialDataSupplier; withIssuerMetadata(issuerMetadata) { if (!issuerMetadata.credential_configurations_supported) { throw new Error("IssuerMetadata should be from type v1_0_13 or higher."); } this.issuerMetadata = issuerMetadata; return this; } withASClientMetadata(clientMetadata) { this.asClientOpts = clientMetadata; return this; } withASClientMetadataParams({ client_id, client_secret, redirect_uris, response_types, ...other }) { this.asClientOpts = { ...other, client_id, client_secret, redirect_uris, response_types }; return this; } withAuthorizationMetadata(authorizationServerMetadata) { this.authorizationServerMetadata = authorizationServerMetadata; return this; } withIssuerMetadataBuilder(builder) { this.issuerMetadataBuilder = builder; return this; } withDefaultCredentialOfferBaseUri(baseUri) { this.defaultCredentialOfferBaseUri = baseUri; return this; } withCredentialIssuer(issuer) { this.issuerMetadata.credential_issuer = issuer; return this; } withAuthorizationServers(authorizationServers) { this.issuerMetadata.authorization_servers = typeof authorizationServers === "string" ? [ authorizationServers ] : authorizationServers; return this; } withCredentialEndpoint(credentialEndpoint) { this.issuerMetadata.credential_endpoint = credentialEndpoint; return this; } withBatchCredentialEndpoint(batchCredentialEndpoint) { this.issuerMetadata.batch_credential_endpoint = batchCredentialEndpoint; throw Error("Not implemented yet"); } withTokenEndpoint(tokenEndpoint) { this.issuerMetadata.token_endpoint = tokenEndpoint; return this; } withIssuerDisplay(issuerDisplay) { this.issuerMetadata.display = Array.isArray(issuerDisplay) ? issuerDisplay : [ issuerDisplay ]; return this; } addIssuerDisplay(issuerDisplay) { this.issuerMetadata.display = [ ...this.issuerMetadata.display ?? [], issuerDisplay ]; return this; } withCredentialConfigurationsSupported(credentialConfigurationsSupported) { this.issuerMetadata.credential_configurations_supported = credentialConfigurationsSupported; return this; } addCredentialConfigurationsSupported(id, supportedCredential) { if (!this.issuerMetadata.credential_configurations_supported) { this.issuerMetadata.credential_configurations_supported = {}; } this.issuerMetadata.credential_configurations_supported[id] = supportedCredential; return this; } withTXCode(txCode) { this.txCode = txCode; return this; } withCredentialOfferURIStateManager(credentialOfferURIManager) { this.credentialOfferURIManager = credentialOfferURIManager; return this; } withInMemoryCredentialOfferURIState() { this.withCredentialOfferURIStateManager(new MemoryStates()); return this; } withCredentialOfferStateManager(credentialOfferManager) { this.credentialOfferStateManager = credentialOfferManager; return this; } withInMemoryCredentialOfferState() { this.withCredentialOfferStateManager(new MemoryStates()); return this; } withCNonceStateManager(cNonceManager) { this.cNonceStateManager = cNonceManager; return this; } withInMemoryCNonceState() { this.withCNonceStateManager(new MemoryStates()); return this; } withCNonceExpiresIn(cNonceExpiresIn) { this.cNonceExpiresIn = cNonceExpiresIn; return this; } withCredentialSignerCallback(cb) { this.credentialSignerCallback = cb; return this; } withJWTVerifyCallback(verifyCallback) { this.jwtVerifyCallback = verifyCallback; return this; } withCredentialDataSupplier(credentialDataSupplier) { this.credentialDataSupplier = credentialDataSupplier; return this; } build() { if (!this.credentialOfferStateManager) { throw new Error(TokenErrorResponse3.invalid_request); } if (!this.cNonceStateManager) { throw new Error(TokenErrorResponse3.invalid_request); } if (Object.keys(this.issuerMetadata).length === 0) { throw new Error("issuerMetadata not set"); } if (Object.keys(this.authorizationServerMetadata).length === 0) { throw new Error("authorizationServerMetadata not set"); } const builder = this.issuerMetadataBuilder?.build(); const metadata = { ...this.issuerMetadata, ...builder }; metadata.credential_configurations_supported = this.issuerMetadata.credential_configurations_supported; metadata.display = [ ...this.issuerMetadata.display ?? [], ...builder?.display ?? [] ]; if (!metadata.credential_endpoint || !metadata.credential_issuer || !this.issuerMetadata.credential_configurations_supported) { throw new Error(TokenErrorResponse3.invalid_request); } if (this.asClientOpts && typeof this.jwtVerifyCallback !== "function") { if (!this.issuerMetadata.credential_issuer) { throw Error("issuerMetadata.credential_issuer is required when using asClientOpts"); } else if (!this.issuerMetadata.authorization_servers) { throw Error("issuerMetadata.authorization_servers is required when usi