@sphereon/oid4vci-issuer
Version:
OpenID 4 Verifiable Credential Issuance issuer REST endpoints
1 lines • 158 kB
Source Map (JSON)
{"version":3,"sources":["../lib/index.ts","../lib/builder/CredentialSupportedBuilderV1_15.ts","../lib/builder/VcIssuerBuilder.ts","../lib/VcIssuer.ts","../lib/functions/CredentialOfferUtils.ts","../lib/functions/ASOidcClient.ts","../lib/state-manager/MemoryStates.ts","../lib/state-manager/LookupStateManager.ts","../lib/state-manager/CredentialOfferStateBuilder.ts","../lib/builder/DisplayBuilder.ts","../lib/builder/IssuerMetadataBuilderV1_15.ts","../lib/builder/AuthorizationServerMetadataBuilder.ts","../lib/tokens/index.ts"],"sourcesContent":["import { VCI_LOGGERS } from '@sphereon/oid4vci-common'\nimport { ISimpleLogger } from '@sphereon/ssi-types'\n\nexport const LOG: ISimpleLogger<string | unknown> = VCI_LOGGERS.get('sphereon:oid4vci:issuer')\n\nexport * from './builder'\nexport * from './functions'\nexport * from './VcIssuer'\nexport * from './state-manager'\nexport * from './tokens'\nexport * from './types'\n","import {\n ClaimsDescriptionV1_0_15,\n CredentialConfigurationSupportedV1_0_15,\n CredentialDefinitionJwtVcJsonLdAndLdpVcV1_0_15,\n CredentialDefinitionJwtVcJsonV1_0_15,\n CredentialsSupportedDisplay,\n KeyProofType,\n OID4VCICredentialFormat,\n ProofType,\n ProofTypesSupported,\n TokenErrorResponse,\n} from '@sphereon/oid4vci-common'\n\nexport class CredentialSupportedBuilderV1_15 {\n format?: OID4VCICredentialFormat\n scope?: string\n credentialName?: string\n credentialDefinition?: CredentialDefinitionJwtVcJsonLdAndLdpVcV1_0_15 | CredentialDefinitionJwtVcJsonV1_0_15\n cryptographicBindingMethodsSupported?: ('jwk' | 'cose_key' | 'did' | string)[]\n credentialSigningAlgValuesSupported?: string[]\n proofTypesSupported?: ProofTypesSupported\n display?: CredentialsSupportedDisplay[]\n claims?: ClaimsDescriptionV1_0_15[] // Changed to use claims path pointers in v15\n vct?: string // For dc+sd-jwt format\n doctype?: string // For mso_mdoc format\n\n withFormat(credentialFormat: OID4VCICredentialFormat): CredentialSupportedBuilderV1_15 {\n this.format = credentialFormat\n return this\n }\n\n withCredentialName(credentialName: string): CredentialSupportedBuilderV1_15 {\n this.credentialName = credentialName\n return this\n }\n\n withCredentialDefinition(\n credentialDefinition: CredentialDefinitionJwtVcJsonLdAndLdpVcV1_0_15 | CredentialDefinitionJwtVcJsonV1_0_15,\n ): CredentialSupportedBuilderV1_15 {\n if (!credentialDefinition.type) {\n throw new Error('credentialDefinition should contain a type array')\n }\n this.credentialDefinition = credentialDefinition\n return this\n }\n\n withScope(scope: string): CredentialSupportedBuilderV1_15 {\n this.scope = scope\n return this\n }\n\n // New in v15: VCT support for dc+sd-jwt format\n withVct(vct: string): CredentialSupportedBuilderV1_15 {\n this.vct = vct\n return this\n }\n\n // New in v15: Doctype support for mso_mdoc format\n withDoctype(doctype: string): CredentialSupportedBuilderV1_15 {\n this.doctype = doctype\n return this\n }\n\n addCryptographicBindingMethod(method: string | string[]): CredentialSupportedBuilderV1_15 {\n if (!Array.isArray(method)) {\n this.cryptographicBindingMethodsSupported = this.cryptographicBindingMethodsSupported\n ? [...this.cryptographicBindingMethodsSupported, method]\n : [method]\n } else {\n this.cryptographicBindingMethodsSupported = this.cryptographicBindingMethodsSupported\n ? [...this.cryptographicBindingMethodsSupported, ...method]\n : method\n }\n return this\n }\n\n withCryptographicBindingMethod(method: string | string[]): CredentialSupportedBuilderV1_15 {\n this.cryptographicBindingMethodsSupported = Array.isArray(method) ? method : [method]\n return this\n }\n\n addCredentialSigningAlgValuesSupported(algValues: string | string[]): CredentialSupportedBuilderV1_15 {\n if (!Array.isArray(algValues)) {\n this.credentialSigningAlgValuesSupported = this.credentialSigningAlgValuesSupported\n ? [...this.credentialSigningAlgValuesSupported, algValues]\n : [algValues]\n } else {\n this.credentialSigningAlgValuesSupported = this.credentialSigningAlgValuesSupported\n ? [...this.credentialSigningAlgValuesSupported, ...algValues]\n : algValues\n }\n return this\n }\n\n withCredentialSigningAlgValuesSupported(algValues: string | string[]): CredentialSupportedBuilderV1_15 {\n this.credentialSigningAlgValuesSupported = Array.isArray(algValues) ? algValues : [algValues]\n return this\n }\n\n addProofTypesSupported(keyProofType: KeyProofType, proofType: ProofType): CredentialSupportedBuilderV1_15 {\n if (!this.proofTypesSupported) {\n this.proofTypesSupported = {}\n }\n this.proofTypesSupported[keyProofType] = proofType\n return this\n }\n\n withProofTypesSupported(proofTypesSupported: ProofTypesSupported): CredentialSupportedBuilderV1_15 {\n this.proofTypesSupported = proofTypesSupported\n return this\n }\n\n addCredentialSupportedDisplay(credentialDisplay: CredentialsSupportedDisplay | CredentialsSupportedDisplay[]): CredentialSupportedBuilderV1_15 {\n if (!Array.isArray(credentialDisplay)) {\n this.display = this.display ? [...this.display, credentialDisplay] : [credentialDisplay]\n } else {\n this.display = this.display ? [...this.display, ...credentialDisplay] : credentialDisplay\n }\n return this\n }\n\n withCredentialSupportedDisplay(credentialDisplay: CredentialsSupportedDisplay | CredentialsSupportedDisplay[]): CredentialSupportedBuilderV1_15 {\n this.display = Array.isArray(credentialDisplay) ? credentialDisplay : [credentialDisplay]\n return this\n }\n\n // New in v15: Claims description using path pointers\n withClaims(claims: ClaimsDescriptionV1_0_15[]): CredentialSupportedBuilderV1_15 {\n this.claims = claims\n return this\n }\n\n addClaim(claim: ClaimsDescriptionV1_0_15): CredentialSupportedBuilderV1_15 {\n if (!this.claims) {\n this.claims = []\n }\n this.claims.push(claim)\n return this\n }\n\n public build(): Record<string, CredentialConfigurationSupportedV1_0_15> {\n if (!this.format) {\n throw new Error(TokenErrorResponse.invalid_request)\n }\n\n const credentialSupported: CredentialConfigurationSupportedV1_0_15 = {\n format: this.format,\n } as CredentialConfigurationSupportedV1_0_15\n\n if (!this.credentialName) {\n throw new Error('A unique credential name is required')\n }\n\n // Format-specific handling for v15\n if (this.format === 'dc+sd-jwt') {\n if (!this.vct) {\n throw new Error('vct is required for dc+sd-jwt format')\n }\n ;(credentialSupported as any).vct = this.vct\n } else if (this.format === 'mso_mdoc') {\n if (!this.doctype) {\n throw new Error('doctype is required for mso_mdoc format')\n }\n ;(credentialSupported as any).doctype = this.doctype\n } else {\n if (!this.credentialDefinition) {\n throw new Error('credentialDefinition is required')\n }\n credentialSupported.credential_definition = this.credentialDefinition\n }\n\n if (this.scope) {\n credentialSupported.scope = this.scope\n }\n if (this.credentialSigningAlgValuesSupported) {\n credentialSupported.credential_signing_alg_values_supported = this.credentialSigningAlgValuesSupported\n }\n if (this.cryptographicBindingMethodsSupported) {\n credentialSupported.cryptographic_binding_methods_supported = this.cryptographicBindingMethodsSupported\n }\n if (this.display) {\n credentialSupported.display = this.display\n }\n if (this.claims) {\n ;(credentialSupported as any).claims = this.claims\n }\n\n const supportedConfiguration: Record<string, CredentialConfigurationSupportedV1_0_15> = {}\n supportedConfiguration[this.credentialName] = credentialSupported as CredentialConfigurationSupportedV1_0_15\n\n return supportedConfiguration\n }\n}\n","import {\n AuthorizationServerMetadata,\n ClientMetadata,\n ClientResponseType,\n CNonceState,\n CredentialConfigurationSupportedV1_0_15,\n CredentialIssuerMetadataOptsV1_0_15,\n CredentialOfferSession,\n IssuerMetadata,\n IssuerMetadataV1_0_15,\n IStateManager,\n JWTVerifyCallback,\n MetadataDisplay,\n TokenErrorResponse,\n TxCode,\n URIState,\n} from '@sphereon/oid4vci-common'\n\nimport { VcIssuer } from '../VcIssuer'\nimport { oidcAccessTokenVerifyCallback } from '../functions'\nimport { MemoryStates } from '../state-manager'\nimport { CredentialDataSupplier, CredentialSignerCallback } from '../types'\n\nimport { IssuerMetadataBuilderV1_15 } from './IssuerMetadataBuilderV1_15'\n\nexport class VcIssuerBuilder {\n issuerMetadataBuilder?: IssuerMetadataBuilderV1_15\n issuerMetadata: Partial<CredentialIssuerMetadataOptsV1_0_15> = {}\n authorizationServerMetadata: Partial<AuthorizationServerMetadata> = {}\n asClientOpts?: ClientMetadata\n txCode?: TxCode\n defaultCredentialOfferBaseUri?: string\n userPinRequired?: boolean\n cNonceExpiresIn?: number\n credentialOfferStateManager?: IStateManager<CredentialOfferSession>\n credentialOfferURIManager?: IStateManager<URIState>\n cNonceStateManager?: IStateManager<CNonceState>\n credentialSignerCallback?: CredentialSignerCallback\n jwtVerifyCallback?: JWTVerifyCallback\n credentialDataSupplier?: CredentialDataSupplier\n\n public withIssuerMetadata(issuerMetadata: IssuerMetadata) {\n if (!issuerMetadata.credential_configurations_supported) {\n throw new Error('IssuerMetadata should be from type v1_0_15 or higher.')\n }\n this.issuerMetadata = issuerMetadata as IssuerMetadataV1_0_15\n return this\n }\n\n public withASClientMetadata(clientMetadata: ClientMetadata): this {\n this.asClientOpts = clientMetadata\n return this\n }\n\n public withASClientMetadataParams({\n client_id,\n client_secret,\n redirect_uris,\n response_types,\n ...other\n }: { client_id: string; client_secret?: string; redirect_uris?: string[]; response_types?: ClientResponseType[] } & ClientMetadata): this {\n this.asClientOpts = { ...other, client_id, client_secret, redirect_uris, response_types }\n return this\n }\n\n public withAuthorizationMetadata(authorizationServerMetadata: AuthorizationServerMetadata) {\n this.authorizationServerMetadata = authorizationServerMetadata\n return this\n }\n\n public withIssuerMetadataBuilder(builder: IssuerMetadataBuilderV1_15) {\n this.issuerMetadataBuilder = builder\n return this\n }\n\n public withDefaultCredentialOfferBaseUri(baseUri: string) {\n this.defaultCredentialOfferBaseUri = baseUri\n return this\n }\n\n public withCredentialIssuer(issuer: string): this {\n this.issuerMetadata.credential_issuer = issuer\n return this\n }\n\n public withAuthorizationServers(authorizationServers: string | string[]): this {\n this.issuerMetadata.authorization_servers = typeof authorizationServers === 'string' ? [authorizationServers] : authorizationServers\n return this\n }\n\n public withCredentialEndpoint(credentialEndpoint: string): this {\n this.issuerMetadata.credential_endpoint = credentialEndpoint\n return this\n }\n\n public withTokenEndpoint(tokenEndpoint: string): this {\n this.issuerMetadata.token_endpoint = tokenEndpoint\n return this\n }\n\n public withNonceEndpoint(nonceEndpoint: string): this {\n this.issuerMetadata.nonce_endpoint = nonceEndpoint\n return this\n }\n\n public withIssuerDisplay(issuerDisplay: MetadataDisplay[] | MetadataDisplay): this {\n this.issuerMetadata.display = Array.isArray(issuerDisplay) ? issuerDisplay : [issuerDisplay]\n return this\n }\n\n public addIssuerDisplay(issuerDisplay: MetadataDisplay): this {\n this.issuerMetadata.display = [...(this.issuerMetadata.display ?? []), issuerDisplay]\n return this\n }\n\n public withCredentialConfigurationsSupported(credentialConfigurationsSupported: Record<string, CredentialConfigurationSupportedV1_0_15>) {\n this.issuerMetadata.credential_configurations_supported = credentialConfigurationsSupported\n return this\n }\n\n public addCredentialConfigurationsSupported(id: string, supportedCredential: CredentialConfigurationSupportedV1_0_15) {\n if (!this.issuerMetadata.credential_configurations_supported) {\n this.issuerMetadata.credential_configurations_supported = {}\n }\n this.issuerMetadata.credential_configurations_supported[id] = supportedCredential\n return this\n }\n\n public withTXCode(txCode: TxCode): this {\n this.txCode = txCode\n return this\n }\n\n public withCredentialOfferURIStateManager(credentialOfferURIManager: IStateManager<URIState>): this {\n this.credentialOfferURIManager = credentialOfferURIManager\n return this\n }\n\n public withInMemoryCredentialOfferURIState(): this {\n this.withCredentialOfferURIStateManager(new MemoryStates<URIState>())\n return this\n }\n\n public withCredentialOfferStateManager(credentialOfferManager: IStateManager<CredentialOfferSession>): this {\n this.credentialOfferStateManager = credentialOfferManager\n return this\n }\n\n public withInMemoryCredentialOfferState(): this {\n this.withCredentialOfferStateManager(new MemoryStates<CredentialOfferSession>())\n return this\n }\n\n public withCNonceStateManager(cNonceManager: IStateManager<CNonceState>): this {\n this.cNonceStateManager = cNonceManager\n return this\n }\n\n public withInMemoryCNonceState(): this {\n this.withCNonceStateManager(new MemoryStates())\n return this\n }\n\n public withCNonceExpiresIn(cNonceExpiresIn: number): this {\n this.cNonceExpiresIn = cNonceExpiresIn\n return this\n }\n\n public withCredentialSignerCallback(cb: CredentialSignerCallback): this {\n this.credentialSignerCallback = cb\n return this\n }\n\n public withJWTVerifyCallback(verifyCallback: JWTVerifyCallback): this {\n this.jwtVerifyCallback = verifyCallback\n return this\n }\n\n public withCredentialDataSupplier(credentialDataSupplier: CredentialDataSupplier): this {\n this.credentialDataSupplier = credentialDataSupplier\n return this\n }\n\n public build(): VcIssuer {\n if (!this.credentialOfferStateManager) {\n throw new Error(TokenErrorResponse.invalid_request)\n }\n if (!this.cNonceStateManager) {\n throw new Error(TokenErrorResponse.invalid_request)\n }\n if (Object.keys(this.issuerMetadata).length === 0) {\n throw new Error('issuerMetadata not set')\n }\n if (Object.keys(this.authorizationServerMetadata).length === 0) {\n throw new Error('authorizationServerMetadata not set')\n }\n\n const builder = this.issuerMetadataBuilder?.build()\n const metadata: Partial<IssuerMetadataV1_0_15> = { ...this.issuerMetadata, ...builder }\n // Let's make sure these get merged correctly:\n metadata.credential_configurations_supported = this.issuerMetadata.credential_configurations_supported\n metadata.display = [...(this.issuerMetadata.display ?? []), ...(builder?.display ?? [])]\n if (!metadata.credential_endpoint || !metadata.credential_issuer || !this.issuerMetadata.credential_configurations_supported) {\n throw new Error(TokenErrorResponse.invalid_request)\n }\n if (this.asClientOpts && typeof this.jwtVerifyCallback !== 'function') {\n if (!this.issuerMetadata.credential_issuer) {\n throw Error('issuerMetadata.credential_issuer is required when using asClientOpts')\n } else if (!this.issuerMetadata.authorization_servers) {\n throw Error('issuerMetadata.authorization_servers is required when using asClientOpts')\n }\n this.jwtVerifyCallback = oidcAccessTokenVerifyCallback({\n clientMetadata: this.asClientOpts,\n credentialIssuer: this.issuerMetadata.credential_issuer,\n authorizationServer: this.issuerMetadata.authorization_servers[0],\n })\n }\n return new VcIssuer(metadata as IssuerMetadataV1_0_15, this.authorizationServerMetadata as AuthorizationServerMetadata, {\n //TODO: discuss this with Niels. I did not find this in the spec. but I think we should somehow communicate this\n ...(this.txCode && { txCode: this.txCode }),\n defaultCredentialOfferBaseUri: this.defaultCredentialOfferBaseUri,\n credentialSignerCallback: this.credentialSignerCallback,\n jwtVerifyCallback: this.jwtVerifyCallback,\n credentialDataSupplier: this.credentialDataSupplier,\n credentialOfferSessions: this.credentialOfferStateManager,\n cNonces: this.cNonceStateManager,\n cNonceExpiresIn: this.cNonceExpiresIn,\n uris: this.credentialOfferURIManager,\n asClientOpts: this.asClientOpts,\n })\n }\n}\n","import { uuidv4 } from '@sphereon/oid4vc-common'\nimport {\n ALG_ERROR,\n AUD_ERROR,\n AuthorizationServerMetadata,\n ClientMetadata,\n CNonceState,\n CreateCredentialOfferURIResult,\n CREDENTIAL_MISSING_ERROR,\n CredentialDataSupplierInput,\n CredentialEventNames,\n CredentialIssuerMetadataOptsV1_0_15,\n CredentialOfferEventNames,\n CredentialOfferMode,\n CredentialOfferSession,\n CredentialOfferV1_0_15,\n CredentialRequest,\n CredentialRequestV1_0_15,\n CredentialResponse,\n DID_NO_DIDDOC_ERROR,\n EVENTS,\n IAT_ERROR,\n ISSUER_CONFIG_ERROR,\n IssueStatus,\n IStateManager,\n JsonLdIssuerCredentialDefinition,\n JWT_VERIFY_CONFIG_ERROR,\n JWTVerifyCallback,\n JwtVerifyResult,\n KID_DID_NO_DID_ERROR,\n KID_JWK_X5C_ERROR,\n NO_ISS_IN_AUTHORIZATION_CODE_CONTEXT,\n NotificationRequest,\n OID4VCICredentialFormat,\n OpenId4VCIVersion,\n PRE_AUTH_GRANT_LITERAL,\n ProofOfPossession,\n QRCodeOpts,\n StatusListOpts,\n TokenErrorResponse,\n toUniformCredentialOfferRequest,\n TxCode,\n TYP_ERROR,\n URIState,\n} from '@sphereon/oid4vci-common'\nimport { CompactSdJwtVc, CredentialMapper, InitiatorType, SubSystem, System, W3CVerifiableCredential } from '@sphereon/ssi-types'\nimport ShortUUID from 'short-uuid'\n\nimport { assertValidPinNumber, createCredentialOfferObject, createCredentialOfferURIFromObject, CredentialOfferGrantInput } from './functions'\nimport { LookupStateManager, lookupStateManagerMultiGetAsserted, MemoryStates } from './state-manager'\nimport { CredentialDataSupplier, CredentialDataSupplierArgs, CredentialIssuanceInput, CredentialSignerCallback, IssuerCorrelation } from './types'\n\nimport { LOG } from './index'\n\nconst shortUUID = ShortUUID()\n\nexport class VcIssuer {\n private _issuerMetadata: CredentialIssuerMetadataOptsV1_0_15 // TODO SSISDK-87 create proper solution to update issuer metadata\n private readonly _authorizationServerMetadata: AuthorizationServerMetadata\n private readonly _defaultCredentialOfferBaseUri?: string\n private readonly _credentialSignerCallback?: CredentialSignerCallback\n private readonly _jwtVerifyCallback?: JWTVerifyCallback\n private readonly _credentialDataSupplier?: CredentialDataSupplier\n private readonly _credentialOfferSessions: IStateManager<CredentialOfferSession>\n private readonly _cNonces: IStateManager<CNonceState>\n private readonly _uris: IStateManager<URIState>\n private readonly _cNonceExpiresIn: number\n private readonly _asClientOpts?: ClientMetadata\n\n constructor(\n issuerMetadata: CredentialIssuerMetadataOptsV1_0_15,\n authorizationServerMetadata: AuthorizationServerMetadata,\n args: {\n txCode?: TxCode\n baseUri?: string\n credentialOfferSessions: IStateManager<CredentialOfferSession>\n defaultCredentialOfferBaseUri?: string\n cNonces: IStateManager<CNonceState>\n uris?: IStateManager<URIState>\n credentialSignerCallback?: CredentialSignerCallback\n jwtVerifyCallback?: JWTVerifyCallback\n credentialDataSupplier?: CredentialDataSupplier\n cNonceExpiresIn?: number | undefined // expiration duration in seconds\n asClientOpts?: ClientMetadata\n },\n ) {\n this._issuerMetadata = issuerMetadata\n this._authorizationServerMetadata = authorizationServerMetadata\n this._defaultCredentialOfferBaseUri = args.defaultCredentialOfferBaseUri\n this._credentialOfferSessions = args.credentialOfferSessions ?? new MemoryStates()\n this._uris = args.uris ?? new MemoryStates()\n this._cNonces = args.cNonces\n this._credentialSignerCallback = args?.credentialSignerCallback\n this._jwtVerifyCallback = args?.jwtVerifyCallback\n this._credentialDataSupplier = args?.credentialDataSupplier\n this._cNonceExpiresIn = (args?.cNonceExpiresIn ?? (process.env.C_NONCE_EXPIRES_IN ? parseInt(process.env.C_NONCE_EXPIRES_IN) : 300)) as number\n this._asClientOpts = args?.asClientOpts\n }\n\n public async getCredentialOfferSessionById(\n id: string,\n lookups: Array<'uri' | 'preAuthorizedCode' | 'issuerState' | 'correlationId'> = ['preAuthorizedCode', 'issuerState', 'correlationId'],\n ): Promise<CredentialOfferSession> {\n // preAuth and issuerState can be looked up directly\n if (Array.isArray(lookups) && lookups.length > 0) {\n if (!this.uris) {\n return Promise.reject(Error('Cannot lookup credential offer by id if URI state manager is not set'))\n }\n return lookupStateManagerMultiGetAsserted({\n id,\n keyValueMapper: this._uris,\n valueStateManager: this._credentialOfferSessions,\n lookups: ['preAuthorizedCode', 'issuerState', 'correlationId'],\n })\n // return new LookupStateManager<URIState, CredentialOfferSession>(this.uris, this._credentialOfferSessions, lookup).getFromMultiple(id)\n }\n const session = await this._credentialOfferSessions.get(id)\n if (!session) {\n return Promise.reject(Error(`No session found for id ${id}`))\n }\n return session\n }\n\n public async deleteCredentialOfferSessionById(\n id: string,\n lookups: Array<'uri' | 'preAuthorizedCode' | 'issuerState' | 'correlationId'> = ['preAuthorizedCode', 'issuerState'],\n ): Promise<CredentialOfferSession> {\n const session = await this.getCredentialOfferSessionById(id, lookups)\n if (session) {\n if (session.preAuthorizedCode && (await this._credentialOfferSessions.has(session.preAuthorizedCode))) {\n await this._credentialOfferSessions.delete(session.preAuthorizedCode)\n }\n if (session.issuerState && (await this._credentialOfferSessions.has(session.issuerState))) {\n await this._credentialOfferSessions.delete(session.issuerState)\n }\n }\n return session\n }\n\n public async processNotification({\n preAuthorizedCode,\n issuerState,\n notification,\n }: {\n preAuthorizedCode?: string\n issuerState?: string\n notification: NotificationRequest\n }): Promise<Error | CredentialOfferSession> {\n const sessionId = preAuthorizedCode ?? issuerState\n const session = sessionId ? await this.getCredentialOfferSessionById(sessionId) : undefined\n if (!session || !sessionId) {\n LOG.error(`No session or session id found ${sessionId}`)\n return Error('invalid_notification_request')\n }\n if (notification.notification_id !== session.notification_id) {\n LOG.error(`Notification id ${notification.notification_id} not found in session. session notification id ${session.notification_id}`)\n return Error('invalid_notification_id')\n } else if (session.notification) {\n LOG.info(`Overwriting existing notification, as a new notification came in ${session.notification_id}`)\n }\n await this.updateSession({ preAuthorizedCode: preAuthorizedCode, issuerState: issuerState, notification })\n LOG.info(`Processed notification ${notification} for ${session.notification_id}`)\n return session\n }\n\n public async createCredentialOfferURI(opts: {\n offerMode?: CredentialOfferMode\n grants?: CredentialOfferGrantInput\n client_id?: string\n redirectUri?: string\n credential_configuration_ids?: Array<string>\n credentialDefinition?: JsonLdIssuerCredentialDefinition\n credentialOfferUri?: string\n credentialDataSupplierInput?: CredentialDataSupplierInput // Optional storage that can help the credential Data Supplier. For instance to store credential input data during offer creation, if no additional data can be supplied later on\n baseUri?: string\n scheme?: string\n pinLength?: number\n qrCodeOpts?: QRCodeOpts\n correlationId?: string\n statusListOpts?: Array<StatusListOpts>\n sessionLifeTimeInSec?: number\n }): Promise<CreateCredentialOfferURIResult> {\n const {\n offerMode = 'VALUE',\n correlationId = shortUUID.generate(),\n credential_configuration_ids,\n statusListOpts,\n credentialOfferUri,\n redirectUri,\n } = opts\n if (offerMode === 'REFERENCE' && !credentialOfferUri) {\n return Promise.reject(Error('credentialOfferUri must be supplied for offerMode REFERENCE!'))\n }\n\n const grants = opts.grants ? { ...opts.grants } : {}\n // for backwards compat, would be better if user sets the prop on the grants directly\n if (opts.pinLength !== undefined) {\n if (grants[PRE_AUTH_GRANT_LITERAL]) {\n grants[PRE_AUTH_GRANT_LITERAL].tx_code = {\n ...grants[PRE_AUTH_GRANT_LITERAL].tx_code,\n length: grants[PRE_AUTH_GRANT_LITERAL].tx_code?.length ?? opts.pinLength,\n }\n }\n }\n if (grants[PRE_AUTH_GRANT_LITERAL]?.tx_code && !grants[PRE_AUTH_GRANT_LITERAL]?.tx_code?.length) {\n grants[PRE_AUTH_GRANT_LITERAL].tx_code.length = 4\n }\n\n const baseUri = opts?.baseUri ?? this.defaultCredentialOfferBaseUri\n const credentialOfferObject = createCredentialOfferObject(this._issuerMetadata, {\n ...opts,\n grants,\n credentialOffer: credential_configuration_ids\n ? {\n credential_issuer: this._issuerMetadata.credential_issuer,\n credential_configuration_ids,\n }\n : undefined,\n })\n\n const preAuthGrant = credentialOfferObject.credential_offer.grants?.[PRE_AUTH_GRANT_LITERAL]\n const authGrant = credentialOfferObject.credential_offer.grants?.authorization_code\n\n const preAuthorizedCode = preAuthGrant?.['pre-authorized_code']\n const issuerState = authGrant?.issuer_state\n const txCode = preAuthGrant?.tx_code\n\n let userPin: string | undefined\n if (preAuthGrant?.tx_code) {\n const pinLength = preAuthGrant.tx_code.length ?? 4\n\n userPin = ('' + Math.round((Math.pow(10, pinLength) - 1) * Math.random())).padStart(pinLength, '0')\n assertValidPinNumber(userPin, pinLength)\n }\n const createdAt = +new Date()\n const lastUpdatedAt = createdAt\n const expirationInMs = (opts.sessionLifeTimeInSec ?? 10 * 60) * 1000\n const expiresAt = createdAt + Math.abs(expirationInMs)\n if (offerMode === 'REFERENCE') {\n if (!this.uris) {\n throw Error('No URI state manager set, whilst apparently credential offer by reference is being used')\n }\n\n const offerUri = opts.credentialOfferUri?.replace(':id', correlationId) // TODO how is this going to work with auth code flow?\n if (!offerUri) {\n return Promise.reject(Error('credentialOfferUri must be supplied for offerMode REFERENCE!'))\n }\n\n credentialOfferObject.credential_offer_uri = offerUri\n await this.uris.set(correlationId, {\n uri: offerUri,\n createdAt: createdAt,\n expiresAt,\n preAuthorizedCode,\n issuerState,\n correlationId: correlationId,\n })\n }\n\n const credentialOffer = await toUniformCredentialOfferRequest(\n {\n credential_offer: credentialOfferObject.credential_offer,\n credential_offer_uri: credentialOfferObject.credential_offer_uri,\n } as CredentialOfferV1_0_15,\n {\n version: OpenId4VCIVersion.VER_1_0_15,\n resolve: false, // We are creating the object, so do not resolve\n },\n )\n\n const status = IssueStatus.OFFER_CREATED\n const session: CredentialOfferSession = {\n redirectUri,\n preAuthorizedCode,\n issuerState,\n createdAt,\n lastUpdatedAt,\n expiresAt,\n status,\n notification_id: uuidv4(),\n ...(opts.client_id && { clientId: opts.client_id }),\n ...(userPin && { txCode: userPin }), // We used to use userPin according to older specs. We map these onto txCode now. If both are used, txCode in the end wins, even if they are different\n ...(opts.credentialDataSupplierInput && { credentialDataSupplierInput: opts.credentialDataSupplierInput }),\n credentialOffer,\n statusLists: statusListOpts,\n }\n\n const uri = createCredentialOfferURIFromObject(credentialOffer, offerMode, { ...opts, baseUri })\n if (preAuthorizedCode) {\n const lookupManager = new LookupStateManager<URIState, CredentialOfferSession>(this.uris, this._credentialOfferSessions, 'correlationId')\n await lookupManager.setMapped(\n preAuthorizedCode,\n {\n preAuthorizedCode,\n uri,\n createdAt,\n expiresAt,\n correlationId,\n issuerState,\n },\n session,\n )\n // await this.credentialOfferSessions.set(preAuthorizedCode, session)\n }\n // todo: check whether we could have the same value for issuer state and pre auth code if both are supported.\n if (issuerState) {\n const lookupManager = new LookupStateManager<URIState, CredentialOfferSession>(this.uris, this._credentialOfferSessions, 'correlationId')\n await lookupManager.setMapped(\n issuerState,\n {\n preAuthorizedCode,\n uri,\n createdAt,\n expiresAt,\n correlationId,\n issuerState,\n },\n session,\n )\n // await this.credentialOfferSessions.set(issuerState, session)\n }\n let qrCodeDataUri: string | undefined\n if (opts.qrCodeOpts) {\n const { AwesomeQR } = await import('awesome-qr')\n const qrCode = new AwesomeQR({ ...opts.qrCodeOpts, text: uri })\n qrCodeDataUri = `data:image/png;base64,${(await qrCode.draw())!.toString('base64')}`\n }\n const credentialOfferResult = {\n session,\n uri,\n qrCodeDataUri,\n correlationId,\n txCode,\n ...(userPin !== undefined && { userPin, pinLength: userPin?.length ?? 0 }),\n }\n EVENTS.emit(CredentialOfferEventNames.OID4VCI_OFFER_CREATED, {\n eventName: CredentialOfferEventNames.OID4VCI_OFFER_CREATED,\n id: correlationId,\n data: credentialOfferResult,\n initiator: '<Unknown>',\n initiatorType: InitiatorType.EXTERNAL,\n system: System.OID4VCI,\n issuer: this.issuerMetadata.credential_issuer,\n subsystem: SubSystem.API,\n createdAt,\n expiresAt,\n })\n return credentialOfferResult\n }\n\n /**\n * issueCredentialFromIssueRequest\n * @param opts issuerRequestParams\n * - issueCredentialsRequest the credential request\n * - issuerState the state of the issuer\n * - jwtVerifyCallback callback that verifies the Proof of Possession JWT\n * - issuerCallback callback to issue a Verifiable Credential\n * - cNonce an existing c_nonce\n */\n public async issueCredential(opts: {\n credentialRequest: CredentialRequest\n issuerCorrelation: IssuerCorrelation\n credential?: CredentialIssuanceInput\n credentialDataSupplier?: CredentialDataSupplier\n credentialDataSupplierInput?: CredentialDataSupplierInput\n newCNonce?: string\n cNonceExpiresIn?: number // expiration duration in seconds\n tokenExpiresIn?: number // expiration duration in seconds\n jwtVerifyCallback?: JWTVerifyCallback\n credentialSignerCallback?: CredentialSignerCallback\n responseCNonce?: string\n }): Promise<CredentialResponse> {\n /*if (!('credential_identifier' in opts.credentialRequest)) {\n throw new Error('credential request should be of spec version 1.0.13 or above')\n }*/\n const credentialRequest = opts.credentialRequest as CredentialRequestV1_0_15\n const issuerCorrelation = opts.issuerCorrelation\n try {\n if (!('credential_identifier' in credentialRequest) && !('credential_configuration_id' in credentialRequest)) {\n throw Error('credential request should have either credential_identifier or credential_configuration_id')\n }\n\n // Validate the credential_configuration_id exists in metadata if used\n if ('credential_configuration_id' in credentialRequest && credentialRequest.credential_configuration_id) {\n if (!this._issuerMetadata.credential_configurations_supported?.[credentialRequest.credential_configuration_id]) {\n throw Error(TokenErrorResponse.invalid_request)\n }\n }\n\n // Validate credential_identifier against authorization_details if present\n if ('credential_identifier' in credentialRequest && credentialRequest.credential_identifier && issuerCorrelation.authorizationDetails) {\n const validIdentifiers = issuerCorrelation.authorizationDetails.flatMap((detail: any) => detail.credential_identifiers || [])\n\n if (!validIdentifiers.includes(credentialRequest.credential_identifier)) {\n throw Error('credential_identifier not found in authorization_details')\n }\n }\n\n let format = this.lookupCredentialFormat(credentialRequest)\n const validated = await this.validateCredentialRequestProof({\n ...opts,\n format,\n tokenExpiresIn: opts.tokenExpiresIn ?? 180,\n })\n if (validated.preAuthorizedCode && !issuerCorrelation.preAuthorizedCode) {\n issuerCorrelation.preAuthorizedCode = validated.preAuthorizedCode\n }\n if (validated.issuerState && !issuerCorrelation.issuerState) {\n issuerCorrelation.issuerState = validated.issuerState\n }\n\n const { preAuthSession, authSession, cNonceState, jwtVerifyResult } = validated\n const did = jwtVerifyResult.did\n const jwk = jwtVerifyResult.jwk\n const kid = jwtVerifyResult.kid\n const newcNonce = opts.newCNonce ? opts.newCNonce : uuidv4()\n const newcNonceState = {\n cNonce: newcNonce,\n createdAt: +new Date(),\n ...(authSession?.issuerState && { issuerState: authSession.issuerState }),\n ...(preAuthSession && { preAuthorizedCode: preAuthSession.preAuthorizedCode }),\n }\n await this.cNonces.set(newcNonce, newcNonceState)\n\n if (!opts.credential && this._credentialDataSupplier === undefined && opts.credentialDataSupplier === undefined) {\n throw Error(`Either a credential needs to be supplied or a credentialDataSupplier`)\n }\n let credential: CredentialIssuanceInput | undefined\n\n let signerCallback: CredentialSignerCallback | undefined = opts.credentialSignerCallback\n const session: CredentialOfferSession | undefined = issuerCorrelation.preAuthorizedCode && preAuthSession ? preAuthSession : authSession\n if (opts.credential) {\n credential = opts.credential\n } else {\n const credentialDataSupplier: CredentialDataSupplier | undefined =\n typeof opts.credentialDataSupplier === 'function' ? opts.credentialDataSupplier : this._credentialDataSupplier\n if (typeof credentialDataSupplier !== 'function') {\n throw Error('Data supplier is mandatory if no credential is supplied')\n }\n if (!session) {\n throw Error('Either a preAuth or Auth session is required, none found')\n }\n const credentialOffer = session.credentialOffer\n if (!credentialOffer) {\n throw Error('Credential Offer missing')\n }\n const credentialDataSupplierInput = opts.credentialDataSupplierInput ?? session.credentialDataSupplierInput\n\n const result = await credentialDataSupplier({\n ...(cNonceState ? { ...cNonceState } : { ...authSession }),\n credentialRequest: opts.credentialRequest,\n credentialSupplierConfig: this._issuerMetadata.credential_supplier_config,\n format,\n credentialOffer /*todo: clientId: */,\n ...(credentialDataSupplierInput && { credentialDataSupplierInput }),\n } as CredentialDataSupplierArgs)\n credential = result.credential\n if (result.format) {\n format = result.format\n }\n if (typeof result.signCallback === 'function') {\n signerCallback = result.signCallback\n }\n }\n if (!credential) {\n throw Error('A credential needs to be supplied at this point')\n }\n // Bind credential to the provided proof of possession\n if (CredentialMapper.isSdJwtDecodedCredentialPayload(credential) && (kid || jwk) && !credential.cnf) {\n if (kid) {\n credential.cnf = {\n kid,\n }\n }\n // else TODO temp workaround IATAB2B-57\n if (jwk) {\n credential.cnf = {\n jwk,\n }\n }\n } else if (did && !CredentialMapper.isSdJwtDecodedCredentialPayload(credential) && credential.credentialSubject !== undefined) {\n const credentialSubjects = Array.isArray(credential.credentialSubject) ? credential.credentialSubject : [credential.credentialSubject]\n credentialSubjects.map((subject) => {\n if (!subject.id) {\n subject.id = did\n }\n return subject\n })\n credential.credentialSubject = Array.isArray(credential.credentialSubject) ? credentialSubjects : credentialSubjects[0]\n } else {\n // Mdoc Format\n // Nothing to do here\n }\n\n let issuer: string | undefined = undefined\n if (credential.iss) {\n issuer = credential.iss\n } else if (credential.issuer) {\n if (typeof credential.issuer === 'string') {\n issuer = credential.issuer\n } else if (typeof credential.issuer === 'object' && 'id' in credential.issuer && typeof credential.issuer.id === 'string') {\n issuer = credential.issuer.id\n }\n }\n\n const verifiableCredential = await this.issueCredentialImpl(\n {\n credentialRequest: opts.credentialRequest,\n format,\n credential,\n jwtVerifyResult,\n issuer,\n ...(session && { statusLists: session.statusLists }),\n },\n signerCallback,\n )\n // TODO implement acceptance_token (deferred response)\n // TODO update verification accordingly\n if (!verifiableCredential) {\n // credential: OPTIONAL. Contains issued Credential. MUST be present when acceptance_token is not returned. MAY be a JSON string or a JSON object, depending on the Credential format. See Appendix E for the Credential format specific encoding requirements\n throw new Error(CREDENTIAL_MISSING_ERROR)\n }\n if (cNonceState) {\n // remove the previous nonce\n await this.cNonces.delete(cNonceState.cNonce)\n }\n\n let notification_id: string | undefined\n\n if (issuerCorrelation.preAuthorizedCode && preAuthSession) {\n preAuthSession.lastUpdatedAt = +new Date()\n preAuthSession.status = IssueStatus.CREDENTIAL_ISSUED\n notification_id = preAuthSession.notification_id\n await this._credentialOfferSessions.set(issuerCorrelation.preAuthorizedCode, preAuthSession)\n } else if (issuerCorrelation.issuerState && authSession) {\n // If both were set we used the pre auth flow above as well, hence the else if\n authSession.lastUpdatedAt = +new Date()\n authSession.status = IssueStatus.CREDENTIAL_ISSUED\n notification_id = authSession.notification_id\n await this._credentialOfferSessions.set(issuerCorrelation.issuerState, authSession)\n }\n\n const response: CredentialResponse = {\n credentials: [{ credential: verifiableCredential }],\n // format: credentialRequest.format,\n c_nonce: newcNonce,\n c_nonce_expires_in: this._cNonceExpiresIn,\n ...(notification_id && { notification_id }),\n }\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n const experimentalSubjectIssuance = opts.credentialRequest.credential_subject_issuance\n if (experimentalSubjectIssuance?.subject_proof_mode) {\n if (experimentalSubjectIssuance.subject_proof_mode !== 'proof_replace') {\n throw Error('Only proof replace is supported currently')\n }\n response.transaction_id = authSession?.issuerState\n response.credential_subject_issuance = experimentalSubjectIssuance\n }\n return response\n } catch (error: unknown) {\n await this.updateSession({ preAuthorizedCode: issuerCorrelation.preAuthorizedCode, issuerState: issuerCorrelation.issuerState, error })\n throw error\n }\n }\n\n private lookupCredentialFormat(credentialRequest: CredentialRequestV1_0_15): OID4VCICredentialFormat | undefined {\n let format: OID4VCICredentialFormat | undefined\n\n if ('credential_configuration_id' in credentialRequest && credentialRequest.credential_configuration_id) {\n const credentialConfig = this._issuerMetadata.credential_configurations_supported?.[credentialRequest.credential_configuration_id]\n format = credentialConfig?.format as OID4VCICredentialFormat\n } else if ('credential_identifier' in credentialRequest && credentialRequest.credential_identifier) {\n const credentialIdentifier: any = credentialRequest.credential_identifier\n const matchedConfig = Object.values(this._issuerMetadata.credential_configurations_supported || {}).find(\n (config) => credentialIdentifier === config.id || credentialIdentifier === config.vct,\n )\n\n return matchedConfig?.format as OID4VCICredentialFormat\n }\n return format\n }\n\n private async updateSession({\n preAuthorizedCode,\n error,\n issuerState,\n notification,\n }: {\n preAuthorizedCode?: string\n issuerState?: string\n error?: unknown\n notification?: NotificationRequest\n }) {\n let issueState: IssueStatus | undefined = undefined\n if (error) {\n issueState = IssueStatus.ERROR\n } else if (notification) {\n if (notification.event == 'credential_accepted') {\n issueState = IssueStatus.NOTIFICATION_CREDENTIAL_ACCEPTED\n } else if (notification.event == 'credential_deleted') {\n issueState = IssueStatus.NOTIFICATION_CREDENTIAL_DELETED\n } else if (notification.event == 'credential_failure') {\n issueState = IssueStatus.NOTIFICATION_CREDENTIAL_FAILURE\n }\n }\n\n if (preAuthorizedCode) {\n const preAuthSession = await this._credentialOfferSessions.get(preAuthorizedCode)\n if (preAuthSession) {\n preAuthSession.lastUpdatedAt = +new Date()\n if (issueState) {\n preAuthSession.status = issueState\n }\n if (error) {\n preAuthSession.error = error instanceof Error ? error.message : error?.toString()\n }\n preAuthSession.notification_id\n if (notification) {\n preAuthSession.notification = notification\n }\n await this._credentialOfferSessions.set(preAuthorizedCode, preAuthSession)\n }\n }\n if (issuerState) {\n const authSession = await this._credentialOfferSessions.get(issuerState)\n if (authSession) {\n authSession.lastUpdatedAt = +new Date()\n if (issueState) {\n authSession.status = issueState\n }\n if (error) {\n authSession.error = error instanceof Error ? error.message : error?.toString()\n }\n if (notification) {\n authSession.notification = notification\n }\n await this._credentialOfferSessions.set(issuerState, authSession)\n }\n }\n }\n\n /*\n private async retrieveGrantsAndCredentialOfferSession(id: string): Promise<{\n clientId?: string;\n grants?: Grant,\n session: CredentialOfferSession\n }> {\n const session: CredentialOfferSession | undefined = await this._credentialOfferSessions.getAsserted(id)\n const clientId = session?.clientId\n const grants = session?.credentialOffer?.credential_offer?.grants\n if (!grants?.authorization_code?.issuer_state && !grants?.[PRE_AUTH_GRANT_LITERAL]?.[PRE_AUTH_CODE_LITERAL]) {\n throw new Error(GRANTS_MUST_NOT_BE_UNDEFINED)\n }\n return { session, clientId, grants }\n }*/\n\n private async validateCredentialRequestProof({\n credentialRequest,\n issuerCorrelation,\n format,\n jwtVerifyCallback,\n tokenExpiresIn,\n }: {\n credentialRequest: CredentialRequest\n issuerCorrelation: IssuerCorrelation\n format?: OID4VCICredentialFormat\n tokenExpiresIn: number // expiration duration in seconds\n // grants?: Grant,\n clientId?: string\n jwtVerifyCallback?: JWTVerifyCallback\n }) {\n let issuerState: string | undefined\n\n const supportedIssuanceFormats = ['jwt_vc_json', 'jwt_vc_json-ld', 'dc+sd-jwt', 'ldp_vc', 'mso_mdoc']\n try {\n if (format && !supportedIssuanceFormats.includes(format)) {\n throw Error(`Format ${format} not supported yet`)\n }\n\n const verifyFn: JWTVerifyCallback | undefined = jwtVerifyCallback ?? this._jwtVerifyCallback\n if (typeof verifyFn !== 'function') {\n throw Error(JWT_VERIFY_CONFIG_ERROR)\n }\n\n const credReq = credentialRequest as CredentialRequestV1_0_15\n\n // Validate request structure (mutually exclusive)\n if (credReq.proof && credReq.proofs) {\n throw Error('Credential request may not contain both proof and proofs parameters')\n }\n\n // Normalize candidates into a single array of ProofOfPossession objects\n const proofCandidates: Array<ProofOfPossession> = []\n\n if (credReq.proof) {\n proofCandidates.push(credReq.proof)\n } else if (credReq.proofs) {\n // Handle \"proofs\": prioritize 'jwt' as it's the only fully supported type\n if (Array.isArray(credReq.proofs.jwt)) {\n // Map to ProofOfPossession objects, handling both string and object formats\n for (const jwtProof of credReq.proofs.jwt) {\n if (typeof jwtProof === 'string') {\n // Handle case where jwt array contains strings instead of ProofOfPossession objects\n proofCandidates.push({\n proof_type: 'jwt',\n jwt: jwtProof,\n })\n } else if (jwtProof && typeof jwtProof === 'object' && 'jwt' in jwtProof) {\n // Handle proper ProofOfPossession object\n proofCandidates.push(jwtProof)\n }\n }\n }\n\n // Check if there are no supported proofs found\n if (proofCandidates.length === 0) {\n const availableTypes = Object.keys(credReq.proofs).join(', ')\n throw Error(`No supported proof types found in request. Available: [${availableTypes}]`)\n }\n } else {\n throw Error('Proof of possession is required. No proof or proofs value present in credential request')\n }\n\n // Execute verification\n let jwtVerifyResult: JwtVerifyResult | undefined\n const validationErrors: string[] = []\n\n for (const proof of proofCandidates) {\n try {\n jwtVerifyResult = await verifyFn({ jwt: proof.jwt })\n break\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error)\n validationErrors.push(msg)\n }\n }\n\n // Check success\n if (!jwtVerifyResult) {\n throw Error(`Unable to verify any provided proofs. Errors: ${validationErrors.join('; ')}`)\n }\n\n const { didDocument, did, jwt } = jwtVerifyResult\n const { header, payload } = jwt\n const { iss, aud, iat, nonce } = payload\n const issuer_state =\n 'issuer_state' in credentialRequest && credentialRequest.issuer_state ? credentialRequest.issuer_state : issuerCorrelation.issuerState\n if (!nonce && !issuer_state) {\n throw Error('No nonce or issuer_state was found in the Proof of Possession')\n }\n\n let createdAt: number = +new Date()\n let cNonceState: CNonceState | undefined\n if (nonce) {\n cNonceState = await this.cNonces.getAsserted(nonce)\n createdAt = cNonceState.createdAt\n }\n if (issuer_state) {\n const session = await this._credentialOfferSessions.getAsserted(issuer_state as string)\n issuerState = issuer_state as string | undefined\n createdAt = session.createdAt\n }\n\n // The verify callback should set the correct values, but let's look at the JWT ourselves to to be sure\n const alg = jwtVerifyResult.alg ?? header.alg\n const kid = jwtVerifyResult.kid ?? header.kid\n const jwk = jwtVerifyResult.jwk ?? header.jwk\n const x5c = jwtVerifyResult.x5c ?? header.x5c\n const typ = header.typ\n\n if (typ !== 'openid4vci-proof+jwt') {\n throw Error(TYP_ERROR)\n } else if (!alg) {\n throw Error(ALG_ERROR)\n } else if (x5c && (kid || jwk)) {\n // x5c cannot be used together with kid or jwk\n throw Error(KID_JWK_X5C_ERROR)\n } else if (kid && !did) {\n if (!jwk && !x5c) {\n // Make sure the callback function extracts the DID from the kid\n throw Error(KID_DID_NO_DID_ERROR)\n } else {\n // If JWK or x5c is present, log the information and proceed\n console.log(`KID present but no DID, using JWK or x5c`)\n }\n } else if (did && !didDocument) {\n // Make sure the callback function does DID resolution when a did is pre