diff --git a/.changeset/grumpy-avocados-decide.md b/.changeset/grumpy-avocados-decide.md new file mode 100644 index 0000000000..250622a538 --- /dev/null +++ b/.changeset/grumpy-avocados-decide.md @@ -0,0 +1,5 @@ +--- +'@credo-ts/openid4vc': patch +--- + +fix(openid4vc): use `vp_formats` in client_metadata instead of `vp_formats supported` (#2089) diff --git a/.changeset/perfect-islands-grin.md b/.changeset/perfect-islands-grin.md new file mode 100644 index 0000000000..023c6f44d0 --- /dev/null +++ b/.changeset/perfect-islands-grin.md @@ -0,0 +1,5 @@ +--- +'@credo-ts/openid4vc': patch +--- + +feat(openid4vc): support jwk thumbprint for openid token issuer diff --git a/.changeset/shiny-sheep-appear.md b/.changeset/shiny-sheep-appear.md new file mode 100644 index 0000000000..8e528852e9 --- /dev/null +++ b/.changeset/shiny-sheep-appear.md @@ -0,0 +1,20 @@ +--- +'@credo-ts/openid4vc': minor +--- + +feat(openid4vc): oid4vci authorization code flow, presentation during issuance and batch issuance. + +This is a big change to OpenID4VCI in Credo, with the neccsary breaking changes since we first added it to the framework. Over time the spec has changed significantly, but also our understanding of the standards and protocols. + +**Authorization Code Flow** +Credo now supports the authorization code flow, for both issuer and holder. An issuer can configure multiple authorization servers, and work with external authorization servers as well. The integration is based on OAuth2, with several extension specifications, mainly the OAuth2 JWT Access Token Profile, as well as Token Introspection (for opaque access tokens). Verification works out of the box, as longs as the authorization server has a `jwks_uri` configured. For Token Introspection it's also required to provide a `clientId` and `clientSecret` in the authorization server config. + +To use an external authorization server, the authorization server MUST include the `issuer_state` parameter from the credential offer in the access token. Otherwise it's not possible for Credo to correlate the authorization session to the offer session. + +The demo-openid contains an example with external authorization server, which can be used as reference. The Credo authorization server supports DPoP and PKCE. + +**Batch Issuance** +The credential request to credential mapper has been updated to support multiple proofs, and also multiple credential instances. The client can now also handle batch issuance. + +**Presentation During Issuance** +The presenation during issuance allows to request presentation using OID4VP before granting authorization for issuance of one or more credentials. This flow is automatically handled by the `resolveAuthorizationRequest` method on the oid4vci holder service. diff --git a/.eslintrc.js b/.eslintrc.js index e1ab5de77a..f20ea4d354 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -116,6 +116,7 @@ module.exports = { 'demo-openid/**', 'scripts/**', '**/tests/**', + 'tests/**', ], env: { jest: true, diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 23ef4c4a1f..8997c3067d 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -129,7 +129,7 @@ jobs: - run: mv coverage/coverage-final.json coverage/${{ matrix.shard }}.json - uses: actions/upload-artifact@v4 with: - name: coverage-artifacts + name: coverage-artifacts-${{ matrix.node-version }} path: coverage/${{ matrix.shard }}.json overwrite: true @@ -184,7 +184,7 @@ jobs: - run: mv coverage/coverage-final.json coverage/e2e.json - uses: actions/upload-artifact@v4 with: - name: coverage-artifacts + name: coverage-artifacts-${{ matrix.node-version }} path: coverage/e2e.json overwrite: true @@ -195,9 +195,10 @@ jobs: steps: - uses: actions/download-artifact@v4 with: - name: coverage-artifacts + name: coverage-artifacts-20 path: coverage - uses: codecov/codecov-action@v4 with: directory: coverage + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.gitignore b/.gitignore index cf4a81ebb7..b069958a86 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,6 @@ aries-framework-*.tgz coverage .DS_Store logs.txt -logs/ \ No newline at end of file +logs/ + +ngrok.auth.yml \ No newline at end of file diff --git a/demo-openid/README.md b/demo-openid/README.md index f8ad0777c3..2e2788e35e 100644 --- a/demo-openid/README.md +++ b/demo-openid/README.md @@ -6,7 +6,8 @@ Alice, a former student of Faber College, connects with the College, is issued a ## Features -- ✅ Issuing a credential. +- ✅ Issuing a credential without authorization (pre-authorized code flow). +- ✅ Issuing a credenital with external authorization server (authorization code flow) - ✅ Resolving a credential offer. - ✅ Accepting a credential offer. - ✅ Requesting a credential presentation. @@ -29,7 +30,7 @@ Clone the Credo git repository: git clone https://github.com/openwallet-foundation/credo-ts.git ``` -Open three different terminals next to each other and in both, go to the demo folder: +Open four different terminals next to each other and in each, go to the demo folder: ```sh cd credo-ts/demo-openid @@ -41,13 +42,19 @@ Install the project in one of the terminals: pnpm install ``` -In the first terminal run the Issuer: +In the first terminal run the OpenID Provider: + +```sh +pnpm provider +``` + +In the second terminal run the Issuer: ```sh pnpm issuer ``` -In the second terminal run the Holder: +In the third terminal run the Holder: ```sh pnpm holder @@ -65,7 +72,8 @@ To create a credential offer: - Go to the Issuer terminal. - Select `Create a credential offer`. -- Select `UniversityDegreeCredential`. +- Choose whether authorization is required +- Select the credential(s) you want to issue. - Now copy the content INSIDE the quotes (without the quotes). To resolve and accept the credential: @@ -74,6 +82,8 @@ To resolve and accept the credential: - Select `Resolve a credential offer`. - Paste the content copied from the credential offer and hit enter. - Select `Accept the credential offer`. +- Choose which credential(s) to accept +- If authorization is required a link will be printed in the terminal, open this in your browser. You can sign in using any username and password. Once authenticated return to the terminal - You have now stored your credential. To create a presentation request: @@ -99,3 +109,29 @@ Exit: Restart: - Select 'restart', to shutdown the current program and start a new one + +### Optional Proxy + +By default all services will be started on `localhost`, and thus won't be reachable by other external services (such as a mobile wallet). If you want to expose the required services to the public, you need to expose multiple ngrok tunnels. + +We can setup the tunnels automatically using ngrok. First make sure you have an ngrok account and get your access token from this page: https://dashboard.ngrok.com/get-started/setup/ + +Then copy the `ngrok.auth.example.yml` file to `ngrok.auth.yml`: + +```sh +cp ngrok.auth.example.yml ngrok.auth.yml +``` + +And finally set the `authtoken` to the auth token as displayed in the ngrok dashboard. + +Once set up, you can run the following command in a separate terminal window. + +```sh +pnpm proxies +``` + +This will open three proxies. You should then run your demo environments with these proxies: + +- `PROVIDER_HOST=https://d404-123-123-123-123.ngrok-free.app ISSUER_HOST=https://d738-123-123-123-123.ngrok-free.app pnpm provider` (ngrok url for port 3042) +- `PROVIDER_HOST=https://d404-123-123-123-123.ngrok-free.app ISSUER_HOST=https://d738-123-123-123-123.ngrok-free.app pnpm issuer` (ngrok url for port 2000) +- `VERIFIER_HOST=https://1d91-123-123-123-123.ngrok-free.app pnpm verifier` (ngrok url for port 4000) diff --git a/demo-openid/ngrok.auth.example.yml b/demo-openid/ngrok.auth.example.yml new file mode 100644 index 0000000000..f5efc6b4e3 --- /dev/null +++ b/demo-openid/ngrok.auth.example.yml @@ -0,0 +1,2 @@ +authtoken: ea8af45e-0a76-44d5-b2a2-bab9d4bfb346 +version: '2' diff --git a/demo-openid/ngrok.yml b/demo-openid/ngrok.yml new file mode 100644 index 0000000000..0d39b069c1 --- /dev/null +++ b/demo-openid/ngrok.yml @@ -0,0 +1,12 @@ +version: 2 + +tunnels: + issuer: + proto: http + addr: 2000 + provider: + proto: http + addr: 3042 + verifier: + proto: http + addr: 4000 diff --git a/demo-openid/package.json b/demo-openid/package.json index 787bf0167c..de88ea3f9f 100644 --- a/demo-openid/package.json +++ b/demo-openid/package.json @@ -10,15 +10,20 @@ "license": "Apache-2.0", "scripts": { "issuer": "ts-node src/IssuerInquirer.ts", + "provider": "tsx src/provider.js", "holder": "ts-node src/HolderInquirer.ts", - "verifier": "ts-node src/VerifierInquirer.ts" + "verifier": "ts-node src/VerifierInquirer.ts", + "proxies": "ngrok --config ngrok.yml,ngrok.auth.yml start provider issuer verifier" }, "dependencies": { "@hyperledger/anoncreds-nodejs": "^0.2.2", "@hyperledger/aries-askar-nodejs": "^0.2.3", "@hyperledger/indy-vdr-nodejs": "^0.2.2", + "@koa/bodyparser": "^5.1.1", "express": "^4.18.1", - "inquirer": "^8.2.5" + "inquirer": "^8.2.5", + "jose": "^5.3.0", + "oidc-provider": "^8.4.6" }, "devDependencies": { "@credo-ts/askar": "workspace:*", @@ -28,8 +33,10 @@ "@types/express": "^4.17.13", "@types/figlet": "^1.5.4", "@types/inquirer": "^8.2.6", + "@types/oidc-provider": "^8.4.4", "clear": "^0.1.0", "figlet": "^1.5.2", - "ts-node": "^10.9.2" + "ts-node": "^10.9.2", + "tsx": "^4.11.0" } } diff --git a/demo-openid/src/BaseAgent.ts b/demo-openid/src/BaseAgent.ts index f0c79a4e86..891afc8e34 100644 --- a/demo-openid/src/BaseAgent.ts +++ b/demo-openid/src/BaseAgent.ts @@ -1,7 +1,15 @@ import type { InitConfig, KeyDidCreateOptions, ModulesMap, VerificationMethod } from '@credo-ts/core' import type { Express } from 'express' -import { Agent, DidKey, HttpOutboundTransport, KeyType, TypedArrayEncoder } from '@credo-ts/core' +import { + Agent, + ConsoleLogger, + DidKey, + HttpOutboundTransport, + KeyType, + LogLevel, + TypedArrayEncoder, +} from '@credo-ts/core' import { HttpInboundTransport, agentDependencies } from '@credo-ts/node' import express from 'express' @@ -26,6 +34,8 @@ export class BaseAgent { const config = { label: name, walletConfig: { id: name, key: name }, + allowInsecureHttpUrls: true, + logger: new ConsoleLogger(LogLevel.off), } satisfies InitConfig this.config = config diff --git a/demo-openid/src/BaseInquirer.ts b/demo-openid/src/BaseInquirer.ts index 358d72b632..a1e91f298a 100644 --- a/demo-openid/src/BaseInquirer.ts +++ b/demo-openid/src/BaseInquirer.ts @@ -8,48 +8,66 @@ export enum ConfirmOptions { } export class BaseInquirer { - public optionsInquirer: { type: string; prefix: string; name: string; message: string; choices: string[] } - public inputInquirer: { type: string; prefix: string; name: string; message: string; choices: string[] } - - public constructor() { - this.optionsInquirer = { - type: 'list', - prefix: '', - name: 'options', - message: '', - choices: [], - } - - this.inputInquirer = { - type: 'input', - prefix: '', - name: 'input', - message: '', - choices: [], - } + private optionsInquirer = { + type: 'list', + prefix: '', + name: 'options', + message: '', + choices: [], } + private inputInquirer = { + type: 'input', + prefix: '', + name: 'input', + message: '', + choices: [], + } + + public async pickOne(options: string[], title?: string): Promise { + const result = await prompt([ + { + ...this.optionsInquirer, + message: title ?? Title.OptionsTitle, + choices: options, + }, + ]) - public inquireOptions(promptOptions: string[]) { - this.optionsInquirer.message = Title.OptionsTitle - this.optionsInquirer.choices = promptOptions - return this.optionsInquirer + return result.options } - public inquireInput(title: string) { - this.inputInquirer.message = title - return this.inputInquirer + public async pickMultiple(options: string[], title?: string): Promise { + const result = await prompt([ + { + ...this.optionsInquirer, + message: title ?? Title.OptionsTitle, + choices: options, + type: 'checkbox', + }, + ]) + + return result.options } - public inquireConfirmation(title: string) { - this.optionsInquirer.message = title - this.optionsInquirer.choices = [ConfirmOptions.Yes, ConfirmOptions.No] - return this.optionsInquirer + public async inquireInput(title: string): Promise { + const result = await prompt([ + { + ...this.inputInquirer, + message: title, + }, + ]) + + return result.input } - public async inquireMessage() { - this.inputInquirer.message = Title.MessageTitle - const message = await prompt([this.inputInquirer]) + public async inquireConfirmation(title: string) { + const result = await prompt([ + { + ...this.optionsInquirer, + choices: [ConfirmOptions.Yes, ConfirmOptions.No], + message: title, + }, + ]) - return message.input[0] === 'q' ? null : message.input + return result.options === ConfirmOptions.Yes } } diff --git a/demo-openid/src/Holder.ts b/demo-openid/src/Holder.ts index 6b3d5ab499..7a3aa5a23b 100644 --- a/demo-openid/src/Holder.ts +++ b/demo-openid/src/Holder.ts @@ -6,8 +6,16 @@ import { W3cJsonLdVerifiableCredential, DifPresentationExchangeService, Mdoc, + DidKey, + DidJwk, + getJwkFromKey, } from '@credo-ts/core' -import { OpenId4VcHolderModule } from '@credo-ts/openid4vc' +import { + authorizationCodeGrantIdentifier, + OpenId4VcHolderModule, + OpenId4VciAuthorizationFlow, + preAuthorizedCodeGrantIdentifier, +} from '@credo-ts/openid4vc' import { ariesAskar } from '@hyperledger/aries-askar-nodejs' import { BaseAgent } from './BaseAgent' @@ -21,6 +29,11 @@ function getOpenIdHolderModules() { } export class Holder extends BaseAgent> { + public client = { + clientId: 'wallet', + redirectUri: 'http://localhost:3000/redirect', + } + public constructor(port: number, name: string) { super({ port, name, modules: getOpenIdHolderModules() }) } @@ -28,11 +41,8 @@ export class Holder extends BaseAgent> public static async build(): Promise { const holder = new Holder(3000, 'OpenId4VcHolder ' + Math.random().toString()) await holder.initializeAgent('96213c3d7fc8d4d6754c7a0fd969598e') - - // Set trusted issuer certificates. Required fro verifying mdoc credentials - const trustedCertificates: string[] = [] - await holder.agent.x509.setTrustedCertificates( - trustedCertificates.length === 0 ? undefined : (trustedCertificates as [string, ...string[]]) + await holder.agent.x509.addTrustedCertificate( + 'MIH7MIGioAMCAQICEFvUcSkwWUaPlEWnrOmu_EYwCgYIKoZIzj0EAwIwDTELMAkGA1UEBhMCREUwIBcNMDAwMTAxMDAwMDAwWhgPMjA1MDAxMDEwMDAwMDBaMA0xCzAJBgNVBAYTAkRFMDkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDIgAC3A9V8ynqRcVjADqlfpZ9X8mwbew0TuQldH_QOpkadsWjAjAAMAoGCCqGSM49BAMCA0gAMEUCIQDXGNookSkHqRXiOP_0fVUdNIScY13h3DWkqSopFIYB2QIgUzNFnZ-SEdm-7UMzggaPiFgtznVzmHw2h4vVtuLzWlA' ) return holder @@ -42,25 +52,114 @@ export class Holder extends BaseAgent> return await this.agent.modules.openId4VcHolder.resolveCredentialOffer(credentialOffer) } - public async requestAndStoreCredentials( + public async resolveIssuerMetadata(credentialIssuer: string) { + return await this.agent.modules.openId4VcHolder.resolveIssuerMetadata(credentialIssuer) + } + + public async initiateAuthorization( resolvedCredentialOffer: OpenId4VciResolvedCredentialOffer, credentialsToRequest: string[] ) { - const tokenResponse = await this.agent.modules.openId4VcHolder.requestToken({ resolvedCredentialOffer }) + const grants = resolvedCredentialOffer.credentialOfferPayload.grants + // TODO: extend iniateAuthorization in oid4vci lib? Or not? + if (grants?.[preAuthorizedCodeGrantIdentifier]) { + return { + authorizationFlow: 'PreAuthorized', + preAuthorizedCode: grants[preAuthorizedCodeGrantIdentifier]['pre-authorized_code'], + } as const + } else if (resolvedCredentialOffer.credentialOfferPayload.grants?.[authorizationCodeGrantIdentifier]) { + const resolvedAuthorizationRequest = await this.agent.modules.openId4VcHolder.resolveIssuanceAuthorizationRequest( + resolvedCredentialOffer, + { + clientId: this.client.clientId, + redirectUri: this.client.redirectUri, + scope: Object.entries(resolvedCredentialOffer.offeredCredentialConfigurations) + .map(([id, value]) => (credentialsToRequest.includes(id) ? value.scope : undefined)) + .filter((v): v is string => Boolean(v)), + } + ) + + if (resolvedAuthorizationRequest.authorizationFlow === OpenId4VciAuthorizationFlow.PresentationDuringIssuance) { + return { + ...resolvedAuthorizationRequest, + authorizationFlow: `${OpenId4VciAuthorizationFlow.PresentationDuringIssuance}`, + } as const + } else { + return { + ...resolvedAuthorizationRequest, + authorizationFlow: `${OpenId4VciAuthorizationFlow.Oauth2Redirect}`, + } as const + } + } + + throw new Error('Unsupported grant type') + } + + public async requestAndStoreCredentials( + resolvedCredentialOffer: OpenId4VciResolvedCredentialOffer, + options: { + clientId?: string + codeVerifier?: string + credentialsToRequest: string[] + code?: string + redirectUri?: string + txCode?: string + } + ) { + const tokenResponse = await this.agent.modules.openId4VcHolder.requestToken( + options.code && options.clientId + ? { + resolvedCredentialOffer, + clientId: options.clientId, + codeVerifier: options.codeVerifier, + code: options.code, + redirectUri: options.redirectUri, + } + : { + resolvedCredentialOffer, + txCode: options.txCode, + } + ) + const credentialResponse = await this.agent.modules.openId4VcHolder.requestCredentials({ resolvedCredentialOffer, + clientId: options.clientId, + credentialConfigurationIds: options.credentialsToRequest, + credentialBindingResolver: async ({ keyTypes, supportedDidMethods, supportsAllDidMethods }) => { + const key = await this.agent.wallet.createKey({ + keyType: keyTypes[0], + }) + + if (supportsAllDidMethods || supportedDidMethods?.includes('did:key')) { + const didKey = new DidKey(key) + + return { + method: 'did', + didUrl: `${didKey.did}#${didKey.key.fingerprint}`, + } + } + if (supportedDidMethods?.includes('did:jwk')) { + const didJwk = DidJwk.fromJwk(getJwkFromKey(key)) + + return { + method: 'did', + didUrl: `${didJwk.did}#0`, + } + } + + // We fall back on jwk binding + return { + method: 'jwk', + jwk: getJwkFromKey(key), + } + }, ...tokenResponse, - // TODO: add jwk support for holder binding - credentialsToRequest, - credentialBindingResolver: async () => ({ - method: 'did', - didUrl: this.verificationMethod.id, - }), }) const storedCredentials = await Promise.all( - credentialResponse.map((response) => { - const credential = response.credential + credentialResponse.credentials.map((response) => { + // TODO: handle batch issuance + const credential = response.credentials[0] if (credential instanceof W3cJwtVerifiableCredential || credential instanceof W3cJsonLdVerifiableCredential) { return this.agent.w3cCredentials.storeCredential({ credential }) } else if (credential instanceof Mdoc) { diff --git a/demo-openid/src/HolderInquirer.ts b/demo-openid/src/HolderInquirer.ts index bd745a7bbf..dc2dee3589 100644 --- a/demo-openid/src/HolderInquirer.ts +++ b/demo-openid/src/HolderInquirer.ts @@ -1,12 +1,16 @@ import type { MdocRecord, SdJwtVcRecord, W3cCredentialRecord } from '@credo-ts/core' -import type { OpenId4VcSiopResolvedAuthorizationRequest, OpenId4VciResolvedCredentialOffer } from '@credo-ts/openid4vc' +import type { + OpenId4VciCredentialConfigurationsSupportedWithFormats, + OpenId4VcSiopResolvedAuthorizationRequest, + OpenId4VciResolvedCredentialOffer, +} from '@credo-ts/openid4vc' import { DifPresentationExchangeService, Mdoc } from '@credo-ts/core' +import { preAuthorizedCodeGrantIdentifier } from '@credo-ts/openid4vc' import console, { clear } from 'console' import { textSync } from 'figlet' -import { prompt } from 'inquirer' -import { BaseInquirer, ConfirmOptions } from './BaseInquirer' +import { BaseInquirer } from './BaseInquirer' import { Holder } from './Holder' import { Title, greenText, redText } from './OutputClass' @@ -19,9 +23,11 @@ export const runHolder = async () => { enum PromptOptions { ResolveCredentialOffer = 'Resolve a credential offer.', + DynamicCredentialRequest = 'Dynamically request issuance of credential from issuer.', RequestCredential = 'Accept the credential offer.', ResolveProofRequest = 'Resolve a proof request.', AcceptPresentationRequest = 'Accept the presentation request.', + AddTrustedCertificate = 'Add trusted certificate', Exit = 'Exit', Restart = 'Restart', } @@ -42,21 +48,29 @@ export class HolderInquirer extends BaseInquirer { } private async getPromptChoice() { - const promptOptions = [PromptOptions.ResolveCredentialOffer, PromptOptions.ResolveProofRequest] + const promptOptions = [ + PromptOptions.ResolveCredentialOffer, + PromptOptions.DynamicCredentialRequest, + PromptOptions.ResolveProofRequest, + PromptOptions.AddTrustedCertificate, + ] - if (this.resolvedCredentialOffer) promptOptions.push(PromptOptions.RequestCredential) - if (this.resolvedPresentationRequest) promptOptions.push(PromptOptions.AcceptPresentationRequest) + if (this.resolvedCredentialOffer) promptOptions.unshift(PromptOptions.RequestCredential) + if (this.resolvedPresentationRequest) promptOptions.unshift(PromptOptions.AcceptPresentationRequest) - return prompt([this.inquireOptions(promptOptions.map((o) => o.valueOf()))]) + return this.pickOne(promptOptions) } public async processAnswer() { const choice = await this.getPromptChoice() - switch (choice.options) { + switch (choice) { case PromptOptions.ResolveCredentialOffer: await this.resolveCredentialOffer() break + case PromptOptions.DynamicCredentialRequest: + await this.dynamicCredentialRequest() + break case PromptOptions.RequestCredential: await this.requestCredential() break @@ -66,6 +80,9 @@ export class HolderInquirer extends BaseInquirer { case PromptOptions.AcceptPresentationRequest: await this.acceptPresentationRequest() break + case PromptOptions.AddTrustedCertificate: + await this.addTrustedCertificate() + break case PromptOptions.Exit: await this.exit() break @@ -77,21 +94,50 @@ export class HolderInquirer extends BaseInquirer { } public async exitUseCase(title: string) { - const confirm = await prompt([this.inquireConfirmation(title)]) - if (confirm.options === ConfirmOptions.No) { - return false - } else if (confirm.options === ConfirmOptions.Yes) { - return true - } + return await this.inquireConfirmation(title) } public async resolveCredentialOffer() { - const credentialOffer = await prompt([this.inquireInput('Enter credential offer: ')]) - const resolvedCredentialOffer = await this.holder.resolveCredentialOffer(credentialOffer.input) + const credentialOffer = await this.inquireInput('Enter credential offer: ') + const resolvedCredentialOffer = await this.holder.resolveCredentialOffer(credentialOffer) this.resolvedCredentialOffer = resolvedCredentialOffer console.log(greenText(`Received credential offer for the following credentials.`)) - console.log(greenText(resolvedCredentialOffer.offeredCredentials.map((credential) => credential.id).join('\n'))) + console.log(greenText(Object.keys(resolvedCredentialOffer.offeredCredentialConfigurations).join('\n'))) + } + + public async dynamicCredentialRequest() { + const credentialOffer = await this.inquireInput('Enter issuer url: ') + const issuerMetadata = await this.holder.resolveIssuerMetadata(credentialOffer) + const configurationsWithScope = Object.entries( + issuerMetadata.credentialIssuer.credential_configurations_supported + ).filter(([, configuration]) => configuration.scope) + + this.resolvedCredentialOffer = { + credentialOfferPayload: { + credential_configuration_ids: configurationsWithScope.map(([id]) => id), + credential_issuer: issuerMetadata.credentialIssuer.credential_issuer, + grants: { + authorization_code: { + authorization_server: issuerMetadata.authorizationServers.find((a) => a.authorization_endpoint)?.issuer, + }, + }, + }, + metadata: issuerMetadata, + offeredCredentialConfigurations: Object.fromEntries( + configurationsWithScope + ) as OpenId4VciCredentialConfigurationsSupportedWithFormats, + } + + console.log(greenText(`We can request authorization for the following credentials.`)) + console.log(greenText(configurationsWithScope.map(([id]) => id).join('\n'))) + } + + public async addTrustedCertificate() { + const trustedCertificate = await this.inquireInput('Enter trusted certificate: ') + await this.holder.agent.x509.addTrustedCertificate(trustedCertificate) + + console.log(greenText(`Added trusted certificate`)) } public async requestCredential() { @@ -99,32 +145,77 @@ export class HolderInquirer extends BaseInquirer { throw new Error('No credential offer resolved yet.') } - const credentialsThatCanBeRequested = this.resolvedCredentialOffer.offeredCredentials.map( - (credential) => credential.id - ) - - const choice = await prompt([this.inquireOptions(credentialsThatCanBeRequested)]) + const credentialsThatCanBeRequested = Object.keys(this.resolvedCredentialOffer.offeredCredentialConfigurations) + const credentialsToRequest = await this.pickMultiple(credentialsThatCanBeRequested) - const credentialToRequest = this.resolvedCredentialOffer.offeredCredentials.find( - (credential) => credential.id === choice.options + const resolvedAuthorization = await this.holder.initiateAuthorization( + this.resolvedCredentialOffer, + credentialsToRequest ) - if (!credentialToRequest) throw new Error('Credential to request not found.') + let authorizationCode: string | undefined = undefined + let codeVerifier: string | undefined = undefined + let txCode: string | undefined = undefined + + if (resolvedAuthorization.authorizationFlow === 'Oauth2Redirect') { + console.log(redText(`Authorization required for credential issuance`, true)) + console.log("Open the following url in your browser to authorize. Once you're done come back here") + console.log(resolvedAuthorization.authorizationRequestUrl) + + const code = new Promise((resolve, reject) => { + this.holder.app.get('/redirect', (req, res) => { + if (req.query.code) { + resolve(req.query.code as string) + // Store original routes + const originalStack = this.holder.app._router.stack + + // Remove specific GET route by path + this.holder.app._router.stack = originalStack.filter( + (layer: { route?: { path: string; methods: { get?: unknown } } }) => + !(layer.route && layer.route.path === '/redirect' && layer.route.methods.get) + ) + res.send('Success! You can now go back to the terminal') + } else { + console.log(redText(`Error during authorization`, true)) + console.log(JSON.stringify(req.query, null, 2)) + res.status(500).send('Error during authentication') + reject() + } + }) + }) + + console.log('\n\n') + codeVerifier = resolvedAuthorization.codeVerifier + authorizationCode = await code + console.log(greenText('Authorization complete', true)) + } else if (resolvedAuthorization.authorizationFlow === 'PresentationDuringIssuance') { + console.log(redText(`Presentation during issuance not supported yet`, true)) + return + } else if (resolvedAuthorization.authorizationFlow === 'PreAuthorized') { + if (this.resolvedCredentialOffer.credentialOfferPayload.grants?.[preAuthorizedCodeGrantIdentifier]?.tx_code) { + txCode = await this.inquireInput('Enter PIN') + } + } - console.log(greenText(`Requesting the following credential '${credentialToRequest.id}'`)) + console.log(greenText(`Requesting the following credential '${credentialsToRequest}'`)) - const credentials = await this.holder.requestAndStoreCredentials( - this.resolvedCredentialOffer, - this.resolvedCredentialOffer.offeredCredentials.map((o) => o.id) - ) + const credentials = await this.holder.requestAndStoreCredentials(this.resolvedCredentialOffer, { + credentialsToRequest, + clientId: authorizationCode ? this.holder.client.clientId : undefined, + codeVerifier, + code: authorizationCode, + redirectUri: authorizationCode ? this.holder.client.redirectUri : undefined, + txCode, + }) + + console.log(greenText(`Received and stored the following credentials.`, true)) + this.resolvedCredentialOffer = undefined - console.log(greenText(`Received and stored the following credentials.`)) - console.log('') credentials.forEach(this.printCredential) } public async resolveProofRequest() { - const proofRequestUri = await prompt([this.inquireInput('Enter proof request: ')]) - this.resolvedPresentationRequest = await this.holder.resolveProofRequest(proofRequestUri.input) + const proofRequestUri = await this.inquireInput('Enter proof request: ') + this.resolvedPresentationRequest = await this.holder.resolveProofRequest(proofRequestUri) const presentationDefinition = this.resolvedPresentationRequest?.presentationExchange?.definition console.log(greenText(`Presentation Purpose: '${presentationDefinition?.purpose}'`)) @@ -159,25 +250,23 @@ export class HolderInquirer extends BaseInquirer { } else { console.log(`received error status code '${serverResponse.status}'. ${JSON.stringify(serverResponse.body)}`) } + + this.resolvedPresentationRequest = undefined } public async exit() { - const confirm = await prompt([this.inquireConfirmation(Title.ConfirmTitle)]) - if (confirm.options === ConfirmOptions.No) { - return - } else if (confirm.options === ConfirmOptions.Yes) { + if (await this.inquireConfirmation(Title.ConfirmTitle)) { await this.holder.exit() } } public async restart() { - const confirm = await prompt([this.inquireConfirmation(Title.ConfirmTitle)]) - if (confirm.options === ConfirmOptions.No) { - await this.processAnswer() - return - } else if (confirm.options === ConfirmOptions.Yes) { + const confirmed = await this.inquireConfirmation(Title.ConfirmTitle) + if (confirmed) { await this.holder.restart() await runHolder() + } else { + await this.processAnswer() } } diff --git a/demo-openid/src/Issuer.ts b/demo-openid/src/Issuer.ts index 154a094670..87d3d13df0 100644 --- a/demo-openid/src/Issuer.ts +++ b/demo-openid/src/Issuer.ts @@ -2,10 +2,13 @@ import type { DidKey } from '@credo-ts/core' import type { OpenId4VcCredentialHolderBinding, OpenId4VcCredentialHolderDidBinding, + OpenId4VciCredentialConfigurationsSupportedWithFormats, OpenId4VciCredentialRequestToCredentialMapper, - OpenId4VciCredentialSupportedWithId, - OpenId4VciSignMdocCredential, + OpenId4VciSignMdocCredentials, + OpenId4VciSignSdJwtCredentials, + OpenId4VciSignW3cCredentials, OpenId4VcIssuerRecord, + OpenId4VcVerifierRecord, } from '@credo-ts/openid4vc' import { AskarModule } from '@credo-ts/askar' @@ -20,124 +23,167 @@ import { X509Service, KeyType, X509ModuleConfig, + utils, + TypedArrayEncoder, + JsonTransformer, } from '@credo-ts/core' -import { OpenId4VcIssuerModule, OpenId4VciCredentialFormatProfile } from '@credo-ts/openid4vc' +import { + OpenId4VcIssuerModule, + OpenId4VcVerifierApi, + OpenId4VcVerifierModule, + OpenId4VciCredentialFormatProfile, +} from '@credo-ts/openid4vc' import { ariesAskar } from '@hyperledger/aries-askar-nodejs' import { Router } from 'express' import { BaseAgent } from './BaseAgent' import { Output } from './OutputClass' -export const universityDegreeCredential = { - id: 'UniversityDegreeCredential', - format: OpenId4VciCredentialFormatProfile.JwtVcJson, - types: ['VerifiableCredential', 'UniversityDegreeCredential'], -} satisfies OpenId4VciCredentialSupportedWithId - -export const openBadgeCredential = { - id: 'OpenBadgeCredential', - format: OpenId4VciCredentialFormatProfile.JwtVcJson, - types: ['VerifiableCredential', 'OpenBadgeCredential'], -} satisfies OpenId4VciCredentialSupportedWithId - -export const universityDegreeCredentialSdJwt = { - id: 'UniversityDegreeCredential-sdjwt', - format: OpenId4VciCredentialFormatProfile.SdJwtVc, - vct: 'UniversityDegreeCredential', -} satisfies OpenId4VciCredentialSupportedWithId - -export const universityDegreeCredentialMdoc = { - id: 'UniversityDegreeCredential-mdoc', - format: OpenId4VciCredentialFormatProfile.MsoMdoc, - doctype: 'UniversityDegreeCredential', -} satisfies OpenId4VciCredentialSupportedWithId - -export const credentialsSupported = [ - universityDegreeCredential, - openBadgeCredential, - universityDegreeCredentialSdJwt, - universityDegreeCredentialMdoc, -] satisfies OpenId4VciCredentialSupportedWithId[] +const PROVIDER_HOST = process.env.PROVIDER_HOST ?? 'http://localhost:3042' +const ISSUER_HOST = process.env.ISSUER_HOST ?? 'http://localhost:2000' + +export const credentialConfigurationsSupported = { + PresentationAuthorization: { + format: OpenId4VciCredentialFormatProfile.SdJwtVc, + vct: 'PresentationAuthorization', + scope: 'openid4vc:credential:PresentationAuthorization', + cryptographic_binding_methods_supported: ['jwk', 'did:key', 'did:jwk'], + credential_signing_alg_values_supported: ['ES256', 'EdDSA'], + }, + 'UniversityDegreeCredential-jwtvcjson': { + format: OpenId4VciCredentialFormatProfile.JwtVcJson, + scope: 'openid4vc:credential:UniversityDegreeCredential-jwtvcjson', + // TODO: we should validate this against what is supported by credo + // as otherwise it's very easy to create invalid configurations? + cryptographic_binding_methods_supported: ['did:key', 'did:jwk'], + credential_signing_alg_values_supported: ['ES256', 'EdDSA'], + credential_definition: { + type: ['VerifiableCredential', 'UniversityDegreeCredential'], + }, + }, + 'UniversityDegreeCredential-sdjwt': { + format: OpenId4VciCredentialFormatProfile.SdJwtVc, + vct: 'UniversityDegreeCredential', + scope: 'openid4vc:credential:OpenBadgeCredential-sdjwt', + cryptographic_binding_methods_supported: ['jwk'], + credential_signing_alg_values_supported: ['ES256', 'EdDSA'], + }, + 'UniversityDegreeCredential-mdoc': { + format: OpenId4VciCredentialFormatProfile.MsoMdoc, + doctype: 'UniversityDegreeCredential', + scope: 'openid4vc:credential:OpenBadgeCredential-mdoc', + cryptographic_binding_methods_supported: ['jwk'], + credential_signing_alg_values_supported: ['ES256', 'EdDSA'], + }, +} satisfies OpenId4VciCredentialConfigurationsSupportedWithFormats function getCredentialRequestToCredentialMapper({ issuerDidKey, }: { issuerDidKey: DidKey }): OpenId4VciCredentialRequestToCredentialMapper { - return async ({ holderBinding, credentialConfigurationIds, agentContext }) => { + return async ({ + holderBindings, + credentialConfigurationIds, + credentialConfigurationsSupported: supported, + agentContext, + authorization, + }) => { const trustedCertificates = agentContext.dependencyManager.resolve(X509ModuleConfig).trustedCertificates if (trustedCertificates?.length !== 1) { throw new Error(`Expected exactly one trusted certificate. Received ${trustedCertificates?.length}.`) } - const credentialConfigurationId = credentialConfigurationIds[0] - if (credentialConfigurationId === universityDegreeCredential.id) { - assertDidBasedHolderBinding(holderBinding) + const credentialConfigurationId = credentialConfigurationIds[0] + const credentialConfiguration = supported[credentialConfigurationId] + if (credentialConfigurationId === 'PresentationAuthorization') { return { - credentialSupportedId: universityDegreeCredential.id, - format: ClaimFormat.JwtVc, - credential: new W3cCredential({ - type: universityDegreeCredential.types, - issuer: new W3cIssuer({ - id: issuerDidKey.did, - }), - credentialSubject: new W3cCredentialSubject({ - id: parseDid(holderBinding.didUrl).did, - }), - issuanceDate: w3cDate(Date.now()), - }), - verificationMethod: `${issuerDidKey.did}#${issuerDidKey.key.fingerprint}`, - } + credentialConfigurationId, + format: ClaimFormat.SdJwtVc, + credentials: holderBindings.map((holderBinding) => ({ + payload: { + vct: credentialConfiguration.vct, + authorized_user: authorization.accessToken.payload.sub, + }, + holder: holderBinding, + issuer: + holderBindings[0].method === 'did' + ? { + method: 'did', + didUrl: `${issuerDidKey.did}#${issuerDidKey.key.fingerprint}`, + } + : { method: 'x5c', x5c: [trustedCertificates[0]], issuer: ISSUER_HOST }, + })), + } satisfies OpenId4VciSignSdJwtCredentials } - if (credentialConfigurationId === openBadgeCredential.id) { - assertDidBasedHolderBinding(holderBinding) + if (credentialConfiguration.format === OpenId4VciCredentialFormatProfile.JwtVcJson) { + holderBindings.forEach((holderBinding) => assertDidBasedHolderBinding(holderBinding)) return { + credentialConfigurationId, format: ClaimFormat.JwtVc, - credentialSupportedId: openBadgeCredential.id, - credential: new W3cCredential({ - type: openBadgeCredential.types, - issuer: new W3cIssuer({ - id: issuerDidKey.did, - }), - credentialSubject: new W3cCredentialSubject({ - id: parseDid(holderBinding.didUrl).did, - }), - issuanceDate: w3cDate(Date.now()), + credentials: holderBindings.map((holderBinding) => { + assertDidBasedHolderBinding(holderBinding) + return { + credential: new W3cCredential({ + type: credentialConfiguration.credential_definition.type, + issuer: new W3cIssuer({ + id: issuerDidKey.did, + }), + credentialSubject: JsonTransformer.fromJSON( + { + id: parseDid(holderBinding.didUrl).did, + authorizedUser: authorization.accessToken.payload.sub, + }, + W3cCredentialSubject + ), + issuanceDate: w3cDate(Date.now()), + }), + verificationMethod: `${issuerDidKey.did}#${issuerDidKey.key.fingerprint}`, + } }), - verificationMethod: `${issuerDidKey.did}#${issuerDidKey.key.fingerprint}`, - } + } satisfies OpenId4VciSignW3cCredentials } - if (credentialConfigurationId === universityDegreeCredentialSdJwt.id) { + if (credentialConfiguration.format === OpenId4VciCredentialFormatProfile.SdJwtVc) { return { - credentialSupportedId: universityDegreeCredentialSdJwt.id, + credentialConfigurationId, format: ClaimFormat.SdJwtVc, - payload: { vct: universityDegreeCredentialSdJwt.vct, university: 'innsbruck', degree: 'bachelor' }, - holder: holderBinding, - issuer: { - method: 'did', - didUrl: `${issuerDidKey.did}#${issuerDidKey.key.fingerprint}`, - }, - disclosureFrame: { _sd: ['university', 'degree'] }, - } + credentials: holderBindings.map((holderBinding) => ({ + payload: { + vct: credentialConfiguration.vct, + university: 'innsbruck', + degree: 'bachelor', + authorized_user: authorization.accessToken.payload.sub, + }, + holder: holderBinding, + issuer: { + method: 'did', + didUrl: `${issuerDidKey.did}#${issuerDidKey.key.fingerprint}`, + }, + disclosureFrame: { _sd: ['university', 'degree', 'authorized_user'] }, + })), + } satisfies OpenId4VciSignSdJwtCredentials } - if (credentialConfigurationId === universityDegreeCredentialMdoc.id) { + if (credentialConfiguration.format === OpenId4VciCredentialFormatProfile.MsoMdoc) { return { - credentialSupportedId: universityDegreeCredentialMdoc.id, + credentialConfigurationId, format: ClaimFormat.MsoMdoc, - docType: universityDegreeCredentialMdoc.doctype, - issuerCertificate: trustedCertificates[0], - holderKey: holderBinding.key, - namespaces: { - 'Leopold-Franzens-University': { - degree: 'bachelor', + credentials: holderBindings.map((holderBinding) => ({ + issuerCertificate: trustedCertificates[0], + holderKey: holderBinding.key, + namespaces: { + 'Leopold-Franzens-University': { + degree: 'bachelor', + authorized_user: authorization.accessToken.payload.sub, + }, }, - }, - } satisfies OpenId4VciSignMdocCredential + docType: credentialConfiguration.doctype, + })), + } satisfies OpenId4VciSignMdocCredentials } throw new Error('Invalid request') @@ -147,70 +193,154 @@ function getCredentialRequestToCredentialMapper({ export class Issuer extends BaseAgent<{ askar: AskarModule openId4VcIssuer: OpenId4VcIssuerModule + openId4VcVerifier: OpenId4VcVerifierModule }> { public issuerRecord!: OpenId4VcIssuerRecord + public verifierRecord!: OpenId4VcVerifierRecord - public constructor(port: number, name: string) { + public constructor(url: string, port: number, name: string) { const openId4VciRouter = Router() + const openId4VpRouter = Router() super({ port, name, modules: { askar: new AskarModule({ ariesAskar }), + openId4VcVerifier: new OpenId4VcVerifierModule({ + baseUrl: `${url}/oid4vp`, + router: openId4VpRouter, + }), openId4VcIssuer: new OpenId4VcIssuerModule({ - baseUrl: 'http://localhost:2000/oid4vci', + baseUrl: `${url}/oid4vci`, router: openId4VciRouter, - endpoints: { - credential: { - credentialRequestToCredentialMapper: (...args) => - getCredentialRequestToCredentialMapper({ issuerDidKey: this.didKey })(...args), - }, + credentialRequestToCredentialMapper: (...args) => + getCredentialRequestToCredentialMapper({ issuerDidKey: this.didKey })(...args), + getVerificationSessionForIssuanceSessionAuthorization: async ({ agentContext, scopes }) => { + const verifierApi = agentContext.dependencyManager.resolve(OpenId4VcVerifierApi) + const authorizationRequest = await verifierApi.createAuthorizationRequest({ + verifierId: this.verifierRecord.verifierId, + requestSigner: { + method: 'did', + didUrl: `${this.didKey.did}#${this.didKey.key.fingerprint}`, + }, + responseMode: 'direct_post.jwt', + presentationExchange: { + definition: { + id: '18e2c9c3-1722-4393-a558-f0ce1e32c4ec', + input_descriptors: [ + { + id: '16f00df5-67f1-47e6-81b1-bd3e3743f84c', + constraints: { + fields: [ + { + path: ['$.vct'], + filter: { + type: 'string', + const: credentialConfigurationsSupported.PresentationAuthorization.vct, + }, + }, + ], + }, + }, + ], + name: 'Presentation Authorization', + purpose: `To issue the requested credentials, we need to verify your 'Presentation Authorization' credential`, + }, + }, + }) + + return { + scopes, + ...authorizationRequest, + } }, }), }, }) this.app.use('/oid4vci', openId4VciRouter) + this.app.use('/oid4vp', openId4VpRouter) } public static async build(): Promise { - const issuer = new Issuer(2000, 'OpenId4VcIssuer ' + Math.random().toString()) + const issuer = new Issuer(ISSUER_HOST, 2000, 'OpenId4VcIssuer ' + Math.random().toString()) await issuer.initializeAgent('96213c3d7fc8d4d6754c7a0fd969598f') - const currentDate = new Date() - currentDate.setDate(currentDate.getDate() - 1) - const nextDay = new Date(currentDate) - nextDay.setDate(currentDate.getDate() + 2) - const selfSignedCertificate = await X509Service.createSelfSignedCertificate(issuer.agent.context, { - key: await issuer.agent.context.wallet.createKey({ keyType: KeyType.P256 }), - notBefore: currentDate, - notAfter: nextDay, - extensions: [], + key: await issuer.agent.context.wallet.createKey({ + keyType: KeyType.P256, + seed: TypedArrayEncoder.fromString('e5f18b10cd15cdb76818bc6ae8b71eb475e6eac76875ed085d3962239bbcf42f'), + }), + notBefore: new Date('2000-01-01'), + notAfter: new Date('2050-01-01'), + extensions: [[{ type: 'dns', value: ISSUER_HOST.replace('https://', '').replace('http://', '') }]], name: 'C=DE', }) - const issuerCertficicate = selfSignedCertificate.toString('pem') + const issuerCertficicate = selfSignedCertificate.toString('base64url') await issuer.agent.x509.setTrustedCertificates([issuerCertficicate]) console.log('Set the following certficate for the holder to verify mdoc credentials.') console.log(issuerCertficicate) + issuer.verifierRecord = await issuer.agent.modules.openId4VcVerifier.createVerifier({ + verifierId: '726222ad-7624-4f12-b15b-e08aa7042ffa', + }) issuer.issuerRecord = await issuer.agent.modules.openId4VcIssuer.createIssuer({ - credentialsSupported, + issuerId: '726222ad-7624-4f12-b15b-e08aa7042ffa', + credentialConfigurationsSupported, + authorizationServerConfigs: [ + { + issuer: PROVIDER_HOST, + clientAuthentication: { + clientId: 'issuer-server', + clientSecret: 'issuer-server', + }, + }, + ], }) + const issuerMetadata = await issuer.agent.modules.openId4VcIssuer.getIssuerMetadata(issuer.issuerRecord.issuerId) + console.log(`\nIssuer url is ${issuerMetadata.credentialIssuer.credential_issuer}`) + return issuer } - public async createCredentialOffer(offeredCredentials: string[]) { - const { credentialOffer } = await this.agent.modules.openId4VcIssuer.createCredentialOffer({ + public async createCredentialOffer(options: { + credentialConfigurationIds: string[] + requireAuthorization?: 'presentation' | 'browser' + requirePin: boolean + }) { + const issuerMetadata = await this.agent.modules.openId4VcIssuer.getIssuerMetadata(this.issuerRecord.issuerId) + + const { credentialOffer, issuanceSession } = await this.agent.modules.openId4VcIssuer.createCredentialOffer({ issuerId: this.issuerRecord.issuerId, - offeredCredentials, - preAuthorizedCodeFlowConfig: { userPinRequired: false }, + offeredCredentials: options.credentialConfigurationIds, + // Pre-auth using our own server + preAuthorizedCodeFlowConfig: !options.requireAuthorization + ? { + authorizationServerUrl: issuerMetadata.credentialIssuer.credential_issuer, + txCode: options.requirePin + ? { + input_mode: 'numeric', + length: 4, + description: 'Pin has been printed to the terminal', + } + : undefined, + } + : undefined, + // Auth using external authorization server + authorizationCodeFlowConfig: options.requireAuthorization + ? { + authorizationServerUrl: options.requireAuthorization === 'browser' ? PROVIDER_HOST : undefined, + // TODO: should be generated by us, if we're going to use for matching + issuerState: utils.uuid(), + requirePresentationDuringIssuance: options.requireAuthorization === 'presentation', + } + : undefined, }) - return credentialOffer + return { credentialOffer, issuanceSession } } public async exit() { diff --git a/demo-openid/src/IssuerInquirer.ts b/demo-openid/src/IssuerInquirer.ts index dca38ed86a..5bcd4e8eda 100644 --- a/demo-openid/src/IssuerInquirer.ts +++ b/demo-openid/src/IssuerInquirer.ts @@ -1,10 +1,9 @@ import { clear } from 'console' import { textSync } from 'figlet' -import { prompt } from 'inquirer' -import { BaseInquirer, ConfirmOptions } from './BaseInquirer' -import { Issuer, credentialsSupported } from './Issuer' -import { Title, purpleText } from './OutputClass' +import { BaseInquirer } from './BaseInquirer' +import { credentialConfigurationsSupported, Issuer } from './Issuer' +import { Title, greenText, purpleText, redText } from './OutputClass' export const runIssuer = async () => { clear() @@ -21,12 +20,10 @@ enum PromptOptions { export class IssuerInquirer extends BaseInquirer { public issuer: Issuer - public promptOptionsString: string[] public constructor(issuer: Issuer) { super() this.issuer = issuer - this.promptOptionsString = Object.values(PromptOptions) } public static async build(): Promise { @@ -34,14 +31,10 @@ export class IssuerInquirer extends BaseInquirer { return new IssuerInquirer(issuer) } - private async getPromptChoice() { - return prompt([this.inquireOptions(this.promptOptionsString)]) - } - public async processAnswer() { - const choice = await this.getPromptChoice() + const choice = await this.pickOne(Object.values(PromptOptions)) - switch (choice.options) { + switch (choice) { case PromptOptions.CreateCredentialOffer: await this.createCredentialOffer() break @@ -56,31 +49,47 @@ export class IssuerInquirer extends BaseInquirer { } public async createCredentialOffer() { - const choice = await prompt([this.inquireOptions(credentialsSupported.map((credential) => credential.id))]) - const offeredCredential = credentialsSupported.find((credential) => credential.id === choice.options) - if (!offeredCredential) throw new Error(`No credential of type ${choice.options} found, that can be offered.`) - const offerRequest = await this.issuer.createCredentialOffer([offeredCredential.id]) + let credentialConfigurationIds = await this.pickMultiple(Object.keys(credentialConfigurationsSupported)) + while (credentialConfigurationIds.length === 0) { + console.log(redText('Pick at least one', true)) + credentialConfigurationIds = await this.pickMultiple(Object.keys(credentialConfigurationsSupported)) + } + + const authorizationMethod = await this.pickOne( + ['Transaction Code', 'Browser', 'Presentation', 'None'], + 'Authorization method' + ) + const { credentialOffer, issuanceSession } = await this.issuer.createCredentialOffer({ + credentialConfigurationIds, + requireAuthorization: + authorizationMethod === 'Browser' + ? 'browser' + : authorizationMethod === 'Presentation' + ? 'presentation' + : undefined, + requirePin: authorizationMethod === 'Transaction Code', + }) - console.log(purpleText(`credential offer: '${offerRequest}'`)) + console.log(purpleText(`credential offer: '${credentialOffer}'`, true)) + + if (issuanceSession.userPin) { + console.log(greenText(`\nEnter PIN ${issuanceSession.userPin} when asked`, true)) + } } public async exit() { - const confirm = await prompt([this.inquireConfirmation(Title.ConfirmTitle)]) - if (confirm.options === ConfirmOptions.No) { - return - } else if (confirm.options === ConfirmOptions.Yes) { + if (await this.inquireConfirmation(Title.ConfirmTitle)) { await this.issuer.exit() } } public async restart() { - const confirm = await prompt([this.inquireConfirmation(Title.ConfirmTitle)]) - if (confirm.options === ConfirmOptions.No) { - await this.processAnswer() - return - } else if (confirm.options === ConfirmOptions.Yes) { + const confirmed = await this.inquireConfirmation(Title.ConfirmTitle) + if (confirmed) { await this.issuer.restart() await runIssuer() + } else { + await this.processAnswer() } } } diff --git a/demo-openid/src/Provider.ts b/demo-openid/src/Provider.ts new file mode 100644 index 0000000000..002986b3e3 --- /dev/null +++ b/demo-openid/src/Provider.ts @@ -0,0 +1,139 @@ +import { bodyParser } from '@koa/bodyparser' +import { Provider } from 'oidc-provider' + +// I can't figure out how to bind a custom request parameter to the session +// so it can be bound to the access token. This is a very hacky 'global' issuer_state +// and only works if only person is authenticating. Of course very unsecure, but it's a demo +let issuer_state: string | undefined = undefined + +const PROVIDER_HOST = process.env.PROVIDER_HOST ?? 'http://localhost:3042' +const ISSUER_HOST = process.env.ISSUER_HOST ?? 'http://localhost:2000' + +const oidc = new Provider(PROVIDER_HOST, { + clientAuthMethods: ['client_secret_basic', 'client_secret_post', 'none'], + clients: [ + { + client_id: 'wallet', + client_secret: 'wallet', + grant_types: ['authorization_code'], + id_token_signed_response_alg: 'ES256', + redirect_uris: [], + application_type: 'native', + }, + { + client_id: 'issuer-server', + client_secret: 'issuer-server', + id_token_signed_response_alg: 'ES256', + redirect_uris: [], + }, + ], + jwks: { + keys: [ + { + alg: 'ES256', + kid: 'first-key', + kty: 'EC', + d: '2hdTKWEZza_R-DF4l3aoWEuGZPy6L6PGmUT_GqeJczM', + crv: 'P-256', + x: '73lW9QyiXTvpOOXuT_LoRRvM3oEWKSLyzfNGe04sV5k', + y: 'AiFefLdnP-cWkdsevwozKdxNGvF_VSSZ1K5yDQ4jWwM', + }, + ], + }, + scopes: [], + pkce: { + methods: ['S256'], + required: () => true, + }, + extraTokenClaims: async (context, token) => { + if (token.kind === 'AccessToken') { + return { + issuer_state, + } + } + return undefined + }, + clientBasedCORS: () => true, + extraParams: { + issuer_state: (_, value) => { + issuer_state = value + }, + }, + features: { + dPoP: { enabled: true }, + pushedAuthorizationRequests: { + enabled: true, + requirePushedAuthorizationRequests: true, + allowUnregisteredRedirectUris: true, + }, + introspection: { + enabled: true, + }, + resourceIndicators: { + defaultResource: () => `${ISSUER_HOST}/oid4vci/726222ad-7624-4f12-b15b-e08aa7042ffa`, + enabled: true, + getResourceServerInfo: (context) => { + return { + scope: Array.from(context.oidc.requestParamScopes).join(' '), + accessTokenTTL: 5 * 60, // 5 minutes + + // NOTE: switch this between opaque and jwt to use JWT tokens or Token introspection + accessTokenFormat: 'jwt', + audience: `${ISSUER_HOST}/oid4vci/726222ad-7624-4f12-b15b-e08aa7042ffa`, + jwt: { + sign: { + kid: 'first-key', + alg: 'ES256', + }, + }, + } + }, + useGrantedResource: () => { + return true + }, + }, + }, + + async findAccount(_, id) { + return { + accountId: id, + async claims() { + return { sub: id } + }, + } + }, +}) +oidc.proxy = true + +oidc.use(bodyParser()) +oidc.use(async (ctx, next) => { + if (!ctx.path.startsWith('/interaction') && !ctx.path.startsWith('/.well-known/')) { + console.log( + 'Request', + JSON.stringify({ path: ctx.path, body: ctx.request.body, headers: ctx.request.headers }, null, 2) + ) + } + + // We hack the client secret (to allow public client with unregistered redirect uri) + if (ctx.path === '/request' || ctx.path === '/token') { + ctx.request.body.client_id = 'wallet' + ctx.request.body.client_secret = 'wallet' + } + + await next() + + if (!ctx.path.startsWith('/interaction') && !ctx.path.startsWith('/.well-known/')) { + console.log( + 'Response', + JSON.stringify( + { path: ctx.path, body: ctx.response.body, status: ctx.response.status, headers: ctx.response.headers }, + null, + 2 + ) + ) + } +}) + +oidc.listen(3042, () => { + console.log(`oidc-provider listening on port 3042, check ${PROVIDER_HOST}/.well-known/openid-configuration`) +}) diff --git a/demo-openid/src/Verifier.ts b/demo-openid/src/Verifier.ts index 8382b8fb25..d7d31f0f34 100644 --- a/demo-openid/src/Verifier.ts +++ b/demo-openid/src/Verifier.ts @@ -9,6 +9,8 @@ import { Router } from 'express' import { BaseAgent } from './BaseAgent' import { Output } from './OutputClass' +const VERIFIER_HOST = process.env.VERIFIER_HOST ?? 'http://localhost:4000' + const universityDegreePresentationDefinition = { id: 'UniversityDegreeCredential', purpose: 'Present your UniversityDegreeCredential to verify your education level.', @@ -61,7 +63,7 @@ export const presentationDefinitions = [ export class Verifier extends BaseAgent<{ askar: AskarModule; openId4VcVerifier: OpenId4VcVerifierModule }> { public verifierRecord!: OpenId4VcVerifierRecord - public constructor(port: number, name: string) { + public constructor(url: string, port: number, name: string) { const openId4VcSiopRouter = Router() super({ @@ -70,7 +72,7 @@ export class Verifier extends BaseAgent<{ askar: AskarModule; openId4VcVerifier: modules: { askar: new AskarModule({ ariesAskar }), openId4VcVerifier: new OpenId4VcVerifierModule({ - baseUrl: 'http://localhost:4000/siop', + baseUrl: `${url}/siop`, router: openId4VcSiopRouter, }), }, @@ -80,7 +82,7 @@ export class Verifier extends BaseAgent<{ askar: AskarModule; openId4VcVerifier: } public static async build(): Promise { - const verifier = new Verifier(4000, 'OpenId4VcVerifier ' + Math.random().toString()) + const verifier = new Verifier(VERIFIER_HOST, 4000, 'OpenId4VcVerifier ' + Math.random().toString()) await verifier.initializeAgent('96213c3d7fc8d4d6754c7a0fd969598g') verifier.verifierRecord = await verifier.agent.modules.openId4VcVerifier.createVerifier() diff --git a/demo-openid/src/VerifierInquirer.ts b/demo-openid/src/VerifierInquirer.ts index 8877242fb6..96fa61c68b 100644 --- a/demo-openid/src/VerifierInquirer.ts +++ b/demo-openid/src/VerifierInquirer.ts @@ -1,8 +1,7 @@ import { clear } from 'console' import { textSync } from 'figlet' -import { prompt } from 'inquirer' -import { BaseInquirer, ConfirmOptions } from './BaseInquirer' +import { BaseInquirer } from './BaseInquirer' import { Title, purpleText } from './OutputClass' import { Verifier, presentationDefinitions } from './Verifier' @@ -21,12 +20,10 @@ enum PromptOptions { export class VerifierInquirer extends BaseInquirer { public verifier: Verifier - public promptOptionsString: string[] public constructor(verifier: Verifier) { super() this.verifier = verifier - this.promptOptionsString = Object.values(PromptOptions) } public static async build(): Promise { @@ -34,14 +31,10 @@ export class VerifierInquirer extends BaseInquirer { return new VerifierInquirer(verifier) } - private async getPromptChoice() { - return prompt([this.inquireOptions(this.promptOptionsString)]) - } - public async processAnswer() { - const choice = await this.getPromptChoice() + const choice = await this.pickOne(Object.values(PromptOptions)) - switch (choice.options) { + switch (choice) { case PromptOptions.CreateProofOffer: await this.createProofRequest() break @@ -56,32 +49,28 @@ export class VerifierInquirer extends BaseInquirer { } public async createProofRequest() { - const choice = await prompt([this.inquireOptions(presentationDefinitions.map((p) => p.id))]) - const presentationDefinition = presentationDefinitions.find((p) => p.id === choice.options) + const presentationDefinitionId = await this.pickOne(presentationDefinitions.map((p) => p.id)) + const presentationDefinition = presentationDefinitions.find((p) => p.id === presentationDefinitionId) if (!presentationDefinition) throw new Error('No presentation definition found') const proofRequest = await this.verifier.createProofRequest(presentationDefinition) - console.log(purpleText(`Proof request for the presentation of an ${choice.options}.\n'${proofRequest}'`)) + console.log(purpleText(`Proof request for the presentation of an ${presentationDefinitionId}.\n'${proofRequest}'`)) } public async exit() { - const confirm = await prompt([this.inquireConfirmation(Title.ConfirmTitle)]) - if (confirm.options === ConfirmOptions.No) { - return - } else if (confirm.options === ConfirmOptions.Yes) { + if (await this.inquireConfirmation(Title.ConfirmTitle)) { await this.verifier.exit() } } public async restart() { - const confirm = await prompt([this.inquireConfirmation(Title.ConfirmTitle)]) - if (confirm.options === ConfirmOptions.No) { - await this.processAnswer() - return - } else if (confirm.options === ConfirmOptions.Yes) { + const confirmed = await this.inquireConfirmation(Title.ConfirmTitle) + if (confirmed) { await this.verifier.restart() await runVerifier() + } else { + await this.processAnswer() } } } diff --git a/demo/package.json b/demo/package.json index e2d867acd5..fa654da196 100644 --- a/demo/package.json +++ b/demo/package.json @@ -29,6 +29,6 @@ "@types/inquirer": "^8.2.6", "clear": "^0.1.0", "figlet": "^1.5.2", - "ts-node": "^10.4.0" + "ts-node": "^10.9.2" } } diff --git a/jest.config.base.ts b/jest.config.base.ts index 5dfdf49c1f..43b6b40302 100644 --- a/jest.config.base.ts +++ b/jest.config.base.ts @@ -10,7 +10,7 @@ const config: Config.InitialOptions = { '@credo-ts/(.+)': ['/../../packages/$1/src', '/../packages/$1/src', '/packages/$1/src'], }, transform: { - '^.+\\.tsx?$': [ + '\\.tsx?$': [ 'ts-jest', { isolatedModules: true, diff --git a/package.json b/package.json index 1b2f1cd497..c041195c18 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,8 @@ "release": "pnpm build && pnpm changeset publish --no-git-tag" }, "devDependencies": { + "@babel/core": "^7.25.8", + "@babel/preset-env": "^7.25.8", "@changesets/cli": "^2.27.5", "@hyperledger/aries-askar-nodejs": "^0.2.3", "@jest/types": "^29.6.3", @@ -40,6 +42,7 @@ "@types/express": "^4.17.13", "@types/jest": "^29.5.12", "@types/node": "^18.18.8", + "@types/supertest": "^6.0.2", "@types/uuid": "^9.0.1", "@types/varint": "^6.0.0", "@types/ws": "^8.5.4", @@ -55,16 +58,20 @@ "eslint-plugin-workspaces": "^0.8.0", "express": "^4.17.1", "jest": "^29.7.0", + "nock": "^14.0.0-beta.15", "prettier": "^2.3.1", "rxjs": "^7.8.0", + "supertest": "^7.0.0", "ts-jest": "^29.1.2", - "ts-node": "^10.0.0", + "ts-node": "^10.9.2", "tsyringe": "^4.8.0", "typescript": "~5.5.2", + "undici": "^6.20.1", "ws": "^8.13.0" }, "resolutions": { - "@types/node": "18.18.8" + "@types/node": "18.18.8", + "undici": "^6.20.1" }, "engines": { "node": ">=18" diff --git a/packages/core/package.json b/packages/core/package.json index 234f5ae7e5..e01eb2b583 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -75,7 +75,6 @@ "@types/object-inspect": "^1.8.0", "@types/uuid": "^9.0.1", "@types/varint": "^6.0.0", - "nock": "^13.3.0", "rimraf": "^4.4.0", "tslog": "^4.8.2", "typescript": "~5.5.2" diff --git a/packages/core/src/agent/AgentConfig.ts b/packages/core/src/agent/AgentConfig.ts index b7e737212d..fc160fd901 100644 --- a/packages/core/src/agent/AgentConfig.ts +++ b/packages/core/src/agent/AgentConfig.ts @@ -32,6 +32,10 @@ export class AgentConfig { return this.initConfig.didCommMimeType ?? DidCommMimeType.V1 } + public get allowInsecureHttpUrls() { + return this.initConfig.allowInsecureHttpUrls ?? false + } + /** * Encode keys in did:key format instead of 'naked' keys, as stated in Aries RFC 0360. * diff --git a/packages/core/src/agent/AgentDependencies.ts b/packages/core/src/agent/AgentDependencies.ts index 15ba4da53b..af916b1d6e 100644 --- a/packages/core/src/agent/AgentDependencies.ts +++ b/packages/core/src/agent/AgentDependencies.ts @@ -1,5 +1,6 @@ import type { FileSystem } from '../storage/FileSystem' import type { EventEmitter } from 'events' +// eslint-disable-next-line import/no-named-as-default import type WebSocket from 'ws' export interface AgentDependencies { diff --git a/packages/core/src/agent/context/AgentContext.ts b/packages/core/src/agent/context/AgentContext.ts index 73a857d518..fd501dc5d1 100644 --- a/packages/core/src/agent/context/AgentContext.ts +++ b/packages/core/src/agent/context/AgentContext.ts @@ -52,6 +52,9 @@ export class AgentContext { * End session the current agent context */ public async endSession() { + // TODO: we need to create a custom agent context per sesion + // and then track if it has already been ended, because it's quite + // easy to mess up the session count at the moment const agentContextProvider = this.dependencyManager.resolve( InjectionSymbols.AgentContextProvider ) diff --git a/packages/core/src/modules/dif-presentation-exchange/index.ts b/packages/core/src/modules/dif-presentation-exchange/index.ts index 4f4e4b3923..5006a03120 100644 --- a/packages/core/src/modules/dif-presentation-exchange/index.ts +++ b/packages/core/src/modules/dif-presentation-exchange/index.ts @@ -2,3 +2,5 @@ export * from './DifPresentationExchangeError' export * from './DifPresentationExchangeModule' export * from './DifPresentationExchangeService' export * from './models' +export { extractPresentationsWithDescriptorsFromSubmission } from './utils' +export type { DifPexPresentationWithDescriptor } from './utils' diff --git a/packages/core/src/modules/dif-presentation-exchange/utils/index.ts b/packages/core/src/modules/dif-presentation-exchange/utils/index.ts index 18fe3ad53c..d5afd79158 100644 --- a/packages/core/src/modules/dif-presentation-exchange/utils/index.ts +++ b/packages/core/src/modules/dif-presentation-exchange/utils/index.ts @@ -1,3 +1,4 @@ export * from './transform' export * from './credentialSelection' export * from './presentationsToCreate' +export * from './presentationSelection' diff --git a/packages/core/src/modules/dif-presentation-exchange/utils/presentationSelection.ts b/packages/core/src/modules/dif-presentation-exchange/utils/presentationSelection.ts new file mode 100644 index 0000000000..b223fcd65b --- /dev/null +++ b/packages/core/src/modules/dif-presentation-exchange/utils/presentationSelection.ts @@ -0,0 +1,93 @@ +import type { SingleOrArray } from '../../../utils' +import type { W3cJsonLdVerifiableCredential, W3cJwtVerifiableCredential } from '../../vc' +import type { + DifPresentationExchangeDefinition, + DifPresentationExchangeSubmission, + VerifiablePresentation, +} from '../models' + +import { default as jp } from 'jsonpath' + +import { CredoError } from '../../../error' +import { MdocDeviceResponse } from '../../mdoc' +import { ClaimFormat, W3cJsonLdVerifiablePresentation, W3cJwtVerifiablePresentation } from '../../vc' + +export type DifPexPresentationWithDescriptor = ReturnType< + typeof extractPresentationsWithDescriptorsFromSubmission +>[number] +export function extractPresentationsWithDescriptorsFromSubmission( + presentations: SingleOrArray, + submission: DifPresentationExchangeSubmission, + definition: DifPresentationExchangeDefinition +) { + return submission.descriptor_map.map((descriptor) => { + const [presentation] = jp.query(presentations, descriptor.path) as [VerifiablePresentation | undefined] + const inputDescriptor = definition.input_descriptors.find(({ id }) => id === descriptor.id) + + if (!presentation) { + throw new CredoError( + `Unable to extract presentation at path '${descriptor.path}' for submission descriptor '${descriptor.id}'` + ) + } + + if (!inputDescriptor) { + throw new Error( + `Unable to extract input descriptor '${descriptor.id}' from definition '${definition.id}' for submission '${submission.id}'` + ) + } + + if (presentation instanceof MdocDeviceResponse) { + const document = presentation.documents.find((document) => document.docType === descriptor.id) + if (!document) { + throw new Error( + `Unable to extract mdoc document with doctype '${descriptor.id}' from mdoc device response for submission '${submission.id}'.` + ) + } + + return { + format: ClaimFormat.MsoMdoc, + descriptor, + presentation, + credential: document, + inputDescriptor, + } as const + } else if ( + presentation instanceof W3cJwtVerifiablePresentation || + presentation instanceof W3cJsonLdVerifiablePresentation + ) { + if (!descriptor.path_nested) { + throw new Error( + `Submission descriptor '${descriptor.id}' for submission '${submission.id}' has no 'path_nested' but presentation is format '${presentation.claimFormat}'` + ) + } + + const [verifiableCredential] = jp.query( + // Path is `$.vp.verifiableCredential[]` in case of jwt vp + presentation.claimFormat === ClaimFormat.JwtVp ? { vp: presentation } : presentation, + descriptor.path_nested.path + ) as [W3cJwtVerifiableCredential | W3cJsonLdVerifiableCredential | undefined] + + if (!verifiableCredential) { + throw new CredoError( + `Unable to extract credential at path '${descriptor.path_nested.path}' from presentation at path '${descriptor.path}' for submission descriptor '${descriptor.id}'` + ) + } + + return { + format: presentation.claimFormat, + descriptor, + presentation, + credential: verifiableCredential, + inputDescriptor, + } as const + } else { + return { + format: ClaimFormat.SdJwtVc, + descriptor, + presentation, + credential: presentation, + inputDescriptor, + } as const + } + }) +} diff --git a/packages/core/src/modules/mdoc/Mdoc.ts b/packages/core/src/modules/mdoc/Mdoc.ts index 6882a74e0b..85ee56e00c 100644 --- a/packages/core/src/modules/mdoc/Mdoc.ts +++ b/packages/core/src/modules/mdoc/Mdoc.ts @@ -102,14 +102,21 @@ export class Mdoc { } const cert = X509Certificate.fromEncodedCertificate(issuerCertificate) - const issuerKey = await getJwkFromKey(cert.publicKey) + const issuerKey = getJwkFromKey(cert.publicKey) const alg = issuerKey.supportedSignatureAlgorithms.find( - (alg): alg is JwaSignatureAlgorithm.ES256 | JwaSignatureAlgorithm.ES384 | JwaSignatureAlgorithm.ES512 => { + ( + alg + ): alg is + | JwaSignatureAlgorithm.ES256 + | JwaSignatureAlgorithm.ES384 + | JwaSignatureAlgorithm.ES512 + | JwaSignatureAlgorithm.EdDSA => { return ( alg === JwaSignatureAlgorithm.ES256 || alg === JwaSignatureAlgorithm.ES384 || - alg === JwaSignatureAlgorithm.ES512 + alg === JwaSignatureAlgorithm.ES512 || + alg === JwaSignatureAlgorithm.EdDSA ) } ) diff --git a/packages/core/src/modules/sd-jwt-vc/SdJwtVcService.ts b/packages/core/src/modules/sd-jwt-vc/SdJwtVcService.ts index 765e437677..fcc00d214c 100644 --- a/packages/core/src/modules/sd-jwt-vc/SdJwtVcService.ts +++ b/packages/core/src/modules/sd-jwt-vc/SdJwtVcService.ts @@ -182,8 +182,8 @@ export class SdJwtVcService { return compactDerivedSdJwtVc } - private assertValidX5cJwtIssuer(iss: string, leafCertificate: X509Certificate) { - if (!iss.startsWith('https://')) { + private assertValidX5cJwtIssuer(agentContext: AgentContext, iss: string, leafCertificate: X509Certificate) { + if (!iss.startsWith('https://') && !(iss.startsWith('http://') && agentContext.config.allowInsecureHttpUrls)) { throw new SdJwtVcError('The X509 certificate issuer must be a HTTPS URI.') } @@ -423,7 +423,7 @@ export class SdJwtVcService { } const alg = supportedSignatureAlgorithms[0] - this.assertValidX5cJwtIssuer(issuer.issuer, leafCertificate) + this.assertValidX5cJwtIssuer(agentContext, issuer.issuer, leafCertificate) return { key, diff --git a/packages/core/src/modules/vc/jwt-vc/W3cJwtCredentialService.ts b/packages/core/src/modules/vc/jwt-vc/W3cJwtCredentialService.ts index fdbd8fb3ab..a03a07528d 100644 --- a/packages/core/src/modules/vc/jwt-vc/W3cJwtCredentialService.ts +++ b/packages/core/src/modules/vc/jwt-vc/W3cJwtCredentialService.ts @@ -369,7 +369,7 @@ export class W3cJwtCredentialService { return { isValid: false, error: new CredoError( - 'Credential is of format ldp_vc. presentations in jwp_vp format can only contain credentials in jwt_vc format' + 'Credential is of format ldp_vc. presentations in jwt_vp format can only contain credentials in jwt_vc format' ), validations: {}, } diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index c0dcee8406..3620ceafe1 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -84,6 +84,19 @@ export interface InitConfig { autoUpdateStorageOnStartup?: boolean backupBeforeStorageUpdate?: boolean processDidCommMessagesConcurrently?: boolean + + /** + * Allow insecure http urls in places where this is usually required. + * Unsecure http urls may still be allowed in places where this is not checked (e.g. didcomm) + * + * For some flows this config option is set globally, which means that different agent configurations + * will fight for the configuration. It is meant as a local development option. + * + * Use with caution + * + * @default false + */ + allowInsecureHttpUrls?: boolean } export type ProtocolVersion = `${number}.${number}` diff --git a/packages/core/tests/helpers.ts b/packages/core/tests/helpers.ts index 79c3c5dd3f..d103d5827b 100644 --- a/packages/core/tests/helpers.ts +++ b/packages/core/tests/helpers.ts @@ -52,6 +52,7 @@ import { ProofEventTypes, TrustPingEventTypes, DidsApi, + X509Api, } from '../src' import { Key, KeyType } from '../src/crypto' import { DidKey } from '../src/modules/dids/methods/key' @@ -759,5 +760,20 @@ export async function createDidKidVerificationMethod(agentContext: AgentContext, const verificationMethod = didCreateResult.didState.didDocument?.dereferenceKey(kid, ['authentication']) if (!verificationMethod) throw new Error('No verification method found') - return { did, kid, verificationMethod } + return { did, kid, verificationMethod, key: didKey.key } +} + +export async function createX509Certificate(agentContext: AgentContext, dns: string, key?: Key) { + const x509 = agentContext.dependencyManager.resolve(X509Api) + const certificate = await x509.createSelfSignedCertificate({ + key: + key ?? + (await agentContext.wallet.createKey({ + keyType: KeyType.Ed25519, + })), + name: 'C=DE', + extensions: [[{ type: 'dns', value: dns }]], + }) + + return { certificate, base64Certificate: certificate.toString('base64') } } diff --git a/packages/node/package.json b/packages/node/package.json index adaff8035a..1748ba3bc6 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -37,7 +37,7 @@ "devDependencies": { "@types/node": "^18.18.8", "@types/ws": "^8.5.4", - "nock": "^13.3.0", + "nock": "^14.0.0-beta.16", "rimraf": "^4.4.0", "typescript": "~5.5.2" } diff --git a/packages/openid4vc/package.json b/packages/openid4vc/package.json index 14c86f6ccf..1166574152 100644 --- a/packages/openid4vc/package.json +++ b/packages/openid4vc/package.json @@ -29,18 +29,18 @@ "@credo-ts/core": "workspace:*", "@sphereon/did-auth-siop": "0.16.1-fix.173", "@sphereon/oid4vc-common": "0.16.1-fix.173", - "@sphereon/oid4vci-client": "0.16.1-fix.173", - "@sphereon/oid4vci-common": "0.16.1-fix.173", - "@sphereon/oid4vci-issuer": "0.16.1-fix.173", "@sphereon/ssi-types": "0.30.2-next.135", "class-transformer": "^0.5.1", - "rxjs": "^7.8.0" + "rxjs": "^7.8.0", + "zod": "^3.23.8", + "@animo-id/oid4vci": "0.1.3", + "@animo-id/oauth2": "0.1.3" }, "devDependencies": { "@credo-ts/tenants": "workspace:*", "@types/express": "^4.17.21", "express": "^4.18.2", - "nock": "^13.3.0", + "nock": "^14.0.0-beta.16", "rimraf": "^4.4.0", "typescript": "~5.5.2" } diff --git a/packages/openid4vc/src/openid4vc-holder/OpenId4VcHolderApi.ts b/packages/openid4vc/src/openid4vc-holder/OpenId4VcHolderApi.ts index 1a9dd4ecd8..8444d78370 100644 --- a/packages/openid4vc/src/openid4vc-holder/OpenId4VcHolderApi.ts +++ b/packages/openid4vc/src/openid4vc-holder/OpenId4VcHolderApi.ts @@ -1,16 +1,17 @@ import type { OpenId4VciResolvedCredentialOffer, - OpenId4VciResolvedAuthorizationRequest, OpenId4VciAuthCodeFlowOptions, - OpenId4VciAcceptCredentialOfferOptions, OpenId4VciTokenRequestOptions as OpenId4VciRequestTokenOptions, OpenId4VciCredentialRequestOptions as OpenId4VciRequestCredentialOptions, OpenId4VciSendNotificationOptions, OpenId4VciRequestTokenResponse, + OpenId4VciRetrieveAuthorizationCodeUsingPresentationOptions, } from './OpenId4VciHolderServiceOptions' import type { OpenId4VcSiopAcceptAuthorizationRequestOptions } from './OpenId4vcSiopHolderServiceOptions' -import { injectable, AgentContext } from '@credo-ts/core' +import { injectable, AgentContext, DifPresentationExchangeService, DifPexCredentialsForRequest } from '@credo-ts/core' + +import { OpenId4VciMetadata } from '../shared' import { OpenId4VciHolderService } from './OpenId4VciHolderService' import { OpenId4VcSiopHolderService } from './OpenId4vcSiopHolderService' @@ -23,7 +24,8 @@ export class OpenId4VcHolderApi { public constructor( private agentContext: AgentContext, private openId4VciHolderService: OpenId4VciHolderService, - private openId4VcSiopHolderService: OpenId4VcSiopHolderService + private openId4VcSiopHolderService: OpenId4VcSiopHolderService, + private difPresentationExchangeService: DifPresentationExchangeService ) {} /** @@ -56,6 +58,18 @@ export class OpenId4VcHolderApi { return await this.openId4VcSiopHolderService.acceptAuthorizationRequest(this.agentContext, options) } + /** + * Automatically select credentials from available credentials for a request. Can be called after calling + * @see resolveSiopAuthorizationRequest. + */ + public selectCredentialsForRequest(credentialsForRequest: DifPexCredentialsForRequest) { + return this.difPresentationExchangeService.selectCredentialsForRequest(credentialsForRequest) + } + + public async resolveIssuerMetadata(credentialIssuer: string): Promise { + return await this.openId4VciHolderService.resolveIssuerMetadata(this.agentContext, credentialIssuer) + } + /** * Resolves a credential offer given as credential offer URL, or issuance initiation URL, * into a unified format with metadata. @@ -72,8 +86,14 @@ export class OpenId4VcHolderApi { * * Not to be confused with the {@link resolveSiopAuthorizationRequest}, which is only used for SIOP requests. * - * It will generate the authorization request URI based on the provided options. - * The authorization request URI is used to obtain the authorization code. Currently this needs to be done manually. + * It will generate an authorization session based on the provided options. + * + * There are two possible flows: + * - Oauth2Recirect: an authorization request URI is returend which can be used to obtain the authorization code. + * This needs to be done manually (e.g. by opening a browser window) + * - PresentationDuringIssuance: an openid4vp presentation request needs to be handled. A oid4vpRequestUri is returned + * which can be parsed using `resolveSiopAuthorizationRequest`. After the presentation session has been completed, + * the resulting `presentationDuringIssuanceSession` can be used to obtain an authorization code * * Authorization to request credentials can be requested via authorization_details or scopes. * This function automatically generates the authorization_details for all offered credentials. @@ -95,70 +115,31 @@ export class OpenId4VcHolderApi { } /** - * Accepts a credential offer using the pre-authorized code flow. - * @deprecated use @see requestToken and @see requestCredentials instead + * Retrieve an authorization code using an `presentationDuringIssuanceSession`. * - * @param resolvedCredentialOffer Obtained through @see resolveCredentialOffer - * @param acceptCredentialOfferOptions + * The authorization code can be exchanged for an `accessToken` @see requestToken */ - public async acceptCredentialOfferUsingPreAuthorizedCode( - resolvedCredentialOffer: OpenId4VciResolvedCredentialOffer, - acceptCredentialOfferOptions: OpenId4VciAcceptCredentialOfferOptions + public async retrieveAuthorizationCodeUsingPresentation( + options: OpenId4VciRetrieveAuthorizationCodeUsingPresentationOptions ) { - const credentialResponse = await this.openId4VciHolderService.acceptCredentialOffer(this.agentContext, { - resolvedCredentialOffer, - acceptCredentialOfferOptions, - }) - - return credentialResponse.map((credentialResponse) => credentialResponse.credential) - } - - /** - * Accepts a credential offer using the authorization code flow. - * @deprecated use @see requestToken and @see requestCredentials instead - * - * @param resolvedCredentialOffer Obtained through @see resolveCredentialOffer - * @param resolvedAuthorizationRequest Obtained through @see resolveIssuanceAuthorizationRequest - * @param code The authorization code obtained via the authorization request URI - * @param acceptCredentialOfferOptions - */ - public async acceptCredentialOfferUsingAuthorizationCode( - resolvedCredentialOffer: OpenId4VciResolvedCredentialOffer, - resolvedAuthorizationRequest: OpenId4VciResolvedAuthorizationRequest, - code: string, - acceptCredentialOfferOptions: OpenId4VciAcceptCredentialOfferOptions - ) { - const credentialResponse = await this.openId4VciHolderService.acceptCredentialOffer(this.agentContext, { - resolvedCredentialOffer, - resolvedAuthorizationRequestWithCode: { ...resolvedAuthorizationRequest, code }, - acceptCredentialOfferOptions, - }) - - return credentialResponse.map((credentialResponse) => credentialResponse.credential) + return await this.openId4VciHolderService.retrieveAuthorizationCodeUsingPresentation(this.agentContext, options) } /** * Requests the token to be used for credential requests. - * - * @param options.resolvedCredentialOffer Obtained through @see resolveCredentialOffer - * @param options.userPin The user's PIN - * @param options.resolvedAuthorizationRequest Obtained through @see resolveIssuanceAuthorizationRequest - * @param options.code The authorization code obtained via the authorization request URI */ public async requestToken(options: OpenId4VciRequestTokenOptions): Promise { - const { - access_token: accessToken, - c_nonce: cNonce, - dpop, - } = await this.openId4VciHolderService.requestAccessToken(this.agentContext, options) - return { accessToken, cNonce, dpop } + const { accessTokenResponse, dpop } = await this.openId4VciHolderService.requestAccessToken( + this.agentContext, + options + ) + + return { accessToken: accessTokenResponse.access_token, cNonce: accessTokenResponse.c_nonce, dpop } } /** - * Request a credential. Can be used with both the pre-authorized code flow and the authorization code flow. - * - * @param options.resolvedCredentialOffer Obtained through @see resolveCredentialOffer - * @param options.tokenResponse Obtained through @see requestAccessToken + * Request a set of credentials from the credential isser. + * Can be used with both the pre-authorized code flow and the authorization code flow. */ public async requestCredentials(options: OpenId4VciRequestCredentialOptions) { const { resolvedCredentialOffer, cNonce, accessToken, dpop, clientId, ...credentialRequestOptions } = options @@ -175,10 +156,8 @@ export class OpenId4VcHolderApi { /** * Send a notification event to the credential issuer - * - * @param options OpenId4VciSendNotificationOptions */ public async sendNotification(options: OpenId4VciSendNotificationOptions) { - return this.openId4VciHolderService.sendNotification(options) + return this.openId4VciHolderService.sendNotification(this.agentContext, options) } } diff --git a/packages/openid4vc/src/openid4vc-holder/OpenId4VcHolderModule.ts b/packages/openid4vc/src/openid4vc-holder/OpenId4VcHolderModule.ts index 64cd6b9f08..6208d2c4f5 100644 --- a/packages/openid4vc/src/openid4vc-holder/OpenId4VcHolderModule.ts +++ b/packages/openid4vc/src/openid4vc-holder/OpenId4VcHolderModule.ts @@ -1,5 +1,6 @@ import type { DependencyManager, Module } from '@credo-ts/core' +import { setGlobalConfig } from '@animo-id/oauth2' import { AgentConfig } from '@credo-ts/core' import { OpenId4VcHolderApi } from './OpenId4VcHolderApi' @@ -17,12 +18,18 @@ export class OpenId4VcHolderModule implements Module { * Registers the dependencies of the question answer module on the dependency manager. */ public register(dependencyManager: DependencyManager) { + const agentConfig = dependencyManager.resolve(AgentConfig) + // Warn about experimental module - dependencyManager - .resolve(AgentConfig) - .logger.warn( - "The '@credo-ts/openid4vc' Holder module is experimental and could have unexpected breaking changes. When using this module, make sure to use strict versions for all @credo-ts packages." - ) + agentConfig.logger.warn( + "The '@credo-ts/openid4vc' Holder module is experimental and could have unexpected breaking changes. When using this module, make sure to use strict versions for all @credo-ts packages." + ) + + if (agentConfig.allowInsecureHttpUrls) { + setGlobalConfig({ + allowInsecureUrls: true, + }) + } // Services dependencyManager.registerSingleton(OpenId4VciHolderService) diff --git a/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderService.ts b/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderService.ts index e46bed9a18..aa262c8dad 100644 --- a/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderService.ts +++ b/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderService.ts @@ -3,38 +3,39 @@ import type { OpenId4VciAuthCodeFlowOptions, OpenId4VciCredentialBindingResolver, OpenId4VciCredentialResponse, + OpenId4VciDpopRequestOptions, OpenId4VciNotificationEvent, OpenId4VciProofOfPossessionRequirements, OpenId4VciResolvedAuthorizationRequest, - OpenId4VciResolvedAuthorizationRequestWithCode, OpenId4VciResolvedCredentialOffer, + OpenId4VciRetrieveAuthorizationCodeUsingPresentationOptions, OpenId4VciSupportedCredentialFormats, OpenId4VciTokenRequestOptions, } from './OpenId4VciHolderServiceOptions' import type { OpenId4VciCredentialConfigurationSupported, - OpenId4VciCredentialSupported, - OpenId4VciIssuerMetadata, + OpenId4VciCredentialIssuerMetadata, + OpenId4VciMetadata, } from '../shared' -import type { AgentContext, JwaSignatureAlgorithm, JwkJson, Key } from '@credo-ts/core' -import type { - AccessTokenResponse, - AuthorizationDetails, - AuthorizationDetailsJwtVcJson, - AuthorizationDetailsJwtVcJsonLdAndLdpVc, - AuthorizationDetailsMsoMdoc, - AuthorizationDetailsSdJwtVc, - CredentialResponse, - Jwt, - OpenIDResponse, -} from '@sphereon/oid4vci-common' +import type { AgentContext, JwaSignatureAlgorithm, KeyType } from '@credo-ts/core' +import { + getAuthorizationServerMetadataFromList, + JwtSigner, + Oauth2Client, + preAuthorizedCodeGrantIdentifier, + RequestDpopOptions, +} from '@animo-id/oauth2' +import { + AuthorizationFlow, + CredentialResponse, + IssuerMetadataResult, + Oid4vciClient, + Oid4vciRetrieveCredentialsError, +} from '@animo-id/oid4vci' import { CredoError, - DidsApi, - Hasher, InjectionSymbols, - JsonEncoder, Jwk, JwsService, Logger, @@ -42,43 +43,24 @@ import { MdocApi, SdJwtVcApi, SignatureSuiteRegistry, - TypedArrayEncoder, W3cCredentialService, W3cJsonLdVerifiableCredential, W3cJwtVerifiableCredential, getJwkClassFromJwaSignatureAlgorithm, getJwkFromJson, getJwkFromKey, - getKeyFromVerificationMethod, getSupportedVerificationMethodTypesFromKeyType, inject, injectable, parseDid, } from '@credo-ts/core' -import { CreateDPoPClientOpts, CreateDPoPJwtPayloadProps, SigningAlgo } from '@sphereon/oid4vc-common' -import { - AccessTokenClient, - CredentialRequestClientBuilder, - OpenID4VCIClient, - OpenID4VCIClientStateV1_0_13, - OpenID4VCIClientV1_0_11, - OpenID4VCIClientV1_0_13, - ProofOfPossessionBuilder, -} from '@sphereon/oid4vci-client' -import { - CodeChallengeMethod, - DPoPResponseParams, - EndpointMetadataResult, - OpenId4VCIVersion, - PARMode, - post, -} from '@sphereon/oid4vci-common' import { OpenId4VciCredentialFormatProfile } from '../shared' -import { getOfferedCredentials, getTypesFromCredentialSupported } from '../shared/issuerMetadataUtils' -import { getCreateJwtCallback, getSupportedJwaSignatureAlgorithms, isCredentialOfferV1Draft13 } from '../shared/utils' +import { getOid4vciCallbacks } from '../shared/callbacks' +import { getOfferedCredentials, getScopesFromCredentialConfigurationsSupported } from '../shared/issuerMetadataUtils' +import { getKeyFromDid, getSupportedJwaSignatureAlgorithms } from '../shared/utils' -import { OpenId4VciNotificationMetadata, openId4VciSupportedCredentialFormats } from './OpenId4VciHolderServiceOptions' +import { openId4VciSupportedCredentialFormats } from './OpenId4VciHolderServiceOptions' @injectable() export class OpenId4VciHolderService { @@ -96,101 +78,37 @@ export class OpenId4VciHolderService { this.logger = logger } - public async resolveCredentialOffer( + public async resolveIssuerMetadata( agentContext: AgentContext, - credentialOffer: string - ): Promise { - const client = await OpenID4VCIClient.fromURI({ - uri: credentialOffer, - resolveOfferUri: true, - retrieveServerMetadata: true, - // This is a separate call, so we don't fetch it here, however it may be easier to just construct it here? - createAuthorizationRequestURL: false, - }) - - if (!client.credentialOffer?.credential_offer) { - throw new CredoError(`Could not resolve credential offer from '${credentialOffer}'`) - } - - const metadata = client.endpointMetadata - const credentialIssuerMetadata = metadata.credentialIssuerMetadata as OpenId4VciIssuerMetadata + credentialIssuer: string + ): Promise { + const client = this.getClient(agentContext) - if (!credentialIssuerMetadata) { - throw new CredoError(`Could not retrieve issuer metadata from '${metadata.issuer}'`) - } - - this.logger.info('Fetched server metadata', { - issuer: metadata.issuer, - credentialEndpoint: metadata.credential_endpoint, - tokenEndpoint: metadata.token_endpoint, - }) + const metadata = await client.resolveIssuerMetadata(credentialIssuer) + this.logger.debug('fetched credential issuer metadata', { metadata }) - this.logger.debug('Full server metadata', metadata) + return metadata + } - const credentialOfferPayload = client.credentialOffer.credential_offer + public async resolveCredentialOffer( + agentContext: AgentContext, + credentialOffer: string + ): Promise { + const client = this.getClient(agentContext) - const offeredCredentialsData = isCredentialOfferV1Draft13(credentialOfferPayload) - ? credentialOfferPayload.credential_configuration_ids - : credentialOfferPayload.credentials + const credentialOfferObject = await client.resolveCredentialOffer(credentialOffer) + const metadata = await client.resolveIssuerMetadata(credentialOfferObject.credential_issuer) + this.logger.debug('fetched credential offer and issuer metadata', { metadata, credentialOfferObject }) - const { credentialConfigurationsSupported, credentialsSupported } = getOfferedCredentials( - agentContext, - offeredCredentialsData, - credentialIssuerMetadata.credentials_supported ?? credentialIssuerMetadata.credential_configurations_supported + const credentialConfigurationsSupported = getOfferedCredentials( + credentialOfferObject.credential_configuration_ids, + client.getKnownCredentialConfigurationsSupported(metadata.credentialIssuer) ) return { - metadata: { - ...metadata, - credentialIssuerMetadata: credentialIssuerMetadata, - }, - offeredCredentials: credentialsSupported, + metadata, offeredCredentialConfigurations: credentialConfigurationsSupported, - credentialOfferPayload, - credentialOfferRequestWithBaseUrl: client.credentialOffer, - version: client.version(), - } - } - - private getAuthDetailsFromOfferedCredential( - offeredCredential: OpenId4VciCredentialSupported, - authDetailsLocation: string | undefined - ): AuthorizationDetails | undefined { - const { format } = offeredCredential - const type = 'openid_credential' - - const locations = authDetailsLocation ? [authDetailsLocation] : undefined - if (format === OpenId4VciCredentialFormatProfile.JwtVcJson) { - return { type, format, types: offeredCredential.types, locations } satisfies AuthorizationDetailsJwtVcJson - } else if ( - format === OpenId4VciCredentialFormatProfile.LdpVc || - format === OpenId4VciCredentialFormatProfile.JwtVcJsonLd - ) { - const credential_definition = { - '@context': offeredCredential['@context'], - credentialSubject: offeredCredential.credentialSubject, - types: offeredCredential.types, - } - - return { type, format, locations, credential_definition } satisfies AuthorizationDetailsJwtVcJsonLdAndLdpVc - } else if (format === OpenId4VciCredentialFormatProfile.SdJwtVc) { - return { - type, - format, - locations, - vct: offeredCredential.vct, - claims: offeredCredential.claims, - } satisfies AuthorizationDetailsSdJwtVc - } else if (format === OpenId4VciCredentialFormatProfile.MsoMdoc) { - return { - type, - format, - locations, - claims: offeredCredential.claims, - doctype: offeredCredential.doctype, - } satisfies AuthorizationDetailsMsoMdoc - } else { - throw new CredoError(`Cannot create authorization_details. Unsupported credential format '${format}'.`) + credentialOfferPayload: credentialOfferObject, } } @@ -199,181 +117,199 @@ export class OpenId4VciHolderService { resolvedCredentialOffer: OpenId4VciResolvedCredentialOffer, authCodeFlowOptions: OpenId4VciAuthCodeFlowOptions ): Promise { - const { metadata, offeredCredentials } = resolvedCredentialOffer - const codeVerifier = ( - await Promise.all([agentContext.wallet.generateNonce(), agentContext.wallet.generateNonce()]) - ).join('') - const codeVerifierSha256 = Hasher.hash(codeVerifier, 'sha-256') - const codeChallenge = TypedArrayEncoder.toBase64URL(codeVerifierSha256) - - this.logger.debug('Converted code_verifier to code_challenge', { - codeVerifier: codeVerifier, - sha256: codeVerifierSha256.toString(), - base64Url: codeChallenge, - }) + const { clientId, redirectUri } = authCodeFlowOptions + const { metadata, credentialOfferPayload, offeredCredentialConfigurations } = resolvedCredentialOffer - const authDetailsLocation = metadata.credentialIssuerMetadata.authorization_server - ? metadata.credentialIssuerMetadata.authorization_server - : undefined + const client = this.getClient(agentContext) - const authDetails = offeredCredentials - .map((credential) => this.getAuthDetailsFromOfferedCredential(credential, authDetailsLocation)) - .filter((authDetail): authDetail is AuthorizationDetails => authDetail !== undefined) - - const { clientId, redirectUri, scope } = authCodeFlowOptions - - const vciClientState = { - state: { - credentialOffer: resolvedCredentialOffer.credentialOfferRequestWithBaseUrl, - clientId, - credentialIssuer: metadata.issuer, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - endpointMetadata: metadata as unknown as any, // will be v11 / v13 based on version - pkce: { - codeChallenge, - codeChallengeMethod: CodeChallengeMethod.S256, - codeVerifier, - }, - } satisfies OpenID4VCIClientStateV1_0_13, - } + // If scope is not provided, we request scope for all offered credentials + const scope = + authCodeFlowOptions.scope ?? getScopesFromCredentialConfigurationsSupported(offeredCredentialConfigurations) - const client = - resolvedCredentialOffer.version === OpenId4VCIVersion.VER_1_0_11 - ? await OpenID4VCIClientV1_0_11.fromState(vciClientState) - : await OpenID4VCIClientV1_0_13.fromState(vciClientState) - - const authorizationRequestUri = await client.createAuthorizationRequestUrl({ - authorizationRequest: { - redirectUri, - scope: scope ? scope.join(' ') : undefined, - authorizationDetails: authDetails, - parMode: PARMode.AUTO, - }, + const authorizationResult = await client.initiateAuthorization({ + clientId, + issuerMetadata: metadata, + credentialOffer: credentialOfferPayload, + scope: scope.join(' '), + redirectUri, }) - return { - ...authCodeFlowOptions, - codeVerifier, - authorizationRequestUri, - } - } - - public async sendNotification(options: { - notificationMetadata: OpenId4VciNotificationMetadata - notificationEvent: OpenId4VciNotificationEvent - accessToken: string - }) { - const { notificationMetadata, notificationEvent } = options - const { notificationId, notificationEndpoint } = notificationMetadata - - const response = await post( - notificationEndpoint, - { notification_id: notificationId, event: notificationEvent }, - { - bearerToken: options.accessToken, - contentType: 'application/json', + if (authorizationResult.authorizationFlow === AuthorizationFlow.PresentationDuringIssuance) { + return { + authorizationFlow: AuthorizationFlow.PresentationDuringIssuance, + oid4vpRequestUrl: authorizationResult.oid4vpRequestUrl, + authSession: authorizationResult.authSession, } - ) + } - if (!response.successBody) { - throw new CredoError(`Failed to send notification event '${notificationId}' to '${notificationEndpoint}'`) + // Normal Oauth2Redirect flow + return { + authorizationFlow: AuthorizationFlow.Oauth2Redirect, + codeVerifier: authorizationResult.pkce?.codeVerifier, + authorizationRequestUrl: authorizationResult.authorizationRequestUrl, } } - private async getCreateDpopOptions( + public async sendNotification( agentContext: AgentContext, - metadata: Pick & { - credentialIssuerMetadata: OpenId4VciIssuerMetadata - }, - resourceRequestOptions?: { - jwk: Jwk - jwtPayloadProps: Omit + options: { + metadata: IssuerMetadataResult + notificationId: string + notificationEvent: OpenId4VciNotificationEvent + accessToken: string + dpop?: { jwk: Jwk; alg: JwaSignatureAlgorithm; nonce?: string } } ) { - const dpopSigningAlgValuesSupported = - metadata.authorizationServerMetadata?.dpop_signing_alg_values_supported ?? - metadata.credentialIssuerMetadata.dpop_signing_alg_values_supported - - if (!dpopSigningAlgValuesSupported) return undefined - - const alg = dpopSigningAlgValuesSupported.find((alg) => getJwkClassFromJwaSignatureAlgorithm(alg)) + const client = this.getClient(agentContext) + await client.sendNotification({ + accessToken: options.accessToken, + dpop: options.dpop + ? await this.getDpopOptions(agentContext, { + ...options.dpop, + dpopSigningAlgValuesSupported: [options.dpop.alg], + }) + : undefined, + issuerMetadata: options.metadata, + notification: { + event: options.notificationEvent, + notificationId: options.notificationId, + }, + }) + } - let jwk: Jwk - if (resourceRequestOptions) { - jwk = resourceRequestOptions.jwk - } else { - const JwkClass = alg ? getJwkClassFromJwaSignatureAlgorithm(alg) : undefined + private async getDpopOptions( + agentContext: AgentContext, + { + jwk, + dpopSigningAlgValuesSupported, + nonce, + }: { dpopSigningAlgValuesSupported: string[]; jwk?: Jwk; nonce?: string } + ): Promise { + if (jwk) { + const alg = dpopSigningAlgValuesSupported.find((alg) => + jwk.supportedSignatureAlgorithms.includes(alg as JwaSignatureAlgorithm) + ) - if (!JwkClass) { + if (!alg) { throw new CredoError( `No supported dpop signature algorithms found in dpop_signing_alg_values_supported '${dpopSigningAlgValuesSupported.join( ', ' - )}'` + )}' matching key type ${jwk.keyType}` ) } - const key = await agentContext.wallet.createKey({ keyType: JwkClass.keyType }) - jwk = getJwkFromKey(key) - } - - const createDPoPOpts: CreateDPoPClientOpts = { - jwtIssuer: { alg: alg as unknown as SigningAlgo, jwk: jwk.toJson() }, - dPoPSigningAlgValuesSupported: dpopSigningAlgValuesSupported, - jwtPayloadProps: resourceRequestOptions?.jwtPayloadProps ?? {}, - createJwtCallback: getCreateJwtCallback(agentContext), + return { + signer: { + method: 'jwk', + alg, + publicJwk: jwk.toJson(), + }, + nonce, + } } - return createDPoPOpts - } - public async requestAccessToken(agentContext: AgentContext, options: OpenId4VciTokenRequestOptions) { - const { resolvedCredentialOffer, txCode, resolvedAuthorizationRequest, code } = options - const { metadata, credentialOfferRequestWithBaseUrl } = resolvedCredentialOffer + const alg = dpopSigningAlgValuesSupported.find((alg) => getJwkClassFromJwaSignatureAlgorithm(alg)) + const JwkClass = alg ? getJwkClassFromJwaSignatureAlgorithm(alg) : undefined - // acquire the access token - let accessTokenResponse: OpenIDResponse + if (!alg || !JwkClass) { + throw new CredoError( + `No supported dpop signature algorithms found in dpop_signing_alg_values_supported '${dpopSigningAlgValuesSupported.join( + ', ' + )}'` + ) + } - const accessTokenClient = new AccessTokenClient() + const key = await agentContext.wallet.createKey({ keyType: JwkClass.keyType }) + return { + signer: { + method: 'jwk', + alg, + publicJwk: getJwkFromKey(key).toJson(), + }, + nonce, + } + } - const createDPoPOpts = await this.getCreateDpopOptions(agentContext, metadata) + public async retrieveAuthorizationCodeUsingPresentation( + agentContext: AgentContext, + options: OpenId4VciRetrieveAuthorizationCodeUsingPresentationOptions + ) { + const client = this.getClient(agentContext) + // TODO: support dpop on this endpoint as well + // const dpop = options.dpop + // ? await this.getDpopOptions(agentContext, { + // ...options.dpop, + // dpopSigningAlgValuesSupported: [options.dpop.alg], + // }) + // : undefined + + // TODO: we should support DPoP in this request as well + const { authorizationChallengeResponse } = await client.retrieveAuthorizationCodeUsingPresentation({ + authSession: options.authSession, + presentationDuringIssuanceSession: options.presentationDuringIssuanceSession, + credentialOffer: options.resolvedCredentialOffer.credentialOfferPayload, + issuerMetadata: options.resolvedCredentialOffer.metadata, + // dpop + }) - let dpopJwk: Jwk | undefined - if (createDPoPOpts) { - if (!createDPoPOpts.jwtIssuer.jwk.kty) { - throw new CredoError('Missing required key type (kty) in the jwk.') - } - dpopJwk = getJwkFromJson(createDPoPOpts.jwtIssuer.jwk as JwkJson) - } - if (resolvedAuthorizationRequest) { - const { codeVerifier, redirectUri } = resolvedAuthorizationRequest - accessTokenResponse = await accessTokenClient.acquireAccessToken({ - metadata: metadata, - credentialOffer: { credential_offer: credentialOfferRequestWithBaseUrl.credential_offer }, - pin: txCode, - code, - codeVerifier, - redirectUri, - createDPoPOpts, - }) - } else { - accessTokenResponse = await accessTokenClient.acquireAccessToken({ - metadata: metadata, - credentialOffer: { credential_offer: credentialOfferRequestWithBaseUrl.credential_offer }, - pin: txCode, - createDPoPOpts, - }) + return { + authorizationCode: authorizationChallengeResponse.authorization_code, } + } - if (!accessTokenResponse.successBody) { - throw new CredoError( - `could not acquire access token from '${metadata.issuer}'. ${accessTokenResponse.errorBody?.error}: ${accessTokenResponse.errorBody?.error_description}` - ) - } + public async requestAccessToken(agentContext: AgentContext, options: OpenId4VciTokenRequestOptions) { + const { metadata, credentialOfferPayload } = options.resolvedCredentialOffer + const client = this.getClient(agentContext) + const oauth2Client = this.getOauth2Client(agentContext) + + const authorizationServer = options.code + ? credentialOfferPayload.grants?.authorization_code?.authorization_server + : credentialOfferPayload.grants?.[preAuthorizedCodeGrantIdentifier]?.authorization_server + const authorizationServerMetadata = getAuthorizationServerMetadataFromList( + metadata.authorizationServers, + authorizationServer ?? metadata.authorizationServers[0].issuer + ) - this.logger.debug('Requested OpenId4VCI Access Token.') + // TODO: should allow dpop input parameter for if it was already bound earlier + const isDpopSupported = oauth2Client.isDpopSupported({ + authorizationServerMetadata, + }) + const dpop = isDpopSupported.supported + ? await this.getDpopOptions(agentContext, { + dpopSigningAlgValuesSupported: isDpopSupported.dpopSigningAlgValuesSupported, + }) + : undefined + + const result = options.code + ? await client.retrieveAuthorizationCodeAccessTokenFromOffer({ + issuerMetadata: metadata, + credentialOffer: credentialOfferPayload, + authorizationCode: options.code, + dpop, + pkceCodeVerifier: options.codeVerifier, + redirectUri: options.redirectUri, + additionalRequestPayload: { + // TODO: handle it as part of client auth once we support + // assertion based client authentication + client_id: options.clientId, + }, + }) + : await client.retrievePreAuthorizedCodeAccessTokenFromOffer({ + credentialOffer: credentialOfferPayload, + issuerMetadata: metadata, + dpop, + txCode: options.txCode, + }) return { - ...accessTokenResponse.successBody, - ...(dpopJwk && { dpop: { jwk: dpopJwk, nonce: accessTokenResponse.params?.dpop?.dpopNonce } }), + ...result, + dpop: dpop + ? { + ...result.dpop, + alg: dpop.signer.alg as JwaSignatureAlgorithm, + jwk: getJwkFromJson(dpop.signer.publicJwk), + } + : undefined, } } @@ -382,27 +318,22 @@ export class OpenId4VciHolderService { options: { resolvedCredentialOffer: OpenId4VciResolvedCredentialOffer acceptCredentialOfferOptions: OpenId4VciAcceptCredentialOfferOptions - resolvedAuthorizationRequestWithCode?: OpenId4VciResolvedAuthorizationRequestWithCode - accessToken?: string + accessToken: string cNonce?: string - dpop?: { jwk: Jwk; nonce?: string } + dpop?: OpenId4VciDpopRequestOptions clientId?: string } ) { const { resolvedCredentialOffer, acceptCredentialOfferOptions } = options - const { metadata, version, offeredCredentialConfigurations } = resolvedCredentialOffer + const { metadata, offeredCredentialConfigurations } = resolvedCredentialOffer + const { credentialConfigurationIds, credentialBindingResolver, verifyCredentialStatus, requestBatch } = + acceptCredentialOfferOptions + const client = this.getClient(agentContext) - const { credentialsToRequest, credentialBindingResolver, verifyCredentialStatus } = acceptCredentialOfferOptions - - if (credentialsToRequest?.length === 0) { - this.logger.warn(`Accepting 0 credential offers. Returning`) - return [] + if (credentialConfigurationIds?.length === 0) { + throw new CredoError(`'credentialConfigurationIds' may not be empty`) } - this.logger.info( - `Accepting the following credential offers '${credentialsToRequest ? credentialsToRequest.join(', ') : 'all'}` - ) - const supportedJwaSignatureAlgorithms = getSupportedJwaSignatureAlgorithms(agentContext) const allowedProofOfPossessionSigAlgs = acceptCredentialOfferOptions.allowedProofOfPossessionSignatureAlgorithms @@ -420,26 +351,12 @@ export class OpenId4VciHolderService { ) } - const tokenRequestOptions = { - resolvedCredentialOffer, - resolvedAuthorizationRequest: options.resolvedAuthorizationRequestWithCode, - code: options.resolvedAuthorizationRequestWithCode?.code, - txCode: acceptCredentialOfferOptions.userPin, - } as OpenId4VciTokenRequestOptions - - const tokenResponse = options.accessToken - ? { - access_token: options.accessToken, - c_nonce: options.cNonce, - dpop: options.dpop, - } - : await this.requestAccessToken(agentContext, tokenRequestOptions) - const receivedCredentials: Array = [] - let newCNonce: string | undefined + let cNonce = options.cNonce + let dpopNonce = options.dpop?.nonce - const credentialConfigurationToRequest = - credentialsToRequest?.map((id) => { + const credentialConfigurationsToRequest = + credentialConfigurationIds?.map((id) => { if (!offeredCredentialConfigurations[id]) { const offeredCredentialIds = Object.keys(offeredCredentialConfigurations).join(', ') throw new CredoError( @@ -449,9 +366,48 @@ export class OpenId4VciHolderService { return [id, offeredCredentialConfigurations[id]] as const }) ?? Object.entries(offeredCredentialConfigurations) - for (const [offeredCredentialId, offeredCredentialConfiguration] of credentialConfigurationToRequest) { + // If we don't have a nonce yet, we need to first get one + if (!cNonce) { + // Best option is to use nonce endpoint (draft 14+) + if (!metadata.credentialIssuer.nonce_endpoint) { + const nonceResponse = await client.requestNonce({ issuerMetadata: metadata }) + cNonce = nonceResponse.c_nonce + } else { + // Otherwise we will send a dummy request + await client + .retrieveCredentials({ + issuerMetadata: metadata, + accessToken: options.accessToken, + credentialConfigurationId: credentialConfigurationsToRequest[0][0], + dpop: options.dpop + ? await this.getDpopOptions(agentContext, { + ...options.dpop, + nonce: dpopNonce, + dpopSigningAlgValuesSupported: [options.dpop.alg], + }) + : undefined, + }) + .catch((e) => { + if (e instanceof Oid4vciRetrieveCredentialsError && e.response.credentialErrorResponseResult?.success) { + cNonce = e.response.credentialErrorResponseResult.output.c_nonce + } + }) + } + } + + if (!cNonce) { + throw new CredoError('No cNonce provided and unable to acquire cNonce from the credential issuer') + } + + // If true: use max from issuer or otherwise 1 + // If number not 0: use the number + // Else: use 1 + const batchSize = + requestBatch === true ? metadata.credentialIssuer.batch_credential_issuance?.batch_size ?? 1 : requestBatch || 1 + + for (const [offeredCredentialId, offeredCredentialConfiguration] of credentialConfigurationsToRequest) { // Get all options for the credential request (such as which kid to use, the signature algorithm, etc) - const { credentialBinding, signatureAlgorithm } = await this.getCredentialRequestOptions(agentContext, { + const { jwtSigner } = await this.getCredentialRequestOptions(agentContext, { possibleProofOfPossessionSignatureAlgorithms: possibleProofOfPossessionSigAlgs, offeredCredential: { id: offeredCredentialId, @@ -460,82 +416,71 @@ export class OpenId4VciHolderService { credentialBindingResolver, }) - // Create the proof of possession - const proofOfPossessionBuilder = ProofOfPossessionBuilder.fromAccessTokenResponse({ - accessTokenResponse: tokenResponse, - callbacks: { signCallback: this.proofOfPossessionSignCallback(agentContext) }, - version, - }) - .withEndpointMetadata(metadata) - .withAlg(signatureAlgorithm) - - // TODO: what if auth flow using did, and the did is different from client id. We now use the client_id - if (credentialBinding.method === 'did') { - proofOfPossessionBuilder.withClientId(parseDid(credentialBinding.didUrl).did).withKid(credentialBinding.didUrl) - } else if (credentialBinding.method === 'jwk') { - proofOfPossessionBuilder.withJWK(credentialBinding.jwk.toJson()) + const jwts: string[] = [] + for (let i = 0; i < batchSize; i++) { + const { jwt } = await client.createCredentialRequestJwtProof({ + credentialConfigurationId: offeredCredentialId, + issuerMetadata: resolvedCredentialOffer.metadata, + signer: jwtSigner, + clientId: options.clientId, + nonce: cNonce, + }) + this.logger.debug('Generated credential request proof of possesion jwt', { jwt }) + jwts.push(jwt) } - // Add client id if in auth flow. This may override the clientId from the did binding method. But according to spec, - // the "value of this claim MUST be the client_id of the Client making the Credential request." - if (options.clientId || options.resolvedAuthorizationRequestWithCode?.clientId) { - proofOfPossessionBuilder.withClientId( - (options.clientId || options.resolvedAuthorizationRequestWithCode?.clientId) as string - ) - } - - if (newCNonce) proofOfPossessionBuilder.withAccessTokenNonce(newCNonce) - - const proofOfPossession = await proofOfPossessionBuilder.build() - this.logger.debug('Generated JWS', proofOfPossession) - - // Acquire the credential - const credentialRequestBuilder = CredentialRequestClientBuilder.fromCredentialOffer({ - credentialOffer: resolvedCredentialOffer.credentialOfferRequestWithBaseUrl, - metadata: resolvedCredentialOffer.metadata, - }) - credentialRequestBuilder - .withVersion(version) - .withCredentialEndpoint(metadata.credential_endpoint) - .withToken(tokenResponse.access_token) - - const credentialRequestClient = credentialRequestBuilder.build() - // retrieve the @context from the credential configuration - const context = - offeredCredentialConfiguration.format === OpenId4VciCredentialFormatProfile.JwtVcJsonLd || - offeredCredentialConfiguration.format === OpenId4VciCredentialFormatProfile.LdpVc - ? offeredCredentialConfiguration.credential_definition['@context'] - : undefined - - const createDpopOpts = tokenResponse.dpop - ? await this.getCreateDpopOptions(agentContext, metadata, { - jwk: tokenResponse.dpop.jwk, - jwtPayloadProps: { accessToken: tokenResponse.access_token, nonce: tokenResponse.dpop?.nonce }, - }) - : undefined - - const credentialResponse = await credentialRequestClient.acquireCredentialsUsingProof({ - proofInput: proofOfPossession, - credentialTypes: getTypesFromCredentialSupported(offeredCredentialConfiguration), - format: offeredCredentialConfiguration.format, - createDPoPOpts: createDpopOpts, - context: context, + const { credentialResponse, dpop } = await client.retrieveCredentials({ + issuerMetadata: metadata, + accessToken: options.accessToken, + credentialConfigurationId: offeredCredentialId, + dpop: options.dpop + ? await this.getDpopOptions(agentContext, { + ...options.dpop, + nonce: dpopNonce, + dpopSigningAlgValuesSupported: [options.dpop.alg], + }) + : undefined, + proofs: batchSize > 1 ? { jwt: jwts } : undefined, + proof: + batchSize === 1 + ? { + proof_type: 'jwt', + jwt: jwts[0], + } + : undefined, }) - newCNonce = credentialResponse.successBody?.c_nonce + // Set new nonce values + cNonce = credentialResponse.c_nonce + dpopNonce = dpop?.nonce // Create credential, but we don't store it yet (only after the user has accepted the credential) const credential = await this.handleCredentialResponse(agentContext, credentialResponse, { verifyCredentialStatus: verifyCredentialStatus ?? false, - credentialIssuerMetadata: metadata.credentialIssuerMetadata, + credentialIssuerMetadata: metadata.credentialIssuer, format: offeredCredentialConfiguration.format as OpenId4VciCredentialFormatProfile, + credentialConfigurationId: offeredCredentialId, }) - this.logger.debug('Full credential', credential) - receivedCredentials.push(credential) + this.logger.debug( + 'received credential', + credential.credentials.map((c) => + c instanceof Mdoc ? { issuerSignedNamespaces: c.issuerSignedNamespaces, base64Url: c.base64Url } : c + ) + ) + receivedCredentials.push({ ...credential, credentialConfigurationId: offeredCredentialId }) } - return receivedCredentials + return { + credentials: receivedCredentials, + dpop: options.dpop + ? { + ...options.dpop, + nonce: dpopNonce, + } + : undefined, + cNonce, + } } /** @@ -555,63 +500,109 @@ export class OpenId4VciHolderService { } } ) { - const { signatureAlgorithm, supportedDidMethods, supportsAllDidMethods, supportsJwk } = + const { signatureAlgorithms, supportedDidMethods, supportsAllDidMethods, supportsJwk } = this.getProofOfPossessionRequirements(agentContext, { credentialToRequest: options.offeredCredential, possibleProofOfPossessionSignatureAlgorithms: options.possibleProofOfPossessionSignatureAlgorithms, }) - const JwkClass = getJwkClassFromJwaSignatureAlgorithm(signatureAlgorithm) - if (!JwkClass) { - throw new CredoError(`Could not determine JWK key type of the JWA signature algorithm '${signatureAlgorithm}'`) - } - - const supportedVerificationMethods = getSupportedVerificationMethodTypesFromKeyType(JwkClass.keyType) + const JwkClasses = signatureAlgorithms.map((signatureAlgorithm) => { + const JwkClass = getJwkClassFromJwaSignatureAlgorithm(signatureAlgorithm) + if (!JwkClass) { + throw new CredoError(`Could not determine JWK key type of the JWA signature algorithm '${signatureAlgorithm}'`) + } + return JwkClass + }) + const keyTypes = JwkClasses.map((JwkClass): KeyType => JwkClass.keyType) + const supportedVerificationMethods = keyTypes.flatMap((keyType) => + getSupportedVerificationMethodTypesFromKeyType(keyType) + ) const format = options.offeredCredential.configuration.format as OpenId4VciSupportedCredentialFormats + const supportsAnyMethod = supportedDidMethods !== undefined || supportsAllDidMethods || supportsJwk // Now we need to determine how the credential will be bound to us const credentialBinding = await options.credentialBindingResolver({ + agentContext, credentialFormat: format, - signatureAlgorithm, + signatureAlgorithms, supportedVerificationMethods, - keyType: JwkClass.keyType, - supportedCredentialId: options.offeredCredential.id, + keyTypes: JwkClasses.map((JwkClass): KeyType => JwkClass.keyType), + credentialConfigurationId: options.offeredCredential.id, supportsAllDidMethods, supportedDidMethods, supportsJwk, }) + let jwk: Jwk // Make sure the issuer of proof of possession is valid according to openid issuer metadata - if ( - credentialBinding.method === 'did' && - !supportsAllDidMethods && - // If supportedDidMethods is undefined, it means the issuer didn't include the binding methods in the metadata - // The user can still select a verification method, but we can't validate it - supportedDidMethods !== undefined && - !supportedDidMethods.find((supportedDidMethod) => credentialBinding.didUrl.startsWith(supportedDidMethod)) - ) { - const { method } = parseDid(credentialBinding.didUrl) - const supportedDidMethodsString = supportedDidMethods.join(', ') - throw new CredoError( - `Resolved credential binding for proof of possession uses did method '${method}', but issuer only supports '${supportedDidMethodsString}'` - ) - } else if (credentialBinding.method === 'jwk' && !supportsJwk) { - throw new CredoError( - `Resolved credential binding for proof of possession uses jwk, but openid issuer does not support 'jwk' or 'cose_key' cryptographic binding method` - ) + if (credentialBinding.method === 'did') { + // Test binding method + if ( + !supportsAllDidMethods && + // If supportedDidMethods is undefined, it means the issuer didn't include the binding methods in the metadata + // The user can still select a verification method, but we can't validate it + supportedDidMethods !== undefined && + !supportedDidMethods.find( + (supportedDidMethod) => credentialBinding.didUrl.startsWith(supportedDidMethod) && supportsAnyMethod + ) + ) { + const { method } = parseDid(credentialBinding.didUrl) + const supportedDidMethodsString = supportedDidMethods.join(', ') + throw new CredoError( + `Resolved credential binding for proof of possession uses did method '${method}', but issuer only supports '${supportedDidMethodsString}'` + ) + } + + const key = await getKeyFromDid(agentContext, credentialBinding.didUrl) + jwk = getJwkFromKey(key) + if (!keyTypes.includes(key.keyType)) { + throw new CredoError( + `Credential binding returned did url that points to key with type '${ + key.keyType + }', but one of '${keyTypes.join(', ')}' was expected` + ) + } + } else if (credentialBinding.method === 'jwk') { + if (!supportsJwk && supportsAnyMethod) { + throw new CredoError( + `Resolved credential binding for proof of possession uses jwk, but openid issuer does not support 'jwk' or 'cose_key' cryptographic binding method` + ) + } + + jwk = credentialBinding.jwk + if (!keyTypes.includes(credentialBinding.jwk.key.keyType)) { + throw new CredoError( + `Credential binding returned jwk with key with type '${ + credentialBinding.jwk.key.keyType + }', but one of '${keyTypes.join(', ')}' was expected` + ) + } + } else { + // @ts-expect-error currently if/else if exhaustive, but once we add new option it will give ts error + throw new CredoError(`Unsupported credential binding method ${credentialBinding.method}`) } - // FIXME: we don't have the verification method here - // Make sure the verification method uses a supported verification method type - // if (!supportedVerificationMethods.includes(verificationMethod.type)) { - // const supportedVerificationMethodsString = supportedVerificationMethods.join(', ') - // throw new CredoError( - // `Verification method uses verification method type '${verificationMethod.type}', but only '${supportedVerificationMethodsString}' verification methods are supported for key type '${JwkClass.keyType}'` - // ) - // } + const alg = jwk.supportedSignatureAlgorithms.find((alg) => signatureAlgorithms.includes(alg)) + if (!alg) { + // Should not happen, to make ts happy + throw new CredoError(`Unable to determine alg for key type ${jwk.keyType}`) + } + + const jwtSigner: JwtSigner = + credentialBinding.method === 'did' + ? { + method: credentialBinding.method, + didUrl: credentialBinding.didUrl, + alg, + } + : { + method: 'jwk', + publicJwk: credentialBinding.jwk.toJson(), + alg, + } - return { credentialBinding, signatureAlgorithm } + return { credentialBinding, signatureAlgorithm: alg, jwtSigner } } /** @@ -649,7 +640,7 @@ export class OpenId4VciHolderService { // For each of the supported algs, find the key types, then find the proof types const signatureSuiteRegistry = agentContext.dependencyManager.resolve(SignatureSuiteRegistry) - let signatureAlgorithm: JwaSignatureAlgorithm | undefined + let signatureAlgorithms: JwaSignatureAlgorithm[] = [] if (credentialToRequest.configuration.proof_types_supported) { if (!credentialToRequest.configuration.proof_types_supported.jwt) { @@ -667,19 +658,19 @@ export class OpenId4VciHolderService { // If undefined, it means the issuer didn't include the cryptographic suites in the metadata // We just guess that the first one is supported if (proofSigningAlgsSupported === undefined) { - signatureAlgorithm = options.possibleProofOfPossessionSignatureAlgorithms[0] + signatureAlgorithms = options.possibleProofOfPossessionSignatureAlgorithms } else { switch (credentialToRequest.configuration.format) { case OpenId4VciCredentialFormatProfile.JwtVcJson: case OpenId4VciCredentialFormatProfile.JwtVcJsonLd: case OpenId4VciCredentialFormatProfile.SdJwtVc: case OpenId4VciCredentialFormatProfile.MsoMdoc: - signatureAlgorithm = options.possibleProofOfPossessionSignatureAlgorithms.find((signatureAlgorithm) => + signatureAlgorithms = options.possibleProofOfPossessionSignatureAlgorithms.filter((signatureAlgorithm) => proofSigningAlgsSupported.includes(signatureAlgorithm) ) break case OpenId4VciCredentialFormatProfile.LdpVc: - signatureAlgorithm = options.possibleProofOfPossessionSignatureAlgorithms.find((signatureAlgorithm) => { + signatureAlgorithms = options.possibleProofOfPossessionSignatureAlgorithms.filter((signatureAlgorithm) => { const JwkClass = getJwkClassFromJwaSignatureAlgorithm(signatureAlgorithm) if (!JwkClass) return false @@ -694,9 +685,13 @@ export class OpenId4VciHolderService { } } - if (!signatureAlgorithm) { + if (signatureAlgorithms.length === 0) { throw new CredoError( - `Could not establish signature algorithm for format ${credentialToRequest.configuration.format} and id ${credentialToRequest.id}` + `Could not establish signature algorithm for format ${credentialToRequest.configuration.format} and id ${ + credentialToRequest.id + }. Server supported signature algorithms are '${ + proofSigningAlgsSupported?.join(', ') ?? 'Not defined' + }', available are '${options.possibleProofOfPossessionSignatureAlgorithms.join(', ')}'` ) } @@ -709,7 +704,7 @@ export class OpenId4VciHolderService { const supportsJwk = issuerSupportedBindingMethods?.includes('jwk') || supportsCoseKey return { - signatureAlgorithm, + signatureAlgorithms, supportedDidMethods, supportsAllDidMethods, supportsJwk, @@ -718,157 +713,167 @@ export class OpenId4VciHolderService { private async handleCredentialResponse( agentContext: AgentContext, - credentialResponse: OpenIDResponse, + credentialResponse: CredentialResponse, options: { verifyCredentialStatus: boolean - credentialIssuerMetadata: OpenId4VciIssuerMetadata + credentialIssuerMetadata: OpenId4VciCredentialIssuerMetadata format: OpenId4VciCredentialFormatProfile + credentialConfigurationId: string } ): Promise { - const { verifyCredentialStatus, credentialIssuerMetadata } = options + const { verifyCredentialStatus, credentialConfigurationId } = options + this.logger.debug('Credential response', credentialResponse) - this.logger.debug('Credential request response', credentialResponse) - - if (!credentialResponse.successBody || !credentialResponse.successBody.credential) { - throw new CredoError( - `Did not receive a successful credential response. ${credentialResponse.errorBody?.error}: ${credentialResponse.errorBody?.error_description}` - ) + const credentials = + credentialResponse.credentials ?? (credentialResponse.credential ? [credentialResponse.credential] : undefined) + if (!credentials) { + throw new CredoError(`Credential response returned neither 'credentials' nor 'credential' parameter.`) } - const notificationMetadata = - credentialIssuerMetadata.notification_endpoint && credentialResponse.successBody.notification_id - ? { - notificationEndpoint: credentialIssuerMetadata.notification_endpoint, - notificationId: credentialResponse.successBody.notification_id, - } - : undefined + const notificationId = credentialResponse.notification_id const format = options.format if (format === OpenId4VciCredentialFormatProfile.SdJwtVc) { - if (typeof credentialResponse.successBody.credential !== 'string') + if (!credentials.every((c) => typeof c === 'string')) { throw new CredoError( - `Received a credential of format ${ - OpenId4VciCredentialFormatProfile.SdJwtVc - }, but the credential is not a string. ${JSON.stringify(credentialResponse.successBody.credential)}` + `Received credential(s) of format ${format}, but not all credential(s) are a string. ${JSON.stringify( + credentials + )}` ) + } const sdJwtVcApi = agentContext.dependencyManager.resolve(SdJwtVcApi) - const verificationResult = await sdJwtVcApi.verify({ - compactSdJwtVc: credentialResponse.successBody.credential, - }) + const verificationResults = await Promise.all( + credentials.map((compactSdJwtVc) => + sdJwtVcApi.verify({ + compactSdJwtVc, + }) + ) + ) - if (!verificationResult.isValid) { - agentContext.config.logger.error('Failed to validate credential', { verificationResult }) - throw new CredoError(`Failed to validate sd-jwt-vc credential. Results = ${JSON.stringify(verificationResult)}`) + if (!verificationResults.every((result) => result.isValid)) { + agentContext.config.logger.error('Failed to validate credential(s)', { verificationResults }) + throw new CredoError( + `Failed to validate sd-jwt-vc credentials. Results = ${JSON.stringify(verificationResults)}` + ) } - return { credential: verificationResult.sdJwtVc, notificationMetadata } + return { + credentials: verificationResults.map((result) => result.sdJwtVc), + notificationId, + credentialConfigurationId, + } } else if ( - format === OpenId4VciCredentialFormatProfile.JwtVcJson || - format === OpenId4VciCredentialFormatProfile.JwtVcJsonLd + options.format === OpenId4VciCredentialFormatProfile.JwtVcJson || + options.format === OpenId4VciCredentialFormatProfile.JwtVcJsonLd ) { - const credential = W3cJwtVerifiableCredential.fromSerializedJwt( - credentialResponse.successBody.credential as string - ) - const result = await this.w3cCredentialService.verifyCredential(agentContext, { - credential, - verifyCredentialStatus, - }) - if (!result.isValid) { - agentContext.config.logger.error('Failed to validate credential', { result }) - throw new CredoError(`Failed to validate credential, error = ${result.error?.message ?? 'Unknown'}`) + if (!credentials.every((c) => typeof c === 'string')) { + throw new CredoError( + `Received credential(s) of format ${format}, but not all credential(s) are a string. ${JSON.stringify( + credentials + )}` + ) } - return { credential, notificationMetadata } - } else if (format === OpenId4VciCredentialFormatProfile.LdpVc) { - const credential = W3cJsonLdVerifiableCredential.fromJson( - credentialResponse.successBody.credential as Record + const result = await Promise.all( + credentials.map(async (c) => { + const credential = W3cJwtVerifiableCredential.fromSerializedJwt(c) + const result = await this.w3cCredentialService.verifyCredential(agentContext, { + credential, + verifyCredentialStatus, + }) + + return { credential, result } + }) ) - const result = await this.w3cCredentialService.verifyCredential(agentContext, { - credential, - verifyCredentialStatus, - }) - if (!result.isValid) { - agentContext.config.logger.error('Failed to validate credential', { result }) - throw new CredoError(`Failed to validate credential, error = ${result.error?.message ?? 'Unknown'}`) - } - return { credential, notificationMetadata } - } else if (format === OpenId4VciCredentialFormatProfile.MsoMdoc) { - if (typeof credentialResponse.successBody.credential !== 'string') + if (!result.every((c) => c.result.isValid)) { + agentContext.config.logger.error('Failed to validate credentials', { result }) throw new CredoError( - `Received a credential of format ${ - OpenId4VciCredentialFormatProfile.MsoMdoc - }, but the credential is not a string. ${JSON.stringify(credentialResponse.successBody.credential)}` + `Failed to validate credential, error = ${result + .map((e) => e.result.error?.message) + .filter(Boolean) + .join(', ')}` ) - - const mdocApi = agentContext.dependencyManager.resolve(MdocApi) - const mdoc = Mdoc.fromBase64Url(credentialResponse.successBody.credential) - const verificationResult = await mdocApi.verify(mdoc, {}) - - if (!verificationResult.isValid) { - agentContext.config.logger.error('Failed to validate credential', { verificationResult }) - throw new CredoError(`Failed to validate mdoc credential. Results = ${verificationResult.error}`) } - return { credential: mdoc, notificationMetadata } - } - - throw new CredoError(`Unsupported credential format ${credentialResponse.successBody.format}`) - } - - private proofOfPossessionSignCallback(agentContext: AgentContext) { - return async (jwt: Jwt, kid?: string) => { - if (!jwt.header) throw new CredoError('No header present on JWT') - if (!jwt.payload) throw new CredoError('No payload present on JWT') - if (kid && jwt.header.jwk) { - throw new CredoError('Both KID and JWK are present in the callback. Only one can be present') + return { credentials: result.map((r) => r.credential), notificationId, credentialConfigurationId } + } else if (format === OpenId4VciCredentialFormatProfile.LdpVc) { + if (!credentials.every((c) => typeof c === 'object')) { + throw new CredoError( + `Received credential(s) of format ${format}, but not all credential(s) are an object. ${JSON.stringify( + credentials + )}` + ) } + const result = await Promise.all( + credentials.map(async (c) => { + const credential = W3cJsonLdVerifiableCredential.fromJson(c) + const result = await this.w3cCredentialService.verifyCredential(agentContext, { + credential, + verifyCredentialStatus, + }) - let key: Key - - if (kid) { - if (!kid.startsWith('did:')) { - throw new CredoError(`kid '${kid}' is not a DID. Only dids are supported for kid`) - } else if (!kid.includes('#')) { - throw new CredoError( - `kid '${kid}' does not contain a fragment. kid MUST point to a specific key in the did document.` - ) - } + return { credential, result } + }) + ) - const didsApi = agentContext.dependencyManager.resolve(DidsApi) - const didDocument = await didsApi.resolveDidDocument(kid) - const verificationMethod = didDocument.dereferenceKey(kid, ['authentication']) + if (!result.every((c) => c.result.isValid)) { + agentContext.config.logger.error('Failed to validate credentials', { result }) + throw new CredoError( + `Failed to validate credential, error = ${result + .map((e) => e.result.error?.message) + .filter(Boolean) + .join(', ')}` + ) + } - key = getKeyFromVerificationMethod(verificationMethod) - } else if (jwt.header.jwk) { - key = getJwkFromJson(jwt.header.jwk as JwkJson).key - } else { - throw new CredoError('No KID or JWK is present in the callback') + return { credentials: result.map((r) => r.credential), notificationId, credentialConfigurationId } + } else if (format === OpenId4VciCredentialFormatProfile.MsoMdoc) { + if (!credentials.every((c) => typeof c === 'string')) { + throw new CredoError( + `Received credential(s) of format ${format}, but not all credential(s) are a string. ${JSON.stringify( + credentials + )}` + ) } + const mdocApi = agentContext.dependencyManager.resolve(MdocApi) + const result = await Promise.all( + credentials.map(async (credential) => { + const mdoc = Mdoc.fromBase64Url(credential) + const result = await mdocApi.verify(mdoc, {}) + return { + result, + mdoc, + } + }) + ) - const jwk = getJwkFromKey(key) - if (!jwk.supportsSignatureAlgorithm(jwt.header.alg)) { - throw new CredoError(`key type '${jwk.keyType}', does not support the JWS signature alg '${jwt.header.alg}'`) + if (!result.every((r) => r.result.isValid)) { + agentContext.config.logger.error('Failed to validate credentials', { result }) + throw new CredoError( + `Failed to validate mdoc credential(s). \n - ${result + .map((r, i) => (r.result.isValid ? undefined : `(${i}) ${r.result.error}`)) + .filter(Boolean) + .join('\n - ')}` + ) } - // We don't support these properties, remove them, so we can pass all other header properties to the JWS service - if (jwt.header.x5c) throw new CredoError('x5c is not supported') + return { credentials: result.map((c) => c.mdoc), notificationId, credentialConfigurationId } + } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { x5c: _x5c, ...supportedHeaderOptions } = jwt.header + throw new CredoError(`Unsupported credential format ${options.format}`) + } - const jws = await this.jwsService.createJwsCompact(agentContext, { - key, - payload: JsonEncoder.toBuffer(jwt.payload), - protectedHeaderOptions: { - ...supportedHeaderOptions, - // only pass jwk if it was present in the header - jwk: jwt.header.jwk ? jwk : undefined, - }, - }) + private getClient(agentContext: AgentContext) { + return new Oid4vciClient({ + callbacks: getOid4vciCallbacks(agentContext), + }) + } - return jws - } + private getOauth2Client(agentContext: AgentContext) { + return new Oauth2Client({ + callbacks: getOid4vciCallbacks(agentContext), + }) } } diff --git a/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderServiceOptions.ts b/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderServiceOptions.ts index bc75187add..90678436e2 100644 --- a/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderServiceOptions.ts +++ b/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderServiceOptions.ts @@ -1,21 +1,16 @@ import type { OpenId4VcCredentialHolderBinding, - OpenId4VciCredentialSupportedWithId, - OpenId4VciIssuerMetadata, - OpenId4VciCredentialOfferPayload, - OpenId4VciCredentialConfigurationsSupported, + OpenId4VciCredentialConfigurationsSupportedWithFormats, } from '../shared' -import type { JwaSignatureAlgorithm, Jwk, KeyType } from '@credo-ts/core' -import type { VerifiableCredential } from '@credo-ts/core/src/modules/dif-presentation-exchange/models/index' -import type { - AccessTokenResponse, - CredentialOfferRequestWithBaseUrl, - EndpointMetadataResult, - OpenId4VCIVersion, -} from '@sphereon/oid4vci-common' +import type { CredentialOfferObject, IssuerMetadataResult } from '@animo-id/oid4vci' +import type { AgentContext, JwaSignatureAlgorithm, Jwk, KeyType, VerifiableCredential } from '@credo-ts/core' + +import { AuthorizationFlow as OpenId4VciAuthorizationFlow } from '@animo-id/oid4vci' import { OpenId4VciCredentialFormatProfile } from '../shared/models/OpenId4VciCredentialFormatProfile' +export { OpenId4VciAuthorizationFlow } + export type OpenId4VciSupportedCredentialFormats = | OpenId4VciCredentialFormatProfile.JwtVcJson | OpenId4VciCredentialFormatProfile.JwtVcJsonLd @@ -31,9 +26,10 @@ export const openId4VciSupportedCredentialFormats: OpenId4VciSupportedCredential OpenId4VciCredentialFormatProfile.MsoMdoc, ] -export interface OpenId4VciNotificationMetadata { - notificationId: string - notificationEndpoint: string +export interface OpenId4VciDpopRequestOptions { + jwk: Jwk + alg: JwaSignatureAlgorithm + nonce?: string } /** @@ -43,44 +39,47 @@ export interface OpenId4VciNotificationMetadata { */ export type OpenId4VciNotificationEvent = 'credential_accepted' | 'credential_failure' | 'credential_deleted' -export type OpenId4VciTokenResponse = Pick - export type OpenId4VciRequestTokenResponse = { accessToken: string cNonce?: string - dpop?: { jwk: Jwk; nonce?: string } + dpop?: OpenId4VciDpopRequestOptions } +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type UnionToArrayUnion = T extends any ? T[] : never + export interface OpenId4VciCredentialResponse { - credential: VerifiableCredential - notificationMetadata?: OpenId4VciNotificationMetadata + credentialConfigurationId: string + credentials: UnionToArrayUnion + notificationId?: string } export interface OpenId4VciResolvedCredentialOffer { - metadata: Omit & { - credentialIssuerMetadata: OpenId4VciIssuerMetadata - } - credentialOfferRequestWithBaseUrl: CredentialOfferRequestWithBaseUrl - credentialOfferPayload: OpenId4VciCredentialOfferPayload - offeredCredentials: OpenId4VciCredentialSupportedWithId[] - offeredCredentialConfigurations: OpenId4VciCredentialConfigurationsSupported - version: OpenId4VCIVersion -} + metadata: IssuerMetadataResult + credentialOfferPayload: CredentialOfferObject -export interface OpenId4VciResolvedAuthorizationRequest extends OpenId4VciAuthCodeFlowOptions { - codeVerifier: string - authorizationRequestUri: string + /** + * Offered credential configurations with known formats + */ + offeredCredentialConfigurations: OpenId4VciCredentialConfigurationsSupportedWithFormats } -export interface OpenId4VciResolvedAuthorizationRequestWithCode extends OpenId4VciResolvedAuthorizationRequest { - code: string -} +export type OpenId4VciResolvedAuthorizationRequest = + | { + oid4vpRequestUrl: string + authorizationFlow: OpenId4VciAuthorizationFlow.PresentationDuringIssuance + authSession: string + } + | { + authorizationRequestUrl: string + authorizationFlow: OpenId4VciAuthorizationFlow.Oauth2Redirect + codeVerifier?: string + } export interface OpenId4VciSendNotificationOptions { - /** - * The notification metadata received from @see requestCredential - */ - notificationMetadata: OpenId4VciNotificationMetadata + metadata: IssuerMetadataResult + + notificationId: string /** * The access token obtained through @see requestToken @@ -95,32 +94,51 @@ export interface OpenId4VciSendNotificationOptions { * 'credential_failure' otherwise. */ notificationEvent: OpenId4VciNotificationEvent -} -interface OpenId4VcTokenRequestBaseOptions { - resolvedCredentialOffer: OpenId4VciResolvedCredentialOffer - txCode?: string + dpop?: OpenId4VciDpopRequestOptions } -export interface OpenId4VcAuthorizationCodeTokenRequestOptions extends OpenId4VcTokenRequestBaseOptions { - resolvedAuthorizationRequest: OpenId4VciResolvedAuthorizationRequest +export interface OpenId4VcAuthorizationCodeTokenRequestOptions { + resolvedCredentialOffer: OpenId4VciResolvedCredentialOffer code: string + clientId: string + codeVerifier?: string + redirectUri?: string + + txCode?: never } -export interface OpenId4VciPreAuthorizedTokenRequestOptions extends OpenId4VcTokenRequestBaseOptions { - resolvedAuthorizationRequest?: never - code?: never +export interface OpenId4VciPreAuthorizedTokenRequestOptions { + resolvedCredentialOffer: OpenId4VciResolvedCredentialOffer + txCode?: string + + code?: undefined } export type OpenId4VciTokenRequestOptions = | OpenId4VciPreAuthorizedTokenRequestOptions | OpenId4VcAuthorizationCodeTokenRequestOptions +export interface OpenId4VciRetrieveAuthorizationCodeUsingPresentationOptions { + resolvedCredentialOffer: OpenId4VciResolvedCredentialOffer + dpop?: OpenId4VciDpopRequestOptions + + /** + * auth session returned at an earlier call to the authorization challenge endpoint + */ + authSession: string + + /** + * Presentation during issuance session returned by the verifier after submitting a valid presentation + */ + presentationDuringIssuanceSession?: string +} + export interface OpenId4VciCredentialRequestOptions extends Omit { resolvedCredentialOffer: OpenId4VciResolvedCredentialOffer accessToken: string cNonce?: string - dpop?: { jwk: Jwk; nonce?: string } + dpop?: OpenId4VciDpopRequestOptions /** * The client id used for authorization. Only required if authorization_code flow was used. @@ -134,17 +152,24 @@ export interface OpenId4VciCredentialRequestOptions extends Omit | undefined = undefined - try { - responseDetails = await response.text() - if (responseDetails.includes('{')) { - responseDetails = JSON.parse(responseDetails) - } - } catch (error) { - // no-op + const responseText = await response + .clone() + .text() + .catch(() => null) + const responseJson = (await response + .clone() + .json() + .catch(() => null)) as null | Record + + if (!response.ok) { + return { + ok: false, + serverResponse: { + status: response.status, + body: responseJson ?? responseText, + }, + submittedResponse: authorizationResponseWithCorrelationId.response.payload, + } as const } return { + ok: true, serverResponse: { status: response.status, - body: responseDetails, + body: responseJson ?? {}, }, submittedResponse: authorizationResponseWithCorrelationId.response.payload, - } + + redirectUri: responseJson?.redirect_uri as string | undefined, + presentationDuringIssuanceSession: responseJson?.presentation_during_issuance_session as string | undefined, + } as const } private async getOpenIdProvider(agentContext: AgentContext) { @@ -319,7 +332,6 @@ export class OpenId4VcSiopHolderService { authorizationRequest: VerifiedAuthorizationRequest, openIdTokenIssuer: OpenId4VcJwtIssuer ) { - // TODO: jwk thumbprint support const subjectSyntaxTypesSupported = authorizationRequest.registrationMetadataPayload.subject_syntax_types_supported if (!subjectSyntaxTypesSupported) { throw new CredoError( @@ -333,8 +345,10 @@ export class OpenId4VcSiopHolderService { // Either did: or did (for all did methods) is allowed allowedSubjectSyntaxTypes = [`did:${parsedDid.method}`, 'did'] + } else if (openIdTokenIssuer.method === 'jwk') { + allowedSubjectSyntaxTypes = ['urn:ietf:params:oauth:jwk-thumbprint'] } else { - throw new CredoError("Only 'did' is supported as openIdTokenIssuer at the moment") + throw new CredoError("Only 'did' and 'jwk' are supported as openIdTokenIssuer at the moment") } // At least one of the allowed subject syntax types must be supported by the RP diff --git a/packages/openid4vc/src/openid4vc-holder/OpenId4vcSiopHolderServiceOptions.ts b/packages/openid4vc/src/openid4vc-holder/OpenId4vcSiopHolderServiceOptions.ts index c59a9dd53f..3260d01f75 100644 --- a/packages/openid4vc/src/openid4vc-holder/OpenId4vcSiopHolderServiceOptions.ts +++ b/packages/openid4vc/src/openid4vc-holder/OpenId4vcSiopHolderServiceOptions.ts @@ -1,8 +1,4 @@ -import type { - OpenId4VcJwtIssuer, - OpenId4VcSiopVerifiedAuthorizationRequest, - OpenId4VcSiopAuthorizationResponsePayload, -} from '../shared' +import type { OpenId4VcJwtIssuer, OpenId4VcSiopVerifiedAuthorizationRequest } from '../shared' import type { DifPexCredentialsForRequest, DifPexInputDescriptorToCredentials, @@ -49,10 +45,3 @@ export interface OpenId4VcSiopAcceptAuthorizationRequestOptions { */ authorizationRequest: OpenId4VcSiopVerifiedAuthorizationRequest } - -// FIXME: rethink properties -export interface OpenId4VcSiopAuthorizationResponseSubmission { - ok: boolean - status: number - submittedResponse: OpenId4VcSiopAuthorizationResponsePayload -} diff --git a/packages/openid4vc/src/openid4vc-holder/__tests__/fixtures.ts b/packages/openid4vc/src/openid4vc-holder/__tests__/fixtures.ts index 6f5b58d71c..0a55c7b157 100644 --- a/packages/openid4vc/src/openid4vc-holder/__tests__/fixtures.ts +++ b/packages/openid4vc/src/openid4vc-holder/__tests__/fixtures.ts @@ -3,6 +3,7 @@ export const matrrLaunchpadDraft11JwtVcJson = { 'openid-credential-offer://?credential_offer=%7B%22credential_issuer%22%3A%22https%3A%2F%2Flaunchpad.vii.electron.mattrlabs.io%22%2C%22credentials%22%3A%5B%22613ecbbb-0a4c-4041-bb78-c64943139d5f%22%5D%2C%22grants%22%3A%7B%22urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Apre-authorized_code%22%3A%7B%22pre-authorized_code%22%3A%22Jd6TUmLJct1DNyJpKKmt0i85scznBoJrEe_y_SlMW0j%22%7D%7D%7D', getMetadataResponse: { issuer: 'https://launchpad.vii.electron.mattrlabs.io', + credential_issuer: 'https://launchpad.vii.electron.mattrlabs.io', authorization_endpoint: 'https://launchpad.vii.electron.mattrlabs.io/oidc/v1/auth/authorize', token_endpoint: 'https://launchpad.vii.electron.mattrlabs.io/oidc/v1/auth/token', jwks_uri: 'https://launchpad.vii.electron.mattrlabs.io/oidc/v1/auth/jwks', @@ -24,6 +25,8 @@ export const matrrLaunchpadDraft11JwtVcJson = { { id: 'd2662472-891c-413d-b3c6-e2f0109001c5', format: 'ldp_vc', + // TODO: should we make this optional? Seems a lot of types break on this + '@context': [], types: ['VerifiableCredential', 'OpenBadgeCredential'], cryptographic_binding_methods_supported: ['did:key'], cryptographic_suites_supported: ['Ed25519Signature2018'], @@ -39,6 +42,8 @@ export const matrrLaunchpadDraft11JwtVcJson = { { id: 'b4c4cdf5-ccc9-4945-8c19-9334558653b2', format: 'ldp_vc', + // TODO: should we make this optional? Seems a lot of types break on this + '@context': [], types: ['VerifiableCredential', 'Passport'], cryptographic_binding_methods_supported: ['did:key'], cryptographic_suites_supported: ['Ed25519Signature2018'], @@ -116,6 +121,7 @@ export const matrrLaunchpadDraft11JwtVcJson = { expires_in: 3600, scope: 'OpenBadgeCredential', token_type: 'Bearer', + c_nonce: '4fb1359d-e9cd-44f2-a2b0-739162b68760', }, credentialResponse: { @@ -140,7 +146,7 @@ export const waltIdDraft11JwtVcJson = { response_modes_supported: ['query', 'fragment'], grant_types_supported: ['authorization_code', 'urn:ietf:params:oauth:grant-type:pre-authorized_code'], subject_types_supported: ['public'], - credential_issuer: 'https://issuer.portal.walt.id/.well-known/openid-credential-issuer', + credential_issuer: 'https://issuer.portal.walt.id', credential_endpoint: 'https://issuer.portal.walt.id/credential', credentials_supported: [ { @@ -247,7 +253,7 @@ export const waltIdDraft11JwtVcJson = { par: { request_uri: 'urn:ietf:params:oauth:request_uri:738f2ac2-18ac-4162-b0a8-5e0e6ba2270b', - expires_in: 'PT3M46.132011234S', + expires_in: 132011234, }, credentialResponse: { diff --git a/packages/openid4vc/src/openid4vc-holder/__tests__/openid4vci-holder.test.ts b/packages/openid4vc/src/openid4vc-holder/__tests__/openid4vci-holder.test.ts index 2d37bfd355..6e81e72167 100644 --- a/packages/openid4vc/src/openid4vc-holder/__tests__/openid4vci-holder.test.ts +++ b/packages/openid4vc/src/openid4vc-holder/__tests__/openid4vci-holder.test.ts @@ -15,6 +15,7 @@ import { AskarModule } from '../../../../askar/src' import { askarModuleConfig } from '../../../../askar/tests/helpers' import { agentDependencies } from '../../../../node/src' import { OpenId4VcHolderModule } from '../OpenId4VcHolderModule' +import { OpenId4VciAuthorizationFlow } from '../OpenId4VciHolderServiceOptions' import { animoOpenIdPlaygroundDraft11SdJwtVc, matrrLaunchpadDraft11JwtVcJson, waltIdDraft11JwtVcJson } from './fixtures' @@ -96,17 +97,25 @@ describe('OpenId4VcHolder', () => { .reply(200, fixture.wellKnownDid) const resolved = await holder.modules.openId4VcHolder.resolveCredentialOffer(fixture.credentialOffer) - const credentials = await holder.modules.openId4VcHolder.acceptCredentialOfferUsingPreAuthorizedCode(resolved, { + const accessTokenResponse = await holder.modules.openId4VcHolder.requestToken({ + resolvedCredentialOffer: resolved, + }) + const credentialsResult = await holder.modules.openId4VcHolder.requestCredentials({ + resolvedCredentialOffer: resolved, + ...accessTokenResponse, + verifyCredentialStatus: false, // We only allow EdDSa, as we've created a did with keyType ed25519. If we create // or determine the did dynamically we could use any signature algorithm allowedProofOfPossessionSignatureAlgorithms: [JwaSignatureAlgorithm.EdDSA], - credentialsToRequest: resolved.offeredCredentials.filter((c) => c.format === 'jwt_vc_json').map((m) => m.id), + credentialConfigurationIds: Object.entries(resolved.offeredCredentialConfigurations) + .filter(([, configuration]) => configuration.format === 'jwt_vc_json') + .map(([id]) => id), credentialBindingResolver: () => ({ method: 'did', didUrl: holderVerificationMethod }), }) - expect(credentials).toHaveLength(1) - const w3cCredential = credentials[0] as W3cJwtVerifiableCredential + expect(credentialsResult.credentials).toHaveLength(1) + const w3cCredential = credentialsResult.credentials[0].credentials[0] as W3cJwtVerifiableCredential expect(w3cCredential).toBeInstanceOf(W3cJwtVerifiableCredential) expect(w3cCredential.credential.type).toEqual(['VerifiableCredential', 'OpenBadgeCredential']) @@ -137,19 +146,26 @@ describe('OpenId4VcHolder', () => { .reply(200, fixture.credentialResponse) const resolved = await holder.modules.openId4VcHolder.resolveCredentialOffer(fixture.credentialOfferPreAuth) + const accessTokenResponse = await holder.modules.openId4VcHolder.requestToken({ + resolvedCredentialOffer: resolved, + }) await expect(() => - holder.modules.openId4VcHolder.acceptCredentialOfferUsingPreAuthorizedCode(resolved, { + holder.modules.openId4VcHolder.requestCredentials({ + resolvedCredentialOffer: resolved, + ...accessTokenResponse, verifyCredentialStatus: false, // We only allow EdDSa, as we've created a did with keyType ed25519. If we create // or determine the did dynamically we could use any signature algorithm allowedProofOfPossessionSignatureAlgorithms: [JwaSignatureAlgorithm.EdDSA], - credentialsToRequest: resolved.offeredCredentials.filter((c) => c.format === 'jwt_vc_json').map((m) => m.id), + credentialConfigurationIds: Object.entries(resolved.offeredCredentialConfigurations) + .filter(([, configuration]) => configuration.format === 'jwt_vc_json') + .map(([id]) => id), credentialBindingResolver: () => ({ method: 'did', didUrl: holderVerificationMethod }), }) ) // FIXME: walt.id issues jwt where nbf and issuanceDate do not match - .rejects.toThrowError('JWT nbf and vc.issuanceDate do not match') + .rejects.toThrow('JWT nbf and vc.issuanceDate do not match') }) it('Should successfully receive credential from animo openid4vc playground using the pre-authorized flow using a jwk EdDSA subject and vc+sd-jwt credential', async () => { @@ -192,22 +208,24 @@ describe('OpenId4VcHolder', () => { // We only allow EdDSa, as we've created a did with keyType ed25519. If we create // or determine the did dynamically we could use any signature algorithm allowedProofOfPossessionSignatureAlgorithms: [JwaSignatureAlgorithm.EdDSA], - credentialsToRequest: resolvedCredentialOffer.offeredCredentials - .filter((c) => c.format === 'vc+sd-jwt') - .map((m) => m.id), + credentialConfigurationIds: Object.entries(resolvedCredentialOffer.offeredCredentialConfigurations) + .filter(([, configuration]) => configuration.format === 'vc+sd-jwt') + .map(([id]) => id), credentialBindingResolver: () => ({ method: 'jwk', jwk: getJwkFromKey(holderKey) }), }) - if (!credentialResponse[0]?.notificationMetadata) throw new Error("Notification metadata wasn't returned") + if (!credentialResponse.credentials[0]?.notificationId) throw new Error("Notification metadata wasn't returned") await holder.modules.openId4VcHolder.sendNotification({ accessToken: tokenResponse.accessToken, notificationEvent: 'credential_accepted', - notificationMetadata: credentialResponse[0].notificationMetadata, + notificationId: credentialResponse.credentials[0]?.notificationId, + metadata: resolvedCredentialOffer.metadata, + dpop: credentialResponse.dpop, }) - expect(credentialResponse).toHaveLength(1) - const credential = credentialResponse[0].credential as SdJwtVc + expect(credentialResponse.credentials).toHaveLength(1) + const credential = credentialResponse.credentials[0].credentials[0] as SdJwtVc expect(credential).toEqual({ compact: 'eyJhbGciOiJFZERTQSIsInR5cCI6InZjK3NkLWp3dCIsImtpZCI6IiN6Nk1raDVITlBDQ0pXWm42V1JMalJQdHR5dllaQnNrWlVkU0pmVGlad2NVU2llcXgifQ.eyJ2Y3QiOiJBbmltb09wZW5JZDRWY1BsYXlncm91bmQiLCJwbGF5Z3JvdW5kIjp7ImZyYW1ld29yayI6IkFyaWVzIEZyYW1ld29yayBKYXZhU2NyaXB0IiwiY3JlYXRlZEJ5IjoiQW5pbW8gU29sdXRpb25zIiwiX3NkIjpbImZZM0ZqUHpZSEZOcHlZZnRnVl9kX25DMlRHSVh4UnZocE00VHdrMk1yMDQiLCJwTnNqdmZJeVBZOEQwTks1c1l0alR2Nkc2R0FNVDNLTjdaZDNVNDAwZ1pZIl19LCJjbmYiOnsiandrIjp7Imt0eSI6Ik9LUCIsImNydiI6IkVkMjU1MTkiLCJ4Ijoia2MydGxwaGNadzFBSUt5a3pNNnBjY2k2UXNLQW9jWXpGTC01RmUzNmg2RSJ9fSwiaXNzIjoiZGlkOmtleTp6Nk1raDVITlBDQ0pXWm42V1JMalJQdHR5dllaQnNrWlVkU0pmVGlad2NVU2llcXgiLCJpYXQiOjE3MDU4NDM1NzQsIl9zZF9hbGciOiJzaGEtMjU2In0.2iAjaCFcuiHXTfQsrxXo6BghtwzqTrfDmhmarAAJAhY8r9yKXY3d10JY1dry2KnaEYWpq2R786thjdA5BXlPAQ~WyI5MzM3MTM0NzU4NDM3MjYyODY3NTE4NzkiLCJsYW5ndWFnZSIsIlR5cGVTY3JpcHQiXQ~WyIxMTQ3MDA5ODk2Nzc2MDYzOTc1MDUwOTMxIiwidmVyc2lvbiIsIjEuMCJd~', @@ -298,9 +316,14 @@ describe('OpenId4VcHolder', () => { } ) + if (resolvedAuthorizationRequest.authorizationFlow === OpenId4VciAuthorizationFlow.PresentationDuringIssuance) { + throw new Error('unexpected authorization flow') + } + const tokenResponse = await holder.modules.openId4VcHolder.requestToken({ resolvedCredentialOffer, - resolvedAuthorizationRequest, + clientId: 'test-client', + redirectUri: 'https://example.com', code: fixture.authorizationCode, }) diff --git a/packages/openid4vc/src/openid4vc-holder/__tests__/openid4vp-holder.e2e.test.ts b/packages/openid4vc/src/openid4vc-holder/__tests__/openid4vp-holder.e2e.test.ts index c52e89892e..d08ff9bf23 100644 --- a/packages/openid4vc/src/openid4vc-holder/__tests__/openid4vp-holder.e2e.test.ts +++ b/packages/openid4vc/src/openid4vc-holder/__tests__/openid4vp-holder.e2e.test.ts @@ -85,7 +85,7 @@ describe('OpenId4VcHolder | OpenID4VP', () => { expect(serverResponse).toEqual({ status: 200, - body: '', + body: {}, }) expect(submittedResponse).toMatchObject({ diff --git a/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuanceSessionState.ts b/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuanceSessionState.ts index 9bce616687..82e948bad4 100644 --- a/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuanceSessionState.ts +++ b/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuanceSessionState.ts @@ -1,8 +1,15 @@ export enum OpenId4VcIssuanceSessionState { OfferCreated = 'OfferCreated', OfferUriRetrieved = 'OfferUriRetrieved', + + // Used with authorization code flow where Credo is the auth server + AuthorizationInitiated = 'AuthorizationInitiated', + AuthorizationGranted = 'AuthorizationGranted', + + // Used with pre-auth and auth code flow where Credo is the auth server AccessTokenRequested = 'AccessTokenRequested', AccessTokenCreated = 'AccessTokenCreated', + CredentialRequestReceived = 'CredentialRequestReceived', CredentialsPartiallyIssued = 'CredentialsPartiallyIssued', Completed = 'Completed', diff --git a/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerApi.ts b/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerApi.ts index bae4c0d3b9..772e24041c 100644 --- a/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerApi.ts +++ b/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerApi.ts @@ -3,12 +3,11 @@ import type { OpenId4VciCreateCredentialOfferOptions, OpenId4VciCreateCredentialResponseOptions, OpenId4VciCreateIssuerOptions, + OpenId4VciCreateStatelessCredentialOfferOptions, } from './OpenId4VcIssuerServiceOptions' import { AgentContext, injectable } from '@credo-ts/core' -import { credentialsSupportedV13ToV11, type OpenId4VciCredentialRequest } from '../shared' - import { OpenId4VcIssuerModuleConfig } from './OpenId4VcIssuerModuleConfig' import { OpenId4VcIssuerService } from './OpenId4VcIssuerService' @@ -30,14 +29,6 @@ export class OpenId4VcIssuerApi { return this.openId4VcIssuerService.getAllIssuers(this.agentContext) } - /** - * @deprecated use {@link getIssuerByIssuerId} instead. - * @todo remove in 0.6 - */ - public async getByIssuerId(issuerId: string) { - return this.getIssuerByIssuerId(issuerId) - } - public async getIssuerByIssuerId(issuerId: string) { return this.openId4VcIssuerService.getIssuerByIssuerId(this.agentContext, issuerId) } @@ -59,23 +50,36 @@ export class OpenId4VcIssuerApi { } public async updateIssuerMetadata(options: OpenId4VcUpdateIssuerRecordOptions) { - const { issuerId, credentialConfigurationsSupported, credentialsSupported, ...issuerOptions } = options + const { + issuerId, + credentialConfigurationsSupported, + display, + dpopSigningAlgValuesSupported, + batchCredentialIssuance, + } = options const issuer = await this.openId4VcIssuerService.getIssuerByIssuerId(this.agentContext, issuerId) - if (credentialConfigurationsSupported) { - issuer.credentialConfigurationsSupported = credentialConfigurationsSupported - issuer.credentialsSupported = credentialsSupportedV13ToV11(credentialConfigurationsSupported) - } else { - issuer.credentialsSupported = credentialsSupported - issuer.credentialConfigurationsSupported = undefined - } - issuer.display = issuerOptions.display - issuer.dpopSigningAlgValuesSupported = issuerOptions.dpopSigningAlgValuesSupported + issuer.credentialConfigurationsSupported = credentialConfigurationsSupported + issuer.display = display + issuer.dpopSigningAlgValuesSupported = dpopSigningAlgValuesSupported + issuer.batchCredentialIssuance = batchCredentialIssuance return this.openId4VcIssuerService.updateIssuer(this.agentContext, issuer) } + /** + * Creates a stateless credential offer. This can only be used with an external authorization server, as credo only supports statefull + * crednetial offers. + */ + public async createStatelessCredentialOffer( + options: OpenId4VciCreateStatelessCredentialOfferOptions & { issuerId: string } + ) { + const { issuerId, ...rest } = options + const issuer = await this.openId4VcIssuerService.getIssuerByIssuerId(this.agentContext, issuerId) + return await this.openId4VcIssuerService.createStatelessCredentialOffer(this.agentContext, { ...rest, issuer }) + } + /** * Creates a credential offer. Either the preAuthorizedCodeFlowConfig or the authorizationCodeFlowConfig must be provided. * @@ -102,20 +106,12 @@ export class OpenId4VcIssuerApi { return await this.openId4VcIssuerService.createCredentialResponse(this.agentContext, { ...rest, issuanceSession }) } - public async findIssuanceSessionForCredentialRequest(options: { - credentialRequest: OpenId4VciCredentialRequest - issuerId?: string - }) { - const issuanceSession = await this.openId4VcIssuerService.findIssuanceSessionForCredentialRequest( - this.agentContext, - options - ) - - return issuanceSession - } - public async getIssuerMetadata(issuerId: string) { const issuer = await this.openId4VcIssuerService.getIssuerByIssuerId(this.agentContext, issuerId) return this.openId4VcIssuerService.getIssuerMetadata(this.agentContext, issuer) } + + public async getIssuanceSessionById(issuanceSessionId: string) { + return this.openId4VcIssuerService.getIssuanceSessionById(this.agentContext, issuanceSessionId) + } } diff --git a/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerModule.ts b/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerModule.ts index 44f4f6e84c..8744114f21 100644 --- a/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerModule.ts +++ b/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerModule.ts @@ -1,7 +1,9 @@ import type { OpenId4VcIssuerModuleConfigOptions } from './OpenId4VcIssuerModuleConfig' import type { OpenId4VcIssuanceRequest } from './router' import type { AgentContext, DependencyManager, Module } from '@credo-ts/core' +import type { NextFunction, Response } from 'express' +import { setGlobalConfig } from '@animo-id/oauth2' import { AgentConfig } from '@credo-ts/core' import { getAgentContextForActorId, getRequestContext, importExpress } from '../shared/router' @@ -16,6 +18,10 @@ import { configureAccessTokenEndpoint, configureCredentialEndpoint, configureIssuerMetadataEndpoint, + configureOAuthAuthorizationServerMetadataEndpoint, + configureJwksEndpoint, + configureNonceEndpoint, + configureAuthorizationChallengeEndpoint, } from './router' /** @@ -30,16 +36,21 @@ export class OpenId4VcIssuerModule implements Module { } /** - * Registers the dependencies of the question answer module on the dependency manager. + * Registers the dependencies of the openid4vc issuer module on the dependency manager. */ public register(dependencyManager: DependencyManager) { - // Warn about experimental module - dependencyManager - .resolve(AgentConfig) - .logger.warn( - "The '@credo-ts/openid4vc' Issuer module is experimental and could have unexpected breaking changes. When using this module, make sure to use strict versions for all @credo-ts packages." - ) + const agentConfig = dependencyManager.resolve(AgentConfig) + // Warn about experimental module + agentConfig.logger.warn( + "The '@credo-ts/openid4vc' Issuer module is experimental and could have unexpected breaking changes. When using this module, make sure to use strict versions for all @credo-ts packages." + ) + + if (agentConfig.allowInsecureHttpUrls) { + setGlobalConfig({ + allowInsecureUrls: true, + }) + } // Register config dependencyManager.registerInstance(OpenId4VcIssuerModuleConfig, this.config) @@ -90,7 +101,7 @@ export class OpenId4VcIssuerModule implements Module { // FIXME: should we create combined openId actor record? agentContext = await getAgentContextForActorId(rootAgentContext, issuerId) const issuerApi = agentContext.dependencyManager.resolve(OpenId4VcIssuerApi) - const issuer = await issuerApi.getByIssuerId(issuerId) + const issuer = await issuerApi.getIssuerByIssuerId(issuerId) req.requestContext = { agentContext, @@ -116,21 +127,37 @@ export class OpenId4VcIssuerModule implements Module { // Configure endpoints configureIssuerMetadataEndpoint(endpointRouter) - configureCredentialOfferEndpoint(endpointRouter, this.config.credentialOfferEndpoint) - configureAccessTokenEndpoint(endpointRouter, this.config.accessTokenEndpoint) - configureCredentialEndpoint(endpointRouter, this.config.credentialEndpoint) + configureJwksEndpoint(endpointRouter, this.config) + configureNonceEndpoint(endpointRouter, this.config) + configureOAuthAuthorizationServerMetadataEndpoint(endpointRouter) + configureCredentialOfferEndpoint(endpointRouter, this.config) + configureAccessTokenEndpoint(endpointRouter, this.config) + configureAuthorizationChallengeEndpoint(endpointRouter, this.config) + configureCredentialEndpoint(endpointRouter, this.config) // First one will be called for all requests (when next is called) contextRouter.use(async (req: OpenId4VcIssuanceRequest, _res: unknown, next) => { const { agentContext } = getRequestContext(req) await agentContext.endSession() + next() }) // This one will be called for all errors that are thrown - // eslint-disable-next-line @typescript-eslint/no-explicit-any - contextRouter.use(async (_error: unknown, req: OpenId4VcIssuanceRequest, _res: unknown, next: any) => { + contextRouter.use(async (_error: unknown, req: OpenId4VcIssuanceRequest, res: Response, next: NextFunction) => { const { agentContext } = getRequestContext(req) + + if (!res.headersSent) { + agentContext.config.logger.warn( + 'Error was thrown but openid4vci endpoint did not send a response. Sending generic server_error.' + ) + + res.status(500).json({ + error: 'server_error', + error_description: 'An unexpected error occurred on the server.', + }) + } + await agentContext.endSession() next() }) diff --git a/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerModuleConfig.ts b/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerModuleConfig.ts index 71eaa43c9a..2056824d14 100644 --- a/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerModuleConfig.ts +++ b/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerModuleConfig.ts @@ -1,16 +1,15 @@ import type { - OpenId4VciAccessTokenEndpointConfig, - OpenId4VciCredentialEndpointConfig, - OpenId4VciCredentialOfferEndpointConfig, -} from './router' -import type { Optional } from '@credo-ts/core' + OpenId4VciCredentialRequestToCredentialMapper, + OpenId4VciGetVerificationSessionForIssuanceSessionAuthorization, +} from './OpenId4VcIssuerServiceOptions' import type { Router } from 'express' import { importExpress } from '../shared/router' -const DEFAULT_C_NONCE_EXPIRES_IN = 5 * 60 // 5 minutes +const DEFAULT_C_NONCE_EXPIRES_IN = 1 * 60 // 1 minute +const DEFAULT_AUTHORIZATION_CODE_EXPIRES_IN = 1 * 60 // 1 minute const DEFAULT_TOKEN_EXPIRES_IN = 3 * 60 // 3 minutes -const DEFAULT_PRE_AUTH_CODE_EXPIRES_IN = 3 * 60 // 3 minutes +const DEFAULT_STATEFULL_CREDENTIAL_OFFER_EXPIRES_IN = 3 * 60 // 3 minutes export interface OpenId4VcIssuerModuleConfigOptions { /** @@ -28,13 +27,104 @@ export interface OpenId4VcIssuerModuleConfigOptions { */ router?: Router - endpoints: { - credentialOffer?: Optional - credential: Optional - accessToken?: Optional< - OpenId4VciAccessTokenEndpointConfig, - 'cNonceExpiresInSeconds' | 'endpointPath' | 'preAuthorizedCodeExpirationInSeconds' | 'tokenExpiresInSeconds' - > + /** + * The time after which a cNonce will expire. + * + * @default 60 (1 minute) + */ + cNonceExpiresInSeconds?: number + + /** + * The time after which a statefull credential offer not bound to a subject expires. Once the offer has been bound + * to a subject the access token expiration takes effect. This is to prevent long-lived `pre-authorized_code` and + * `issuer_state` values. + * + * @default 180 (3 minutes) + */ + statefullCredentialOfferExpirationInSeconds?: number + + /** + * The time after which an authorization code will expire. + * + * @default 60 (1 minute) + */ + authorizationCodeExpiresInSeconds?: number + + /** + * The time after which an access token will expire. + * + * @default 180 (3 minutes) + */ + accessTokenExpiresInSeconds?: number + + /** + * Whether DPoP is required for all issuance sessions. This value can be overridden when creating + * a credential offer. If dpop is not required, but used by a client in the first request to credo, + * DPoP will be required going forward. + * + * @default false + */ + dpopRequired?: boolean + + /** + * Whether to allow dynamic issuance sessions based on a credential request. + * + * This requires an external authorization server which issues access tokens without + * a `pre-authorized_code` or `issuer_state` parameter. + * + * Credo only support statefull crednetial offer sessions (pre-auth or presentation during issuance) + * + * @default false + */ + allowDynamicIssuanceSessions?: boolean + + /** + * A function mapping a credential request to the credential to be issued. + */ + credentialRequestToCredentialMapper: OpenId4VciCredentialRequestToCredentialMapper + + /** + * Callback to get a verification session that needs to be fulfilled for the authorization of + * of a credential issuance session. Once the verification session has been completed the user can + * retrieve an authorization code and access token and retrieve the credential(s). + * + * Required if presentation during issuance flow is used + */ + getVerificationSessionForIssuanceSessionAuthorization?: OpenId4VciGetVerificationSessionForIssuanceSessionAuthorization + + /** + * Custom the paths used for endpoints + */ + endpoints?: { + /** + * @default /nonce + */ + nonce?: string + + /** + * @default /challenge + */ + authorizationChallenge?: string + + /** + * @default /offers + */ + credentialOffer?: string + + /** + * @default /credential + */ + credential?: string + + /** + * @default /token + */ + accessToken?: string + + /** + * @default /jwks + */ + jwks: string } } @@ -53,45 +143,125 @@ export class OpenId4VcIssuerModuleConfig { } /** - * Get the credential endpoint config, with default values set + * A function mapping a credential request to the credential to be issued. + */ + public get credentialRequestToCredentialMapper() { + return this.options.credentialRequestToCredentialMapper + } + + /** + * Callback to get a verification session that needs to be fulfilled for the authorization of + * of a credential issuance session. Once the verification session has been completed the user can + * retrieve an authorization code and access token and retrieve the credential(s). + * + * Required if presentation during issuance flow is used + */ + public get getVerificationSessionForIssuanceSessionAuthorization() { + return this.options.getVerificationSessionForIssuanceSessionAuthorization + } + + /** + * The time after which a cNone will expire. + * + * @default 60 (1 minute) */ - public get credentialEndpoint(): OpenId4VciCredentialEndpointConfig { - // Use user supplied options, or return defaults. - const userOptions = this.options.endpoints.credential + public get cNonceExpiresInSeconds(): number { + return this.options.cNonceExpiresInSeconds ?? DEFAULT_C_NONCE_EXPIRES_IN + } - return { - ...userOptions, - endpointPath: userOptions.endpointPath ?? '/credential', - } + /** + * The time after which a statefull credential offer not bound to a subject expires. Once the offer has been bound + * to a subject the access token expiration takes effect. This is to prevent long-lived `pre-authorized_code` and + * `issuer_state` values. + * + * @default 360 (5 minutes) + */ + public get statefullCredentialOfferExpirationInSeconds(): number { + return this.options.statefullCredentialOfferExpirationInSeconds ?? DEFAULT_STATEFULL_CREDENTIAL_OFFER_EXPIRES_IN } /** - * Get the access token endpoint config, with default values set + * The time after which a cNonce will expire. + * + * @default 60 (1 minute) */ - public get accessTokenEndpoint(): OpenId4VciAccessTokenEndpointConfig { - // Use user supplied options, or return defaults. - const userOptions = this.options.endpoints.accessToken ?? {} + public get authorizationCodeExpiresInSeconds(): number { + return this.options.authorizationCodeExpiresInSeconds ?? DEFAULT_AUTHORIZATION_CODE_EXPIRES_IN + } - return { - ...userOptions, - endpointPath: userOptions.endpointPath ?? '/token', - cNonceExpiresInSeconds: userOptions.cNonceExpiresInSeconds ?? DEFAULT_C_NONCE_EXPIRES_IN, - preAuthorizedCodeExpirationInSeconds: - userOptions.preAuthorizedCodeExpirationInSeconds ?? DEFAULT_PRE_AUTH_CODE_EXPIRES_IN, - tokenExpiresInSeconds: userOptions.tokenExpiresInSeconds ?? DEFAULT_TOKEN_EXPIRES_IN, - } + /** + * The time after which an access token will expire. + * + * @default 360 (5 minutes) + */ + public get accessTokenExpiresInSeconds(): number { + return this.options.accessTokenExpiresInSeconds ?? DEFAULT_TOKEN_EXPIRES_IN } /** - * Get the hosted credential offer endpoint config, with default values set + * Whether DPoP is required for all issuance sessions. This value can be overridden when creating + * a credential offer. If dpop is not required, but used by a client in the first request to credo, + * DPoP will be required going forward. + * + * @default false */ - public get credentialOfferEndpoint(): OpenId4VciCredentialOfferEndpointConfig { - // Use user supplied options, or return defaults. - const userOptions = this.options.endpoints.credentialOffer ?? {} + public get dpopRequired(): boolean { + return this.options.dpopRequired ?? false + } + + /** + * Whether to allow dynamic issuance sessions based on a credential request. + * + * This requires an external authorization server which issues access tokens without + * a `pre-authorized_code` or `issuer_state` parameter. + * + * Credo only supports statefull crednetial offer sessions (pre-auth or presentation during issuance) + * + * @default false + */ + public get allowDynamicIssuanceSessions(): boolean { + return this.options.allowDynamicIssuanceSessions ?? false + } - return { - ...userOptions, - endpointPath: userOptions.endpointPath ?? '/offers', - } + /** + * @default /nonce + */ + public get nonceEndpointPath(): string { + return this.options.endpoints?.nonce ?? '/nonce' + } + + /** + * @default /challenge + */ + public get authorizationChallengeEndpointPath(): string { + return this.options.endpoints?.authorizationChallenge ?? '/challenge' + } + + /** + * @default /offers + */ + public get credentialOfferEndpointPath(): string { + return this.options.endpoints?.credentialOffer ?? '/offers' + } + + /** + * @default /credential + */ + public get credentialEndpointPath(): string { + return this.options.endpoints?.credential ?? '/credential' + } + + /** + * @default /token + */ + public get accessTokenEndpointPath(): string { + return this.options.endpoints?.accessToken ?? '/token' + } + + /** + * @default /jwks + */ + public get jwksEndpointPath(): string { + return this.options.endpoints?.jwks ?? '/jwks' } } diff --git a/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerService.ts b/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerService.ts index c9661f16e6..c874fa0995 100644 --- a/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerService.ts +++ b/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerService.ts @@ -3,76 +3,75 @@ import type { OpenId4VciCreateCredentialOfferOptions, OpenId4VciCreateIssuerOptions, OpenId4VciPreAuthorizedCodeFlowConfig, - OpenId4VcIssuerMetadata, - OpenId4VciSignSdJwtCredential, - OpenId4VciSignW3cCredential, - OpenId4VciSignMdocCredential, + OpenId4VciSignW3cCredentials, + OpenId4VciAuthorizationCodeFlowConfig, + OpenId4VciCredentialRequestAuthorization, + OpenId4VciCreateStatelessCredentialOfferOptions, + OpenId4VciCredentialRequestToCredentialMapperOptions, } from './OpenId4VcIssuerServiceOptions' -import type { OpenId4VcIssuanceSessionRecord } from './repository' import type { - OpenId4VcCredentialHolderBinding, - OpenId4VciCredentialConfigurationsSupported, - OpenId4VciCredentialOfferPayload, - OpenId4VciCredentialRequest, + OpenId4VcCredentialHolderBindingWithKey, + OpenId4VciCredentialConfigurationsSupportedWithFormats, + OpenId4VciMetadata, } from '../shared' -import type { AgentContext, DidDocument, Key, Query, QueryOptions } from '@credo-ts/core' -import type { - CredentialOfferPayloadV1_0_11, - CredentialOfferPayloadV1_0_13, - Grant, - JWTVerifyCallback, -} from '@sphereon/oid4vci-common' -import type { - CredentialDataSupplier, - CredentialDataSupplierArgs, - CredentialIssuanceInput, - CredentialSignerCallback, -} from '@sphereon/oid4vci-issuer' -import type { ICredential } from '@sphereon/ssi-types' +import type { AgentContext, Query, QueryOptions } from '@credo-ts/core' +import { + AuthorizationServerMetadata, + JwtSigner, + Oauth2AuthorizationServer, + Oauth2Client, + Oauth2ErrorCodes, + Oauth2ResourceServer, + Oauth2ServerErrorResponseError, + PkceCodeChallengeMethod, + preAuthorizedCodeGrantIdentifier, +} from '@animo-id/oauth2' +import { + CredentialIssuerMetadata, + CredentialRequestFormatSpecific, + extractScopesForCredentialConfigurationIds, + getCredentialConfigurationsMatchingRequestFormat, + Oid4vciDraftVersion, + Oid4vciIssuer, +} from '@animo-id/oid4vci' import { SdJwtVcApi, CredoError, ClaimFormat, - DidsApi, - equalsIgnoreOrder, getJwkFromJson, getJwkFromKey, - getKeyFromVerificationMethod, injectable, joinUriParts, - JsonEncoder, - JsonTransformer, JwsService, - Jwt, KeyType, utils, W3cCredentialService, MdocApi, - parseDid, - DidResolverService, + Key, + JwtPayload, + Jwt, + EventEmitter, + TypedArrayEncoder, } from '@credo-ts/core' -import { VcIssuerBuilder } from '@sphereon/oid4vci-issuer' -import { credentialsSupportedV11ToV13, OpenId4VciCredentialFormatProfile } from '../shared' -import { credentialsSupportedV13ToV11, getOfferedCredentials } from '../shared/issuerMetadataUtils' +import { OpenId4VcVerifierApi } from '../openid4vc-verifier' +import { OpenId4VciCredentialFormatProfile } from '../shared' +import { dynamicOid4vciClientAuthentication, getOid4vciCallbacks } from '../shared/callbacks' +import { getCredentialConfigurationsSupportedForScopes, getOfferedCredentials } from '../shared/issuerMetadataUtils' import { storeActorIdForContextCorrelationId } from '../shared/router' -import { getSphereonVerifiableCredential } from '../shared/transform' -import { getProofTypeFromKey, isCredentialOfferV1Draft13 } from '../shared/utils' +import { addSecondsToDate, dateToSeconds, getKeyFromDid, getProofTypeFromKey } from '../shared/utils' import { OpenId4VcIssuanceSessionState } from './OpenId4VcIssuanceSessionState' +import { OpenId4VcIssuanceSessionStateChangedEvent, OpenId4VcIssuerEvents } from './OpenId4VcIssuerEvents' import { OpenId4VcIssuerModuleConfig } from './OpenId4VcIssuerModuleConfig' -import { OpenId4VcIssuerRepository, OpenId4VcIssuerRecord, OpenId4VcIssuanceSessionRepository } from './repository' -import { OpenId4VcCNonceStateManager } from './repository/OpenId4VcCNonceStateManager' -import { OpenId4VcCredentialOfferSessionStateManager } from './repository/OpenId4VcCredentialOfferSessionStateManager' -import { OpenId4VcCredentialOfferUriStateManager } from './repository/OpenId4VcCredentialOfferUriStateManager' -import { getCNonceFromCredentialRequest } from './util/credentialRequest' - -const w3cOpenId4VcFormats = [ - OpenId4VciCredentialFormatProfile.JwtVcJson, - OpenId4VciCredentialFormatProfile.JwtVcJsonLd, - OpenId4VciCredentialFormatProfile.LdpVc, -] +import { + OpenId4VcIssuerRepository, + OpenId4VcIssuerRecord, + OpenId4VcIssuanceSessionRepository, + OpenId4VcIssuanceSessionRecord, +} from './repository' +import { generateTxCode } from './util/txCode' /** * @internal @@ -80,191 +79,286 @@ const w3cOpenId4VcFormats = [ @injectable() export class OpenId4VcIssuerService { private w3cCredentialService: W3cCredentialService - private jwsService: JwsService private openId4VcIssuerConfig: OpenId4VcIssuerModuleConfig private openId4VcIssuerRepository: OpenId4VcIssuerRepository private openId4VcIssuanceSessionRepository: OpenId4VcIssuanceSessionRepository public constructor( w3cCredentialService: W3cCredentialService, - jwsService: JwsService, openId4VcIssuerConfig: OpenId4VcIssuerModuleConfig, openId4VcIssuerRepository: OpenId4VcIssuerRepository, openId4VcIssuanceSessionRepository: OpenId4VcIssuanceSessionRepository ) { this.w3cCredentialService = w3cCredentialService - this.jwsService = jwsService this.openId4VcIssuerConfig = openId4VcIssuerConfig this.openId4VcIssuerRepository = openId4VcIssuerRepository this.openId4VcIssuanceSessionRepository = openId4VcIssuanceSessionRepository } - public async createCredentialOffer( + public async createStatelessCredentialOffer( agentContext: AgentContext, - options: OpenId4VciCreateCredentialOfferOptions & { issuer: OpenId4VcIssuerRecord } + options: OpenId4VciCreateStatelessCredentialOfferOptions & { issuer: OpenId4VcIssuerRecord } ) { - const { preAuthorizedCodeFlowConfig, issuer, offeredCredentials } = options + const { authorizationCodeFlowConfig, issuer, offeredCredentials } = options + const vcIssuer = this.getIssuer(agentContext) + const issuerMetadata = await this.getIssuerMetadata(agentContext, issuer) - const vcIssuer = this.getVcIssuer(agentContext, issuer) + const uniqueOfferedCredentials = Array.from(new Set(options.offeredCredentials)) + if (uniqueOfferedCredentials.length !== offeredCredentials.length) { + throw new CredoError('All offered credentials must have unique ids.') + } - if (options.preAuthorizedCodeFlowConfig.userPinRequired === false && options.preAuthorizedCodeFlowConfig.txCode) { - throw new CredoError('The userPinRequired option must be set to true when using txCode.') + // Check if all the offered credential configuration ids have a scope value. If not, it won't be possible to actually request + // issuance of the crednetial later on + extractScopesForCredentialConfigurationIds({ + credentialConfigurationIds: options.offeredCredentials, + issuerMetadata, + throwOnConfigurationWithoutScope: true, + }) + + if (authorizationCodeFlowConfig.authorizationServerUrl === issuerMetadata.credentialIssuer.credential_issuer) { + throw new CredoError( + 'Stateless offers can only be created for external authorization servers. Make sure to configure an external authorization server on the issuer record, and provide the authoriation server url.' + ) } - if (options.preAuthorizedCodeFlowConfig.userPinRequired && !options.preAuthorizedCodeFlowConfig.txCode) { - options.preAuthorizedCodeFlowConfig.txCode = {} + const { credentialOffer, credentialOfferObject } = await vcIssuer.createCredentialOffer({ + credentialConfigurationIds: options.offeredCredentials, + grants: { + authorization_code: { + authorization_server: authorizationCodeFlowConfig.authorizationServerUrl, + }, + }, + credentialOfferScheme: options.baseUri, + issuerMetadata, + }) + + return { + credentialOffer, + credentialOfferObject, } + } - if (options.preAuthorizedCodeFlowConfig.txCode && !options.preAuthorizedCodeFlowConfig.userPinRequired) { - options.preAuthorizedCodeFlowConfig.userPinRequired = true + public async createCredentialOffer( + agentContext: AgentContext, + options: OpenId4VciCreateCredentialOfferOptions & { issuer: OpenId4VcIssuerRecord } + ) { + const { + preAuthorizedCodeFlowConfig, + authorizationCodeFlowConfig, + issuer, + offeredCredentials, + version = 'v1.draft11-13', + } = options + if (!preAuthorizedCodeFlowConfig && !authorizationCodeFlowConfig) { + throw new CredoError('Authorization Config or Pre-Authorized Config must be provided.') } - // this checks if the structure of the credentials is correct - // it throws an error if a offered credential cannot be found in the credentialsSupported - getOfferedCredentials( - agentContext, - options.offeredCredentials, - vcIssuer.issuerMetadata.credential_configurations_supported - ) + const vcIssuer = this.getIssuer(agentContext) + const issuerMetadata = await this.getIssuerMetadata(agentContext, issuer) + const uniqueOfferedCredentials = Array.from(new Set(options.offeredCredentials)) if (uniqueOfferedCredentials.length !== offeredCredentials.length) { throw new CredoError('All offered credentials must have unique ids.') } + if (uniqueOfferedCredentials.length === 0) { + throw new CredoError('You need to offer at least one credential.') + } + // We always use shortened URIs currently - const hostedCredentialOfferUri = joinUriParts(vcIssuer.issuerMetadata.credential_issuer, [ - this.openId4VcIssuerConfig.credentialOfferEndpoint.endpointPath, + const hostedCredentialOfferUri = joinUriParts(issuerMetadata.credentialIssuer.credential_issuer, [ + this.openId4VcIssuerConfig.credentialOfferEndpointPath, // It doesn't really matter what the url is, as long as it's unique utils.uuid(), ]) - const grants = await this.getGrantsFromConfig(agentContext, preAuthorizedCodeFlowConfig) + // Check if all the offered credential configuration ids have a scope value. If not, it won't be possible to actually request + // issuance of the crednetial later on. For pre-auth it's not needed to add a scope. + if (options.authorizationCodeFlowConfig) { + extractScopesForCredentialConfigurationIds({ + credentialConfigurationIds: options.offeredCredentials, + issuerMetadata, + throwOnConfigurationWithoutScope: true, + }) + } + + const grants = await this.getGrantsFromConfig(agentContext, { + issuerMetadata, + preAuthorizedCodeFlowConfig, + authorizationCodeFlowConfig, + }) - let { uri } = await vcIssuer.createCredentialOfferURI({ - scheme: 'openid-credential-offer', + const { credentialOffer, credentialOfferObject } = await vcIssuer.createCredentialOffer({ + credentialConfigurationIds: options.offeredCredentials, grants, - credential_configuration_ids: offeredCredentials, credentialOfferUri: hostedCredentialOfferUri, - baseUri: options.baseUri, - credentialDataSupplierInput: options.issuanceMetadata, - pinLength: grants['urn:ietf:params:oauth:grant-type:pre-authorized_code']?.tx_code?.length, + credentialOfferScheme: options.baseUri, + issuerMetadata: { + originalDraftVersion: version === 'v1.draft11-13' ? Oid4vciDraftVersion.Draft11 : Oid4vciDraftVersion.Draft14, + ...issuerMetadata, + }, }) - // FIXME: https://github.com/Sphereon-Opensource/OID4VCI/issues/102 - if (uri.includes(hostedCredentialOfferUri)) { - uri = uri.replace(hostedCredentialOfferUri, encodeURIComponent(hostedCredentialOfferUri)) - } - const issuanceSessionRepository = this.openId4VcIssuanceSessionRepository - const issuanceSession = await issuanceSessionRepository.getSingleByQuery(agentContext, { + const issuanceSession = new OpenId4VcIssuanceSessionRecord({ + credentialOfferPayload: credentialOfferObject, credentialOfferUri: hostedCredentialOfferUri, + issuerId: issuer.issuerId, + state: OpenId4VcIssuanceSessionState.OfferCreated, + authorization: credentialOfferObject.grants?.authorization_code?.issuer_state + ? { + issuerState: credentialOfferObject.grants?.authorization_code?.issuer_state, + } + : undefined, + presentation: authorizationCodeFlowConfig?.requirePresentationDuringIssuance + ? { + required: true, + } + : undefined, + // TODO: how to mix pre-auth and auth? Need to do state checks + preAuthorizedCode: credentialOfferObject.grants?.[preAuthorizedCodeGrantIdentifier]?.['pre-authorized_code'], + userPin: preAuthorizedCodeFlowConfig?.txCode + ? generateTxCode(agentContext, preAuthorizedCodeFlowConfig.txCode) + : undefined, + issuanceMetadata: options.issuanceMetadata, }) - - if (options.version !== 'v1.draft13') { - const v13CredentialOfferPayload = issuanceSession.credentialOfferPayload as CredentialOfferPayloadV1_0_13 - const v11CredentialOfferPayload: CredentialOfferPayloadV1_0_11 = { - ...v13CredentialOfferPayload, - credentials: v13CredentialOfferPayload.credential_configuration_ids, - } - if (v11CredentialOfferPayload.grants?.['urn:ietf:params:oauth:grant-type:pre-authorized_code']) { - // property was always defined in v11 - v11CredentialOfferPayload.grants['urn:ietf:params:oauth:grant-type:pre-authorized_code'].user_pin_required = - preAuthorizedCodeFlowConfig.userPinRequired ?? false - } - - issuanceSession.credentialOfferPayload = v11CredentialOfferPayload - await issuanceSessionRepository.update(agentContext, issuanceSession) - } + await issuanceSessionRepository.save(agentContext, issuanceSession) + this.emitStateChangedEvent(agentContext, issuanceSession, null) return { issuanceSession, - credentialOffer: uri, + credentialOffer, } } - /** - * find the issuance session associated with a credential request. You can optionally provide a issuer id if - * the issuer that the request is associated with is already known. - */ - public async findIssuanceSessionForCredentialRequest( - agentContext: AgentContext, - { credentialRequest, issuerId }: { credentialRequest: OpenId4VciCredentialRequest; issuerId?: string } - ) { - const cNonce = getCNonceFromCredentialRequest(credentialRequest) - - const issuanceSession = await this.openId4VcIssuanceSessionRepository.findSingleByQuery(agentContext, { - issuerId, - cNonce, - }) - - return issuanceSession - } - public async createCredentialResponse( agentContext: AgentContext, options: OpenId4VciCreateCredentialResponseOptions & { issuanceSession: OpenId4VcIssuanceSessionRecord } ) { options.issuanceSession.assertState([ + // OfferUriRetrieved is valid when doing auth flow (we should add a check) + OpenId4VcIssuanceSessionState.OfferUriRetrieved, OpenId4VcIssuanceSessionState.AccessTokenCreated, OpenId4VcIssuanceSessionState.CredentialRequestReceived, // It is possible to issue multiple credentials in one session OpenId4VcIssuanceSessionState.CredentialsPartiallyIssued, ]) - const { credentialRequest, issuanceSession } = options - if (!credentialRequest.proof) throw new CredoError('No proof defined in the credentialRequest.') - + const { issuanceSession } = options const issuer = await this.getIssuerByIssuerId(agentContext, options.issuanceSession.issuerId) + const vcIssuer = this.getIssuer(agentContext) + const issuerMetadata = await this.getIssuerMetadata(agentContext, issuer) - const cNonce = getCNonceFromCredentialRequest(credentialRequest) - if (issuanceSession.cNonce !== cNonce) { - throw new CredoError('The cNonce in the credential request does not match the cNonce in the issuance session.') + const parsedCredentialRequest = vcIssuer.parseCredentialRequest({ + credentialRequest: options.credentialRequest, + }) + const { credentialRequest, credentialIdentifier, format, proofs } = parsedCredentialRequest + if (credentialIdentifier) { + throw new Oauth2ServerErrorResponseError({ + error: Oauth2ErrorCodes.InvalidCredentialRequest, + error_description: `Using unsupported 'credential_identifier'`, + }) } - if (!issuanceSession.cNonceExpiresAt) { - throw new CredoError('Missing required cNonceExpiresAt in the issuance session. Assuming cNonce is not valid') + if (!format) { + throw new Oauth2ServerErrorResponseError({ + error: Oauth2ErrorCodes.UnsupportedCredentialFormat, + error_description: `Unsupported credential format '${credentialRequest.format}'`, + }) } - if (Date.now() > issuanceSession.cNonceExpiresAt.getTime()) { - throw new CredoError('The cNonce has expired.') + + if (!proofs?.jwt || proofs.jwt.length === 0) { + const { cNonce, cNonceExpiresInSeconds } = await this.createNonce(agentContext, issuer) + throw new Oauth2ServerErrorResponseError({ + error: Oauth2ErrorCodes.InvalidProof, + error_description: 'Missing required proof(s) in credential request', + c_nonce: cNonce, + c_nonce_expires_in: cNonceExpiresInSeconds, + }) } + await this.updateState(agentContext, issuanceSession, OpenId4VcIssuanceSessionState.CredentialRequestReceived) + + let previousNonce: string | undefined = undefined + const proofSigners: JwtSigner[] = [] + for (const jwt of proofs.jwt) { + const { signer, payload } = await vcIssuer.verifyCredentialRequestJwtProof({ + issuerMetadata, + jwt, + clientId: options.issuanceSession.clientId, + }) - const vcIssuer = this.getVcIssuer(agentContext, issuer) + if (!payload.nonce) { + const { cNonce, cNonceExpiresInSeconds } = await this.createNonce(agentContext, issuer) + throw new Oauth2ServerErrorResponseError({ + error: Oauth2ErrorCodes.InvalidProof, + error_description: 'Missing nonce in proof(s) in credential request', + c_nonce: cNonce, + c_nonce_expires_in: cNonceExpiresInSeconds, + }) + } - const credentialResponse = await vcIssuer.issueCredential({ - credentialRequest, - tokenExpiresIn: this.openId4VcIssuerConfig.accessTokenEndpoint.tokenExpiresInSeconds, + // Set previous nonce if not yet set (first iteration) + if (!previousNonce) previousNonce = payload.nonce + if (previousNonce !== payload.nonce) { + const { cNonce, cNonceExpiresInSeconds } = await this.createNonce(agentContext, issuer) + throw new Oauth2ServerErrorResponseError({ + error: Oauth2ErrorCodes.InvalidProof, + error_description: 'Not all nonce values in proofs are equal', + c_nonce: cNonce, + c_nonce_expires_in: cNonceExpiresInSeconds, + }) + } - // This can just be combined with signing callback right? - credentialDataSupplier: this.getCredentialDataSupplier(agentContext, { ...options, issuer }), - credentialDataSupplierInput: issuanceSession.issuanceMetadata, - responseCNonce: undefined, - }) + // Verify the nonce + await this.verifyNonce(agentContext, issuer, payload.nonce).catch(async (error) => { + const { cNonce, cNonceExpiresInSeconds } = await this.createNonce(agentContext, issuer) + throw new Oauth2ServerErrorResponseError( + { + error: Oauth2ErrorCodes.InvalidNonce, + error_description: 'Invalid nonce in credential request', + c_nonce: cNonce, + c_nonce_expires_in: cNonceExpiresInSeconds, + }, + { + cause: error, + } + ) + }) - // NOTE: ONLY REQUIRED FOR V11 COMPAT - if (isCredentialOfferV1Draft13(options.issuanceSession.credentialOfferPayload)) { - credentialResponse.format = credentialRequest.format + proofSigners.push(signer) } - const updatedIssuanceSession = await this.openId4VcIssuanceSessionRepository.getById( - agentContext, - issuanceSession.id - ) - if (!credentialResponse.credential) { - updatedIssuanceSession.state = OpenId4VcIssuanceSessionState.Error - updatedIssuanceSession.errorMessage = 'No credential found in the issueCredentialResponse.' - await this.openId4VcIssuanceSessionRepository.update(agentContext, updatedIssuanceSession) - throw new CredoError(updatedIssuanceSession.errorMessage) - } + const signedCredentials = await this.getSignedCredentials(agentContext, { + credentialRequest, + issuanceSession, + issuer, + requestFormat: format, + authorization: options.authorization, + credentialRequestToCredentialMapper: options.credentialRequestToCredentialMapper, + proofSigners, + }) - if (credentialResponse.acceptance_token || credentialResponse.transaction_id) { - updatedIssuanceSession.state = OpenId4VcIssuanceSessionState.Error - updatedIssuanceSession.errorMessage = 'Acceptance token and transaction id are not yet supported.' - await this.openId4VcIssuanceSessionRepository.update(agentContext, updatedIssuanceSession) - throw new CredoError(updatedIssuanceSession.errorMessage) - } + // NOTE: nonce in credential response is deprecated in newer drafts, but for now we keep it in + const { cNonce, cNonceExpiresInSeconds } = await this.createNonce(agentContext, issuer) + const credentialResponse = vcIssuer.createCredentialResponse({ + credential: credentialRequest.proof ? signedCredentials.credentials[0] : undefined, + credentials: credentialRequest.proofs ? signedCredentials.credentials : undefined, + cNonce, + cNonceExpiresInSeconds, + credentialRequest: parsedCredentialRequest, + }) + + issuanceSession.issuedCredentials.push(signedCredentials.credentialConfigurationId) + const newState = + issuanceSession.issuedCredentials.length >= + issuanceSession.credentialOfferPayload.credential_configuration_ids.length + ? OpenId4VcIssuanceSessionState.Completed + : OpenId4VcIssuanceSessionState.CredentialsPartiallyIssued + await this.updateState(agentContext, issuanceSession, newState) return { credentialResponse, - issuanceSession: updatedIssuanceSession, + issuanceSession, } } @@ -276,6 +370,13 @@ export class OpenId4VcIssuerService { return this.openId4VcIssuanceSessionRepository.findByQuery(agentContext, query, queryOptions) } + public async findSingleIssuancSessionByQuery( + agentContext: AgentContext, + query: Query + ) { + return this.openId4VcIssuanceSessionRepository.findSingleByQuery(agentContext, query) + } + public async getIssuanceSessionById(agentContext: AgentContext, issuanceSessionId: string) { return this.openId4VcIssuanceSessionRepository.getById(agentContext, issuanceSessionId) } @@ -297,34 +398,31 @@ export class OpenId4VcIssuerService { // - createdAt // - purpose const accessTokenSignerKey = await agentContext.wallet.createKey({ - keyType: KeyType.Ed25519, + keyType: options.accessTokenSignerKeyType ?? KeyType.Ed25519, }) - const openId4VcIssuerBase = { + const openId4VcIssuer = new OpenId4VcIssuerRecord({ issuerId: options.issuerId ?? utils.uuid(), display: options.display, dpopSigningAlgValuesSupported: options.dpopSigningAlgValuesSupported, accessTokenPublicKeyFingerprint: accessTokenSignerKey.fingerprint, - } as const - - const openId4VcIssuer = options.credentialsSupported - ? new OpenId4VcIssuerRecord({ - ...openId4VcIssuerBase, - credentialsSupported: options.credentialsSupported, - }) - : new OpenId4VcIssuerRecord({ - ...openId4VcIssuerBase, - credentialConfigurationsSupported: options.credentialConfigurationsSupported, - }) + authorizationServerConfigs: options.authorizationServerConfigs, + credentialConfigurationsSupported: options.credentialConfigurationsSupported, + batchCredentialIssuance: options.batchCredentialIssuance, + }) await this.openId4VcIssuerRepository.save(agentContext, openId4VcIssuer) await storeActorIdForContextCorrelationId(agentContext, openId4VcIssuer.issuerId) return openId4VcIssuer } - public async rotateAccessTokenSigningKey(agentContext: AgentContext, issuer: OpenId4VcIssuerRecord) { + public async rotateAccessTokenSigningKey( + agentContext: AgentContext, + issuer: OpenId4VcIssuerRecord, + options?: Pick + ) { const accessTokenSignerKey = await agentContext.wallet.createKey({ - keyType: KeyType.Ed25519, + keyType: options?.accessTokenSignerKeyType ?? KeyType.Ed25519, }) // TODO: ideally we can remove the previous key @@ -332,444 +430,570 @@ export class OpenId4VcIssuerService { await this.openId4VcIssuerRepository.update(agentContext, issuer) } - public getIssuerMetadata(agentContext: AgentContext, issuerRecord: OpenId4VcIssuerRecord): OpenId4VcIssuerMetadata { + /** + * @param fetchExternalAuthorizationServerMetadata defaults to false + */ + public async getIssuerMetadata( + agentContext: AgentContext, + issuerRecord: OpenId4VcIssuerRecord, + fetchExternalAuthorizationServerMetadata = false + ): Promise { const config = agentContext.dependencyManager.resolve(OpenId4VcIssuerModuleConfig) const issuerUrl = joinUriParts(config.baseUrl, [issuerRecord.issuerId]) + const oauth2Client = this.getOauth2Client(agentContext) + + const extraAuthorizationServers: AuthorizationServerMetadata[] = + fetchExternalAuthorizationServerMetadata && issuerRecord.authorizationServerConfigs + ? await Promise.all( + issuerRecord.authorizationServerConfigs.map(async (server) => { + const metadata = await oauth2Client.fetchAuthorizationServerMetadata(server.issuer) + if (!metadata) + throw new CredoError(`Authorization server metadata not found for issuer '${server.issuer}'`) + return metadata + }) + ) + : [] + + const authorizationServers = + issuerRecord.authorizationServerConfigs && issuerRecord.authorizationServerConfigs.length > 0 + ? [ + ...issuerRecord.authorizationServerConfigs.map((authorizationServer) => authorizationServer.issuer), + // Our issuer is also a valid authorization server (only for pre-auth) + issuerUrl, + ] + : undefined + + const credentialIssuerMetadata = { + credential_issuer: issuerUrl, + credential_endpoint: joinUriParts(issuerUrl, [config.credentialEndpointPath]), + credential_configurations_supported: issuerRecord.credentialConfigurationsSupported ?? {}, + authorization_servers: authorizationServers, + display: issuerRecord.display, + nonce_endpoint: joinUriParts(issuerUrl, [config.nonceEndpointPath]), + batch_credential_issuance: issuerRecord.batchCredentialIssuance + ? { + batch_size: issuerRecord.batchCredentialIssuance.batchSize, + } + : undefined, + } satisfies CredentialIssuerMetadata + + const issuerAuthorizationServer = { + issuer: issuerUrl, + token_endpoint: joinUriParts(issuerUrl, [config.accessTokenEndpointPath]), + 'pre-authorized_grant_anonymous_access_supported': true, + + jwks_uri: joinUriParts(issuerUrl, [config.jwksEndpointPath]), + authorization_challenge_endpoint: joinUriParts(issuerUrl, [config.authorizationChallengeEndpointPath]), + + // TODO: PAR (maybe not needed as we only use this auth server for presentation during issuance) + // pushed_authorization_request_endpoint: '', + // require_pushed_authorization_requests: true + + code_challenge_methods_supported: [PkceCodeChallengeMethod.S256], + dpop_signing_alg_values_supported: issuerRecord.dpopSigningAlgValuesSupported, + } satisfies AuthorizationServerMetadata - const issuerMetadata = { - issuerUrl, - tokenEndpoint: joinUriParts(issuerUrl, [config.accessTokenEndpoint.endpointPath]), - credentialEndpoint: joinUriParts(issuerUrl, [config.credentialEndpoint.endpointPath]), - credentialsSupported: issuerRecord.credentialsSupported, - credentialConfigurationsSupported: - issuerRecord.credentialConfigurationsSupported ?? - credentialsSupportedV11ToV13(agentContext, issuerRecord.credentialsSupported), - issuerDisplay: issuerRecord.display, - dpopSigningAlgValuesSupported: issuerRecord.dpopSigningAlgValuesSupported, - } satisfies OpenId4VcIssuerMetadata - - return issuerMetadata + return { + credentialIssuer: credentialIssuerMetadata, + authorizationServers: [issuerAuthorizationServer, ...extraAuthorizationServers], + } } - private getJwtVerifyCallback = (agentContext: AgentContext): JWTVerifyCallback => { - return async (opts) => { - let didDocument = undefined as DidDocument | undefined - const { isValid, jws } = await this.jwsService.verifyJws(agentContext, { - jws: opts.jwt, - // Only handles kid as did resolution. JWK is handled by jws service - jwkResolver: async ({ protectedHeader: { kid } }) => { - if (!kid) throw new CredoError('Missing kid in protected header.') - if (!kid.startsWith('did:')) throw new CredoError('Only did is supported for kid identifier') - - const didsApi = agentContext.dependencyManager.resolve(DidsApi) - didDocument = await didsApi.resolveDidDocument(kid) - const verificationMethod = didDocument.dereferenceKey(kid, ['authentication', 'assertionMethod']) - const key = getKeyFromVerificationMethod(verificationMethod) - return getJwkFromKey(key) - }, - }) - - if (!isValid) throw new CredoError('Could not verify JWT signature.') + public async createNonce(agentContext: AgentContext, issuer: OpenId4VcIssuerRecord) { + const issuerMetadata = await this.getIssuerMetadata(agentContext, issuer) + const jwsService = agentContext.dependencyManager.resolve(JwsService) + + const cNonceExpiresInSeconds = this.openId4VcIssuerConfig.cNonceExpiresInSeconds + const cNonceExpiresAt = addSecondsToDate(new Date(), cNonceExpiresInSeconds) + + const key = Key.fromFingerprint(issuer.accessTokenPublicKeyFingerprint) + const jwk = getJwkFromKey(key) + + const cNonce = await jwsService.createJwsCompact(agentContext, { + key, + payload: JwtPayload.fromJson({ + iss: issuerMetadata.credentialIssuer.credential_issuer, + exp: dateToSeconds(cNonceExpiresAt), + }), + protectedHeaderOptions: { + typ: 'credo+cnonce', + kid: issuer.accessTokenPublicKeyFingerprint, + alg: jwk.supportedSignatureAlgorithms[0], + }, + }) - // TODO: the jws service should return some better decoded metadata also from the resolver - // as currently is less useful if you afterwards need properties from the JWS - const firstJws = jws.signatures[0] - const protectedHeader = JsonEncoder.fromBase64(firstJws.protected) - return { - jwt: { header: protectedHeader, payload: JsonEncoder.fromBase64(jws.payload) }, - kid: protectedHeader.kid, - jwk: protectedHeader.jwk ? getJwkFromJson(protectedHeader.jwk) : undefined, - did: didDocument?.id, - alg: protectedHeader.alg, - didDocument, - } + return { + cNonce, + cNonceExpiresAt, + cNonceExpiresInSeconds, } } - private getVcIssuer(agentContext: AgentContext, issuer: OpenId4VcIssuerRecord) { - const issuerMetadata = this.getIssuerMetadata(agentContext, issuer) + /** + * @todo nonces are very short lived (1 min), but it might be nice to also cache the nonces + * in the cache if we have 'seen' them. They will only be in the cache for a short time + * and it will prevent replay + */ + private async verifyNonce(agentContext: AgentContext, issuer: OpenId4VcIssuerRecord, cNonce: string) { + const issuerMetadata = await this.getIssuerMetadata(agentContext, issuer) + const jwsService = agentContext.dependencyManager.resolve(JwsService) - const builder = new VcIssuerBuilder() - .withCredentialIssuer(issuerMetadata.issuerUrl) - .withCredentialEndpoint(issuerMetadata.credentialEndpoint) - .withTokenEndpoint(issuerMetadata.tokenEndpoint) - .withCredentialConfigurationsSupported( - issuer.credentialConfigurationsSupported ?? - credentialsSupportedV11ToV13(agentContext, issuer.credentialsSupported) - ) - .withCNonceStateManager(new OpenId4VcCNonceStateManager(agentContext, issuer.issuerId)) - .withCredentialOfferStateManager(new OpenId4VcCredentialOfferSessionStateManager(agentContext, issuer.issuerId)) - .withCredentialOfferURIStateManager(new OpenId4VcCredentialOfferUriStateManager(agentContext, issuer.issuerId)) - .withJWTVerifyCallback(this.getJwtVerifyCallback(agentContext)) - .withCredentialSignerCallback(() => { - throw new CredoError('Credential signer callback should be overwritten. This is a no-op') - }) + const key = Key.fromFingerprint(issuer.accessTokenPublicKeyFingerprint) + const jwk = getJwkFromKey(key) - if (issuerMetadata.authorizationServer) { - builder.withAuthorizationServers(issuerMetadata.authorizationServer) - } + const jwt = Jwt.fromSerializedJwt(cNonce) + jwt.payload.validate() - if (issuerMetadata.issuerDisplay) { - builder.withIssuerDisplay(issuerMetadata.issuerDisplay) + if (jwt.payload.iss !== issuerMetadata.credentialIssuer.credential_issuer) { + throw new CredoError(`Invalid 'iss' claim in cNonce jwt`) + } + if (jwt.header.typ !== 'credo+cnonce') { + throw new CredoError(`Invalid 'typ' claim in cNonce jwt header`) } - return builder.build() - } + const verification = await jwsService.verifyJws(agentContext, { + jws: cNonce, + jwkResolver: () => jwk, + }) - private async getGrantsFromConfig( - agentContext: AgentContext, - preAuthorizedCodeFlowConfig: OpenId4VciPreAuthorizedCodeFlowConfig - ) { - const grants: Grant = { - 'urn:ietf:params:oauth:grant-type:pre-authorized_code': { - 'pre-authorized_code': - preAuthorizedCodeFlowConfig.preAuthorizedCode ?? (await agentContext.wallet.generateNonce()), - // v11 only - user_pin_required: preAuthorizedCodeFlowConfig.userPinRequired ?? false, - tx_code: preAuthorizedCodeFlowConfig.txCode, - }, + if ( + !verification.signerKeys + .map((singerKey) => singerKey.fingerprint) + .includes(issuer.accessTokenPublicKeyFingerprint) + ) { + throw new CredoError('Invalid nonce') } + } - return grants + public getIssuer(agentContext: AgentContext) { + return new Oid4vciIssuer({ + callbacks: getOid4vciCallbacks(agentContext), + }) } - private findOfferedCredentialsMatchingRequest( - agentContext: AgentContext, - credentialOffer: OpenId4VciCredentialOfferPayload, - credentialRequest: OpenId4VciCredentialRequest, - allCredentialConfigurationsSupported: OpenId4VciCredentialConfigurationsSupported, - issuanceSession: OpenId4VcIssuanceSessionRecord - ): OpenId4VciCredentialConfigurationsSupported { - const offeredCredentialsData = isCredentialOfferV1Draft13(credentialOffer) - ? credentialOffer.credential_configuration_ids - : credentialOffer.credentials + public getOauth2Client(agentContext: AgentContext) { + return new Oauth2Client({ + callbacks: getOid4vciCallbacks(agentContext), + }) + } - const { credentialConfigurationsSupported: offeredCredentialConfigurations } = getOfferedCredentials( - agentContext, - offeredCredentialsData, - allCredentialConfigurationsSupported - ) + public getOauth2AuthorizationServer(agentContext: AgentContext) { + return new Oauth2AuthorizationServer({ + callbacks: getOid4vciCallbacks(agentContext), + }) + } - if ('credential_identifier' in credentialRequest && typeof credentialRequest.credential_identifier === 'string') { - const offeredCredential = offeredCredentialConfigurations[credentialRequest.credential_identifier] - if (!offeredCredential) { - throw new CredoError( - `Requested credential with id '${credentialRequest.credential_identifier}' was not offered.` - ) - } + public getResourceServer(agentContext: AgentContext, issuerRecord: OpenId4VcIssuerRecord) { + return new Oauth2ResourceServer({ + callbacks: { + ...getOid4vciCallbacks(agentContext), + clientAuthentication: dynamicOid4vciClientAuthentication(agentContext, issuerRecord), + }, + }) + } - return { - [credentialRequest.credential_identifier]: offeredCredential, - } - } + /** + * Update the record to a new state and emit an state changed event. Also updates the record + * in storage. + */ + public async updateState( + agentContext: AgentContext, + issuanceSession: OpenId4VcIssuanceSessionRecord, + newState: OpenId4VcIssuanceSessionState + ) { + agentContext.config.logger.debug( + `Updating openid4vc issuance session record ${issuanceSession.id} to state ${newState} (previous=${issuanceSession.state})` + ) - return Object.fromEntries( - Object.entries(offeredCredentialConfigurations).filter(([id, offeredCredential]) => { - if (offeredCredential.format !== credentialRequest.format) return false - if (issuanceSession.issuedCredentials.includes(id)) return false - - if ( - credentialRequest.format === OpenId4VciCredentialFormatProfile.JwtVcJson && - offeredCredential.format === credentialRequest.format - ) { - const types = - 'credential_definition' in credentialRequest - ? credentialRequest.credential_definition.type - : credentialRequest.types - - return equalsIgnoreOrder(offeredCredential.credential_definition.type ?? [], types) - } else if ( - credentialRequest.format === OpenId4VciCredentialFormatProfile.JwtVcJsonLd && - offeredCredential.format === credentialRequest.format - ) { - const types = - 'type' in credentialRequest.credential_definition - ? credentialRequest.credential_definition.type - : credentialRequest.credential_definition.types - - return equalsIgnoreOrder(offeredCredential.credential_definition.type ?? [], types) - } else if ( - credentialRequest.format === OpenId4VciCredentialFormatProfile.LdpVc && - offeredCredential.format === credentialRequest.format - ) { - const types = - 'type' in credentialRequest.credential_definition - ? credentialRequest.credential_definition.type - : credentialRequest.credential_definition.types - - return equalsIgnoreOrder(offeredCredential.credential_definition.type ?? [], types) - } else if ( - credentialRequest.format === OpenId4VciCredentialFormatProfile.SdJwtVc && - offeredCredential.format === credentialRequest.format - ) { - return offeredCredential.vct === credentialRequest.vct - } else if ( - credentialRequest.format === OpenId4VciCredentialFormatProfile.MsoMdoc && - offeredCredential.format === credentialRequest.format - ) { - return offeredCredential.doctype === credentialRequest.doctype - } + const previousState = issuanceSession.state + issuanceSession.state = newState + await this.openId4VcIssuanceSessionRepository.update(agentContext, issuanceSession) - return false - }) - ) + this.emitStateChangedEvent(agentContext, issuanceSession, previousState) } - private getSdJwtVcCredentialSigningCallback = ( + public emitStateChangedEvent( agentContext: AgentContext, - options: OpenId4VciSignSdJwtCredential - ): CredentialSignerCallback => { - return async () => { - const sdJwtVcApi = agentContext.dependencyManager.resolve(SdJwtVcApi) + issuanceSession: OpenId4VcIssuanceSessionRecord, + previousState: OpenId4VcIssuanceSessionState | null + ) { + const eventEmitter = agentContext.dependencyManager.resolve(EventEmitter) - const sdJwtVc = await sdJwtVcApi.sign(options) - return getSphereonVerifiableCredential(sdJwtVc) - } + eventEmitter.emit(agentContext, { + type: OpenId4VcIssuerEvents.IssuanceSessionStateChanged, + payload: { + issuanceSession: issuanceSession.clone(), + previousState: previousState, + }, + }) } - private getMsoMdocCredentialSigningCallback = ( + private async getGrantsFromConfig( agentContext: AgentContext, - options: OpenId4VciSignMdocCredential - ): CredentialSignerCallback => { - return async () => { - const mdocApi = agentContext.dependencyManager.resolve(MdocApi) - - const mdoc = await mdocApi.sign(options) - return getSphereonVerifiableCredential(mdoc) + config: { + issuerMetadata: OpenId4VciMetadata + preAuthorizedCodeFlowConfig?: OpenId4VciPreAuthorizedCodeFlowConfig + authorizationCodeFlowConfig?: OpenId4VciAuthorizationCodeFlowConfig } - } + ) { + const { preAuthorizedCodeFlowConfig, authorizationCodeFlowConfig, issuerMetadata } = config - private getW3cCredentialSigningCallback = ( - agentContext: AgentContext, - options: OpenId4VciSignW3cCredential - ): CredentialSignerCallback => { - return async (opts) => { - const { jwtVerifyResult, format } = opts - const { kid, didDocument: holderDidDocument } = jwtVerifyResult + // TOOD: export type + const grants: Parameters[0]['grants'] = {} - if (!kid) throw new CredoError('Missing Kid. Cannot create the holder binding') - if (!holderDidDocument) throw new CredoError('Missing did document. Cannot create the holder binding.') - if (!format) throw new CredoError('Missing format. Cannot issue credential.') + // Pre auth + if (preAuthorizedCodeFlowConfig) { + const { txCode, authorizationServerUrl, preAuthorizedCode } = preAuthorizedCodeFlowConfig - const formatMap: Record = { - [OpenId4VciCredentialFormatProfile.JwtVcJson]: ClaimFormat.JwtVc, - [OpenId4VciCredentialFormatProfile.JwtVcJsonLd]: ClaimFormat.JwtVc, - [OpenId4VciCredentialFormatProfile.LdpVc]: ClaimFormat.LdpVc, - } - const w3cServiceFormat = formatMap[format] - - // Set the binding on the first credential subject if not set yet - // on any subject - if (!options.credential.credentialSubjectIds.includes(holderDidDocument.id)) { - const credentialSubject = Array.isArray(options.credential.credentialSubject) - ? options.credential.credentialSubject[0] - : options.credential.credentialSubject - credentialSubject.id = holderDidDocument.id + grants[preAuthorizedCodeGrantIdentifier] = { + 'pre-authorized_code': preAuthorizedCode ?? (await agentContext.wallet.generateNonce()), + tx_code: txCode, + authorization_server: config.issuerMetadata.credentialIssuer.authorization_servers + ? authorizationServerUrl + : undefined, } + } - const didsApi = agentContext.dependencyManager.resolve(DidsApi) - const issuerDidDocument = await didsApi.resolveDidDocument(options.verificationMethod) - const verificationMethod = issuerDidDocument.dereferenceVerificationMethod(options.verificationMethod) + // Auth + if (authorizationCodeFlowConfig) { + const { requirePresentationDuringIssuance } = authorizationCodeFlowConfig + let authorizationServerUrl = authorizationCodeFlowConfig.authorizationServerUrl - if (w3cServiceFormat === ClaimFormat.JwtVc) { - const key = getKeyFromVerificationMethod(verificationMethod) - const supportedSignatureAlgorithms = getJwkFromKey(key).supportedSignatureAlgorithms - if (supportedSignatureAlgorithms.length === 0) { - throw new CredoError(`No supported JWA signature algorithms found for key with keyType ${key.keyType}`) + if (requirePresentationDuringIssuance) { + if (authorizationServerUrl && authorizationServerUrl !== issuerMetadata.credentialIssuer.credential_issuer) { + throw new CredoError( + `When 'requirePresentationDuringIssuance' is set, 'authorizationServerUrl' must be undefined or match the credential issuer identifier` + ) } - const alg = supportedSignatureAlgorithms[0] - if (!alg) { - throw new CredoError(`No supported JWA signature algorithms for key type ${key.keyType}`) - } + authorizationServerUrl = issuerMetadata.credentialIssuer.credential_issuer + } - const signed = await this.w3cCredentialService.signCredential(agentContext, { - format: w3cServiceFormat, - credential: options.credential, - verificationMethod: options.verificationMethod, - alg, - }) + grants.authorization_code = { + issuer_state: + // TODO: the issuer_state should not be guessable, so it's best if we generate it and now allow the user to provide it? + // but same is true for the pre-auth code and users of credo can also provide that value. We can't easily do unique constraint with askat + authorizationCodeFlowConfig.issuerState ?? + TypedArrayEncoder.toBase64URL(agentContext.wallet.getRandomValues(32)), + authorization_server: config.issuerMetadata.credentialIssuer.authorization_servers + ? authorizationServerUrl + : undefined, + } + } + + return grants + } - return getSphereonVerifiableCredential(signed) - } else { - const key = getKeyFromVerificationMethod(verificationMethod) - const proofType = getProofTypeFromKey(agentContext, key) + private async getHolderBindingFromRequestProofs(agentContext: AgentContext, proofSigners: JwtSigner[]) { + const credentialHolderBindings: OpenId4VcCredentialHolderBindingWithKey[] = [] + for (const signer of proofSigners) { + if (signer.method === 'custom' || signer.method === 'x5c') { + throw new CredoError(`Only 'jwk' and 'did' based holder binding is supported`) + } - const signed = await this.w3cCredentialService.signCredential(agentContext, { - format: w3cServiceFormat, - credential: options.credential, - verificationMethod: options.verificationMethod, - proofType: proofType, + if (signer.method === 'jwk') { + const jwk = getJwkFromJson(signer.publicJwk) + credentialHolderBindings.push({ + method: 'jwk', + jwk, + key: jwk.key, }) + } - return getSphereonVerifiableCredential(signed) + if (signer.method === 'did') { + const key = await getKeyFromDid(agentContext, signer.didUrl) + credentialHolderBindings.push({ + method: 'did', + didUrl: signer.didUrl, + key, + }) } } + + return credentialHolderBindings } - private async getHolderBindingFromRequest( - agentContext: AgentContext, - credentialRequest: OpenId4VciCredentialRequest - ) { - if (!credentialRequest.proof?.jwt) throw new CredoError('Received a credential request without a proof') + private getCredentialConfigurationsForRequest(options: { + requestFormat: CredentialRequestFormatSpecific + issuerMetadata: OpenId4VciMetadata + issuanceSession: OpenId4VcIssuanceSessionRecord + authorization: OpenId4VciCredentialRequestAuthorization + }) { + const { requestFormat, issuanceSession, issuerMetadata, authorization } = options + + // Check against all credential configurations + const configurationsMatchingRequest = getCredentialConfigurationsMatchingRequestFormat({ + requestFormat, + credentialConfigurations: issuerMetadata.credentialIssuer.credential_configurations_supported, + }) + if (Object.keys(configurationsMatchingRequest).length === 0) { + throw new Oauth2ServerErrorResponseError({ + error: Oauth2ErrorCodes.InvalidCredentialRequest, + error_description: 'Credential request does not match any credential configuration', + }) + } - const jwt = Jwt.fromSerializedJwt(credentialRequest.proof.jwt) + // Limit to offered configurations + const configurationsMatchingRequestAndOffer = getOfferedCredentials( + issuanceSession.credentialOfferPayload.credential_configuration_ids, + configurationsMatchingRequest, + { ignoreNotFoundIds: true } + ) + if (Object.keys(configurationsMatchingRequestAndOffer).length === 0) { + throw new Oauth2ServerErrorResponseError({ + error: Oauth2ErrorCodes.InvalidCredentialRequest, + error_description: 'Credential request does not match any credential configurations from credential offer', + }) + } - if (jwt.header.kid) { - if (!jwt.header.kid.startsWith('did:')) { - throw new CredoError("Only did is supported for 'kid' identifier") - } else if (!jwt.header.kid.includes('#')) { - throw new CredoError( - `kid containing did MUST point to a specific key within the did document: ${jwt.header.kid}` - ) - } + // Limit to not-issued configurations + const configurationsMatchingRequestAndOfferNotIssued = getOfferedCredentials( + issuanceSession.credentialOfferPayload.credential_configuration_ids.filter( + (id) => !issuanceSession.issuedCredentials.includes(id) + ), + configurationsMatchingRequestAndOffer, + { ignoreNotFoundIds: true } + ) + if (Object.keys(configurationsMatchingRequestAndOfferNotIssued).length === 0) { + throw new Oauth2ServerErrorResponseError({ + error: Oauth2ErrorCodes.InvalidCredentialRequest, + error_description: + 'Credential request does not match any credential configurations from credential offer that have not been issued yet', + }) + } - const parsedDid = parseDid(jwt.header.kid) - if (!parsedDid.fragment) { - throw new Error(`didUrl '${parsedDid.didUrl}' does not contain a '#'. Unable to derive key from did document.`) + // For pre-auth we allow all ids from the offer + if (authorization.accessToken.payload['pre-authorized_code']) { + return { + credentialConfigurations: + configurationsMatchingRequestAndOfferNotIssued as OpenId4VciCredentialConfigurationsSupportedWithFormats, + credentialConfigurationIds: Object.keys(configurationsMatchingRequestAndOfferNotIssued) as [ + string, + ...string[] + ], } + } - const didResolver = agentContext.dependencyManager.resolve(DidResolverService) - const didDocument = await didResolver.resolveDidDocument(agentContext, parsedDid.didUrl) - const key = getKeyFromVerificationMethod(didDocument.dereferenceKey(parsedDid.didUrl, ['assertionMethod'])) + // Limit to scopes from the token + // We only do this for auth flow, so it's not required to add a scope for every configuration. + const configurationsMatchingRequestOfferScope = getCredentialConfigurationsSupportedForScopes( + configurationsMatchingRequestAndOfferNotIssued, + authorization.accessToken.payload.scope?.split(' ') ?? [] + ) + if (Object.keys(configurationsMatchingRequestOfferScope).length === 0) { + throw new Oauth2ServerErrorResponseError( + { + error: Oauth2ErrorCodes.InsufficientScope, + error_description: + 'Scope does not grant issuance for any requested credential configurations from credential offer', + }, + { + status: 403, + } + ) + } - return { - method: 'did', - didUrl: jwt.header.kid, - key, - } satisfies OpenId4VcCredentialHolderBinding & { key: Key } - } else if (jwt.header.jwk) { - const jwk = getJwkFromJson(jwt.header.jwk) - return { - method: 'jwk', - jwk: jwk, - key: jwk.key, - } satisfies OpenId4VcCredentialHolderBinding & { key: Key } - } else { - throw new CredoError('Either kid or jwk must be present in credential request proof header') + return { + credentialConfigurations: + configurationsMatchingRequestOfferScope as OpenId4VciCredentialConfigurationsSupportedWithFormats, + credentialConfigurationIds: Object.keys(configurationsMatchingRequestOfferScope) as [string, ...string[]], } } - private getCredentialDataSupplier = ( + private async getSignedCredentials( agentContext: AgentContext, options: OpenId4VciCreateCredentialResponseOptions & { issuer: OpenId4VcIssuerRecord issuanceSession: OpenId4VcIssuanceSessionRecord + requestFormat: CredentialRequestFormatSpecific + proofSigners: JwtSigner[] } - ): CredentialDataSupplier => { - return async (args: CredentialDataSupplierArgs) => { - const { issuanceSession, issuer } = options + ): Promise<{ + credentials: string[] | Record[] + format: `${OpenId4VciCredentialFormatProfile}` + credentialConfigurationId: string + }> { + const { issuanceSession, issuer, requestFormat, authorization } = options + const issuerMetadata = await this.getIssuerMetadata(agentContext, issuer) + + const { credentialConfigurations, credentialConfigurationIds } = this.getCredentialConfigurationsForRequest({ + issuanceSession, + issuerMetadata, + requestFormat, + authorization, + }) - const credentialRequest = args.credentialRequest as OpenId4VciCredentialRequest + const mapper = + options.credentialRequestToCredentialMapper ?? this.openId4VcIssuerConfig.credentialRequestToCredentialMapper - const issuerMetadata = this.getIssuerMetadata(agentContext, issuer) + let verification: OpenId4VciCredentialRequestToCredentialMapperOptions['verification'] = undefined - const offeredCredentialsMatchingRequest = this.findOfferedCredentialsMatchingRequest( - agentContext, - options.issuanceSession.credentialOfferPayload, - credentialRequest, - issuerMetadata.credentialConfigurationsSupported, - issuanceSession + // NOTE: this will throw an error if the verifier module is not registered and there is a + // verification session. But you can't get here without the verifier module anyway + if (issuanceSession.presentation?.openId4VcVerificationSessionId) { + const verifierApi = agentContext.dependencyManager.resolve(OpenId4VcVerifierApi) + const session = await verifierApi.getVerificationSessionById( + issuanceSession.presentation.openId4VcVerificationSessionId ) - const numOfferedCredentialsMatchingRequest = Object.keys(offeredCredentialsMatchingRequest).length - if (numOfferedCredentialsMatchingRequest === 0) { - throw new CredoError('No offered credentials match the credential request.') + const response = await verifierApi.getVerifiedAuthorizationResponse( + issuanceSession.presentation.openId4VcVerificationSessionId + ) + if (!response.presentationExchange) { + throw new CredoError( + `Verified authorization response for verification session with id '${session.id}' does not have presenationExchange defined.` + ) } - if (numOfferedCredentialsMatchingRequest > 1) { - agentContext.config.logger.debug( - 'Multiple credentials from credentials supported matching request, picking first one.' - ) + verification = { + session, + presentationExchange: response.presentationExchange, } + } - const mapper = - options.credentialRequestToCredentialMapper ?? - this.openId4VcIssuerConfig.credentialEndpoint.credentialRequestToCredentialMapper - - const credentialConfigurationIds = Object.entries(offeredCredentialsMatchingRequest).map( - ([credentialConfigurationId]) => credentialConfigurationId - ) as [string, ...string[]] - - const holderBinding = await this.getHolderBindingFromRequest(agentContext, credentialRequest) - const signOptions = await mapper({ - agentContext, - issuanceSession, - holderBinding, - credentialOffer: { credential_offer: issuanceSession.credentialOfferPayload }, - credentialRequest: credentialRequest, - credentialsSupported: credentialsSupportedV13ToV11(offeredCredentialsMatchingRequest), - credentialConfigurationIds, - }) + const holderBindings = await this.getHolderBindingFromRequestProofs(agentContext, options.proofSigners) + const signOptions = await mapper({ + agentContext, + issuanceSession, + holderBindings, + credentialOffer: issuanceSession.credentialOfferPayload, + + verification, + + credentialRequest: options.credentialRequest, + credentialRequestFormat: options.requestFormat, + + // Macthing credential configuration ids + credentialConfigurationsSupported: credentialConfigurations, + credentialConfigurationIds, + + // Authorization + authorization: options.authorization, + }) + + if (!credentialConfigurationIds.includes(signOptions.credentialConfigurationId)) { + throw new CredoError( + `Credential request to credential mapper returned credential configuration id '${ + signOptions.credentialConfigurationId + }' but is not part of provided input credential configuration ids. Allowed values are '${credentialConfigurationIds.join( + ', ' + )}'.` + ) + } - const credentialHasAlreadyBeenIssued = issuanceSession.issuedCredentials.includes( - signOptions.credentialSupportedId + // NOTE: we may want to allow a mismatch between this (as with new attestations not every key + // needs a separate proof), but for now it needs to match + if (signOptions.credentials.length !== holderBindings.length) { + throw new CredoError( + `Credential request to credential mapper returned '${signOptions.credentials.length}' to be signed, while only '${holderBindings.length}' holder binding entries were provided. Make sure to return one credential for each holder binding entry` ) - if (credentialHasAlreadyBeenIssued) { + } + + if (signOptions.format === ClaimFormat.JwtVc || signOptions.format === ClaimFormat.LdpVc) { + const oid4vciFormatMap: Record = { + [OpenId4VciCredentialFormatProfile.JwtVcJson]: ClaimFormat.JwtVc, + [OpenId4VciCredentialFormatProfile.JwtVcJsonLd]: ClaimFormat.JwtVc, + [OpenId4VciCredentialFormatProfile.LdpVc]: ClaimFormat.LdpVc, + } + + const expectedClaimFormat = oid4vciFormatMap[options.requestFormat.format] + if (signOptions.format !== expectedClaimFormat) { throw new CredoError( - `The requested credential with id '${signOptions.credentialSupportedId}' has already been issued.` + `Invalid credential format returned by sign options. Expected '${expectedClaimFormat}', received '${signOptions.format}'.` ) } - const updatedIssuanceSession = await this.openId4VcIssuanceSessionRepository.getById( - agentContext, - issuanceSession.id - ) - updatedIssuanceSession.issuedCredentials.push(signOptions.credentialSupportedId) - await this.openId4VcIssuanceSessionRepository.update(agentContext, updatedIssuanceSession) - - if (signOptions.format === ClaimFormat.JwtVc || signOptions.format === ClaimFormat.LdpVc) { - if (!w3cOpenId4VcFormats.includes(credentialRequest.format as OpenId4VciCredentialFormatProfile)) { - throw new CredoError( - `The credential to be issued does not match the request. Cannot issue a W3cCredential if the client expects a credential of format '${credentialRequest.format}'.` + return { + credentialConfigurationId: signOptions.credentialConfigurationId, + format: requestFormat.format, + credentials: (await Promise.all( + signOptions.credentials.map((credential) => + this.signW3cCredential(agentContext, signOptions.format, credential).then((signed) => signed.encoded) ) - } + )) as string[] | Record[], + } + } else if (signOptions.format === ClaimFormat.SdJwtVc) { + if (signOptions.format !== requestFormat.format) { + throw new CredoError( + `Invalid credential format returned by sign options. Expected '${requestFormat.format}', received '${signOptions.format}'.` + ) + } - return { - format: credentialRequest.format, - credential: JsonTransformer.toJSON(signOptions.credential) as ICredential, - signCallback: this.getW3cCredentialSigningCallback(agentContext, signOptions), - } - } else if (signOptions.format === ClaimFormat.SdJwtVc) { - if (credentialRequest.format !== OpenId4VciCredentialFormatProfile.SdJwtVc) { - throw new CredoError( - `Invalid credential format. Expected '${OpenId4VciCredentialFormatProfile.SdJwtVc}', received '${credentialRequest.format}'.` - ) - } - if (credentialRequest.vct !== signOptions.payload.vct) { - throw new CredoError( - `The types of the offered credentials do not match the types of the requested credential. Offered '${signOptions.payload.vct}' Requested '${credentialRequest.vct}'.` - ) - } + if (!signOptions.credentials.every((c) => c.payload.vct === requestFormat.vct)) { + throw new CredoError( + `One or more vct values of the offered credential(s) do not match the vct of the requested credential. Offered ${Array.from( + new Set(signOptions.credentials.map((c) => `'${c.payload.vct}'`)) + ).join(', ')} Requested '${requestFormat.vct}'.` + ) + } - return { - format: credentialRequest.format, - // NOTE: we don't use the credential value here as we pass the credential directly to the singer - credential: { ...signOptions.payload } as unknown as CredentialIssuanceInput, - signCallback: this.getSdJwtVcCredentialSigningCallback(agentContext, signOptions), - } - } else if (signOptions.format === ClaimFormat.MsoMdoc) { - if (credentialRequest.format !== OpenId4VciCredentialFormatProfile.MsoMdoc) { - throw new CredoError( - `Invalid credential format. Expected '${OpenId4VciCredentialFormatProfile.MsoMdoc}', received '${credentialRequest.format}'.` - ) - } + const sdJwtVcApi = agentContext.dependencyManager.resolve(SdJwtVcApi) + return { + credentialConfigurationId: signOptions.credentialConfigurationId, + format: OpenId4VciCredentialFormatProfile.SdJwtVc, + credentials: await Promise.all( + signOptions.credentials.map((credential) => sdJwtVcApi.sign(credential).then((signed) => signed.compact)) + ), + } + } else if (signOptions.format === ClaimFormat.MsoMdoc) { + if (signOptions.format !== requestFormat.format) { + throw new CredoError( + `Invalid credential format returned by sign options. Expected '${requestFormat.format}', received '${signOptions.format}'.` + ) + } + if (!signOptions.credentials.every((c) => c.docType === requestFormat.doctype)) { + throw new CredoError( + `One or more doctype values of the offered credential(s) do not match the doctype of the requested credential. Offered ${Array.from( + new Set(signOptions.credentials.map((c) => `'${c.docType}'`)) + ).join(', ')} Requested '${requestFormat.doctype}'.` + ) + } - if (credentialRequest.doctype !== signOptions.docType) { - throw new CredoError( - `The types of the offered credentials do not match the types of the requested credential. Offered '${signOptions.docType}' Requested '${credentialRequest.doctype}'.` - ) - } + const mdocApi = agentContext.dependencyManager.resolve(MdocApi) + return { + credentialConfigurationId: signOptions.credentialConfigurationId, + format: OpenId4VciCredentialFormatProfile.MsoMdoc, + credentials: await Promise.all( + signOptions.credentials.map((credential) => mdocApi.sign(credential).then((signed) => signed.base64Url)) + ), + } + } else { + throw new CredoError(`Unsupported credential format ${signOptions.format}`) + } + } - return { - format: credentialRequest.format, - // NOTE: we don't use the credential value here as we pass the credential directly to the singer - credential: { ...signOptions.namespaces, docType: signOptions.docType } as unknown as CredentialIssuanceInput, - signCallback: this.getMsoMdocCredentialSigningCallback(agentContext, signOptions), - } - } else { - throw new CredoError(`Unsupported credential format ${signOptions.format}`) + private async signW3cCredential( + agentContext: AgentContext, + format: `${ClaimFormat.JwtVc}` | `${ClaimFormat.LdpVc}`, + options: OpenId4VciSignW3cCredentials['credentials'][number] + ) { + const key = await getKeyFromDid(agentContext, options.verificationMethod) + if (format === ClaimFormat.JwtVc) { + const supportedSignatureAlgorithms = getJwkFromKey(key).supportedSignatureAlgorithms + if (supportedSignatureAlgorithms.length === 0) { + throw new CredoError(`No supported JWA signature algorithms found for key with keyType ${key.keyType}`) + } + + const alg = supportedSignatureAlgorithms[0] + if (!alg) { + throw new CredoError(`No supported JWA signature algorithms for key type ${key.keyType}`) } + + return await this.w3cCredentialService.signCredential(agentContext, { + format: ClaimFormat.JwtVc, + credential: options.credential, + verificationMethod: options.verificationMethod, + alg, + }) + } else { + const proofType = getProofTypeFromKey(agentContext, key) + + return await this.w3cCredentialService.signCredential(agentContext, { + format: ClaimFormat.LdpVc, + credential: options.credential, + verificationMethod: options.verificationMethod, + proofType: proofType, + }) } } } diff --git a/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerServiceOptions.ts b/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerServiceOptions.ts index ab9d47c4a3..6425fc0aa0 100644 --- a/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerServiceOptions.ts +++ b/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerServiceOptions.ts @@ -1,19 +1,20 @@ +import type { OpenId4VcIssuanceSessionRecord, OpenId4VcIssuerRecordProps } from './repository' import type { - OpenId4VcIssuanceSessionRecord, - OpenId4VcIssuerRecordCredentialConfigurationsSupportedProps, - OpenId4VcIssuerRecordCredentialSupportedProps, - OpenId4VcIssuerRecordProps, -} from './repository' + OpenId4VcSiopCreateAuthorizationRequestReturn, + OpenId4VcSiopVerifiedAuthorizationResponsePresentationExchange, + OpenId4VcVerificationSessionRecord, +} from '../openid4vc-verifier' import type { - OpenId4VcCredentialHolderBinding, - OpenId4VciCredentialConfigurationsSupported, - OpenId4VciCredentialOffer, + OpenId4VcCredentialHolderBindingWithKey, + OpenId4VciCredentialConfigurationsSupportedWithFormats, + OpenId4VciCredentialOfferPayload, OpenId4VciCredentialRequest, - OpenId4VciCredentialSupported, - OpenId4VciCredentialSupportedWithId, - OpenId4VciIssuerMetadataDisplay, + OpenId4VciCredentialRequestFormatSpecific, + OpenId4VciCredentialIssuerMetadataDisplay, OpenId4VciTxCode, } from '../shared' +import type { OpenId4VciAuthorizationServerConfig } from '../shared/models/OpenId4VciAuthorizationServerConfig' +import type { AccessTokenProfileJwtPayload, TokenIntrospectionResponse } from '@animo-id/oauth2' import type { AgentContext, ClaimFormat, @@ -21,37 +22,66 @@ import type { SdJwtVcSignOptions, JwaSignatureAlgorithm, MdocSignOptions, - Key, + KeyType, } from '@credo-ts/core' +export interface OpenId4VciCredentialRequestAuthorization { + authorizationServer: string + accessToken: { + payload: AccessTokenProfileJwtPayload | TokenIntrospectionResponse + value: string + } +} + export interface OpenId4VciPreAuthorizedCodeFlowConfig { preAuthorizedCode?: string + /** * The user pin required flag indicates whether the user needs to enter a pin to authorize the transaction. - * Only compatible with v11 - */ - userPinRequired?: boolean - /** - * The user pin required flag indicates whether the user needs to enter a pin to authorize the transaction. - * Only compatible with v13 */ txCode?: OpenId4VciTxCode + + // OPTIONAL string that the Wallet can use to identify the Authorization Server to use with this grant + // type when authorization_servers parameter in the Credential Issuer metadata has multiple entries. + authorizationServerUrl?: string } -export type OpenId4VcIssuerMetadata = { - // The Credential Issuer's identifier. (URL using the https scheme) - issuerUrl: string - credentialEndpoint: string - tokenEndpoint: string - authorizationServer?: string +export interface OpenId4VciAuthorizationCodeFlowConfig { + /** + * OPTIONAL. String value created by the Credential Issuer and opaque to the Wallet + * that is used to bind the subsequent Authorization Request with the Credential Issuer + * to a context set up during previous steps. + * If not provided, a value will be generated. + */ + issuerState?: string + + /** + * OPTIONAL string that the Wallet can use to identify the Authorization Server to use with this grant + * type when authorization_servers parameter in the Credential Issuer metadata has multiple entries. + */ + authorizationServerUrl?: string - issuerDisplay?: OpenId4VciIssuerMetadataDisplay[] - credentialsSupported: OpenId4VciCredentialSupportedWithId[] - credentialConfigurationsSupported: OpenId4VciCredentialConfigurationsSupported - dpopSigningAlgValuesSupported?: [JwaSignatureAlgorithm, ...JwaSignatureAlgorithm[]] + /** + * Whether presentation using OpenID4VP is required as part of the authorization flow. The presentation + * request will be created dynamically when the wallet initiates the authorization flow using the + * `getVerificationSessionForIssuanceSessionAuthorization` callback in the issuer module config. + * + * You can dynamically create the verification session based on the provided issuace session, or you + * can have a more generic implementation based on credential configurations and scopes that are being + * requested. + * + * In case this parameter is set to true, `authorizationServerUrl` MUST be undefined or match the + * `credential_issuer` value, as only Credo can handle this flow. + * + * In case this parameter is set to true, and `getVerificationSessionForIssuanceSessionAuthorization` is + * not configured on the issuer module an error will be thrown. + * + * @default false + */ + requirePresentationDuringIssuance?: boolean } -export interface OpenId4VciCreateCredentialOfferOptions { +interface OpenId4VciCreateCredentialOfferOptionsBase { // NOTE: v11 of OID4VCI supports both inline and referenced (to credentials_supported.id) credential offers. // In draft 12 the inline credential offers have been removed and to make the migration to v12 easier // we only support referenced credentials in an offer @@ -63,7 +93,27 @@ export interface OpenId4VciCreateCredentialOfferOptions { */ baseUri?: string - preAuthorizedCodeFlowConfig: OpenId4VciPreAuthorizedCodeFlowConfig + /** + * @default v1.draft11-13 + */ + version?: 'v1.draft11-13' | 'v1.draft13' +} + +export interface OpenId4VciCreateStatelessCredentialOfferOptions extends OpenId4VciCreateCredentialOfferOptionsBase { + authorizationCodeFlowConfig: Required> + + /** + * For stateless credential offers we need an external authorization server, which also means we need to + * support `authorization_servers`, therefore only draft 13 offers are supported + * + * @default v1.draft13 + */ + version?: 'v1.draft13' +} + +export interface OpenId4VciCreateCredentialOfferOptions extends OpenId4VciCreateCredentialOfferOptionsBase { + preAuthorizedCodeFlowConfig?: OpenId4VciPreAuthorizedCodeFlowConfig + authorizationCodeFlowConfig?: OpenId4VciAuthorizationCodeFlowConfig /** * Metadata about the issuance, that will be stored in the issuance session record and @@ -72,15 +122,11 @@ export interface OpenId4VciCreateCredentialOfferOptions { * data. */ issuanceMetadata?: Record - - /** - * @default v1.draft11-13 - */ - version?: 'v1.draft11-13' | 'v1.draft13' } export interface OpenId4VciCreateCredentialResponseOptions { credentialRequest: OpenId4VciCredentialRequest + authorization: OpenId4VciCredentialRequestAuthorization /** * You can optionally provide a credential request to credential mapper that will be @@ -92,14 +138,59 @@ export interface OpenId4VciCreateCredentialResponseOptions { credentialRequestToCredentialMapper?: OpenId4VciCredentialRequestToCredentialMapper } -// FIXME: Flows: -// - provide credential data at time of offer creation (NOT SUPPORTED) -// - provide credential data at time of calling createCredentialResponse (partially supported by passing in mapper to this method -> preferred as it gives you request data dynamically) -// - provide credential data dynamically using this method (SUPPORTED) -// mapper should get input data passed (which is supplied to offer or create response) like credentialDataSupplierInput in sphereon lib -export type OpenId4VciCredentialRequestToCredentialMapper = (options: { +/** + * Callback that is called when a verification session needs to be created to complete + * authorization of credential issuance. + * + * + */ +export type OpenId4VciGetVerificationSessionForIssuanceSessionAuthorization = (options: { + agentContext: AgentContext + issuanceSession: OpenId4VcIssuanceSessionRecord + + /** + * The credential configurations for which authorization has been requested based on the **scope** + * values. It doesn't mean the wallet will request all credentials to be issued. + */ + requestedCredentialConfigurations: OpenId4VciCredentialConfigurationsSupportedWithFormats + + /** + * The scopes which were requested and are also present in the credential configurations supported + * that were offered. It will match with the scope values in the `requestedCredentialConfiguration` + * parameter + */ + scopes: string[] +}) => Promise< + OpenId4VcSiopCreateAuthorizationRequestReturn & { + /** + * The scopes which will be granted by successfully completing the verification + * session. + * + * @todo do we need more granular support? I.e. every input descriptor can satisfy a + * different scope? + */ + scopes: string[] + } +> + +export interface OpenId4VciCredentialRequestToCredentialMapperOptions { agentContext: AgentContext + /** + * Authorization associated with the credential request + */ + authorization: OpenId4VciCredentialRequestAuthorization + + /** + * If an openid4vp verification was done as part of the authorization flow this parameter will be defined. + * + * The contents can be used to populate credential data + */ + verification?: { + session: OpenId4VcVerificationSessionRecord + presentationExchange: OpenId4VcSiopVerifiedAuthorizationResponsePresentationExchange + } + /** * The issuance session associated with the credential request. You can extract the * issuance metadata from this record if passed in the offer creation method. @@ -111,27 +202,31 @@ export type OpenId4VciCredentialRequestToCredentialMapper = (options: { */ credentialRequest: OpenId4VciCredentialRequest + /** + * Contains format specific credential request data. Currently it will + * always be defined, but may be undefined once `credential_identifier` + * in the credential request will be supported + */ + credentialRequestFormat?: OpenId4VciCredentialRequestFormatSpecific + /** * The offer associated with the credential request */ - credentialOffer: OpenId4VciCredentialOffer + credentialOffer: OpenId4VciCredentialOfferPayload /** - * Verified key binding material that should be included in the credential + * Verified key binding material entries that should be included in the credential(s) + * A separate credential should be returned for each holder binding. * - * Can either be bound to did or a JWK (in case of for ex. SD-JWT) + * Can either be bound to did or a JWK (in case of for ex. SD-JWT). */ - holderBinding: OpenId4VcCredentialHolderBinding & { key: Key } + holderBindings: OpenId4VcCredentialHolderBindingWithKey[] /** - * @deprecated use credentialConfigurations instead - * - * The credentials supported entries from the issuer metadata that were offered - * and match the incoming request - * - * NOTE: in v12 this will probably become a single entry, as it will be matched on id + * The credential configurations supported entries from the issuer metadata + * that were offered and match the incoming request. */ - credentialsSupported: OpenId4VciCredentialSupported[] + credentialConfigurationsSupported: OpenId4VciCredentialConfigurationsSupportedWithFormats /** * v13: The ids of the credential configurations that were offered and match the request @@ -139,28 +234,42 @@ export type OpenId4VciCredentialRequestToCredentialMapper = (options: { * NOTE: This will probably become a single entry, as it will be matched on id */ credentialConfigurationIds: [string, ...string[]] -}) => Promise | OpenId4VciSignCredential +} +export type OpenId4VciCredentialRequestToCredentialMapper = ( + options: OpenId4VciCredentialRequestToCredentialMapperOptions +) => Promise | OpenId4VciSignCredentials -export type OpenId4VciSignCredential = - | OpenId4VciSignSdJwtCredential - | OpenId4VciSignW3cCredential - | OpenId4VciSignMdocCredential +export type OpenId4VciSignCredentials = + | OpenId4VciSignSdJwtCredentials + | OpenId4VciSignW3cCredentials + | OpenId4VciSignMdocCredentials -export interface OpenId4VciSignSdJwtCredential extends SdJwtVcSignOptions { - credentialSupportedId: string +export interface OpenId4VciSignSdJwtCredentials { + credentialConfigurationId: string format: ClaimFormat.SdJwtVc | `${ClaimFormat.SdJwtVc}` + credentials: SdJwtVcSignOptions[] } -export interface OpenId4VciSignMdocCredential extends MdocSignOptions { - credentialSupportedId: string +export interface OpenId4VciSignMdocCredentials { + credentialConfigurationId: string format: ClaimFormat.MsoMdoc | `${ClaimFormat.MsoMdoc}` + credentials: MdocSignOptions[] } -export interface OpenId4VciSignW3cCredential { - credentialSupportedId: string +export interface OpenId4VciSignW3cCredentials { + credentialConfigurationId: string format: ClaimFormat.JwtVc | `${ClaimFormat.JwtVc}` | ClaimFormat.LdpVc | `${ClaimFormat.LdpVc}` - verificationMethod: string - credential: W3cCredential + credentials: Array<{ + verificationMethod: string + credential: W3cCredential + }> +} + +export interface OpenId4VciBatchCredentialIssuanceOptions { + /** + * The maximum batch size + */ + batchSize: number } export type OpenId4VciCreateIssuerOptions = { @@ -169,12 +278,29 @@ export type OpenId4VciCreateIssuerOptions = { */ issuerId?: string - display?: OpenId4VciIssuerMetadataDisplay[] + /** + * Key type to use for signing access tokens + * + * @default KeyType.Ed25519 + */ + accessTokenSignerKeyType?: KeyType + + display?: OpenId4VciCredentialIssuerMetadataDisplay[] + authorizationServerConfigs?: OpenId4VciAuthorizationServerConfig[] dpopSigningAlgValuesSupported?: [JwaSignatureAlgorithm, ...JwaSignatureAlgorithm[]] -} & (OpenId4VcIssuerRecordCredentialSupportedProps | OpenId4VcIssuerRecordCredentialConfigurationsSupportedProps) + credentialConfigurationsSupported: OpenId4VciCredentialConfigurationsSupportedWithFormats + + /** + * Indicate support for batch issuane of credentials + */ + batchCredentialIssuance?: OpenId4VciBatchCredentialIssuanceOptions +} export type OpenId4VcUpdateIssuerRecordOptions = Pick< OpenId4VcIssuerRecordProps, - 'issuerId' | 'display' | 'dpopSigningAlgValuesSupported' -> & - (OpenId4VcIssuerRecordCredentialSupportedProps | OpenId4VcIssuerRecordCredentialConfigurationsSupportedProps) + | 'issuerId' + | 'display' + | 'dpopSigningAlgValuesSupported' + | 'credentialConfigurationsSupported' + | 'batchCredentialIssuance' +> diff --git a/packages/openid4vc/src/openid4vc-issuer/__tests__/OpenId4VcIsserModule.test.ts b/packages/openid4vc/src/openid4vc-issuer/__tests__/OpenId4VcIsserModule.test.ts index f6351696ed..2cd330f315 100644 --- a/packages/openid4vc/src/openid4vc-issuer/__tests__/OpenId4VcIsserModule.test.ts +++ b/packages/openid4vc/src/openid4vc-issuer/__tests__/OpenId4VcIsserModule.test.ts @@ -22,12 +22,8 @@ describe('OpenId4VcIssuerModule', () => { test('registers dependencies on the dependency manager', async () => { const options = { baseUrl: 'http://localhost:3000', - endpoints: { - credential: { - credentialRequestToCredentialMapper: () => { - throw new Error('Not implemented') - }, - }, + credentialRequestToCredentialMapper: () => { + throw new Error('Not implemented') }, router: Router(), } as const diff --git a/packages/openid4vc/src/openid4vc-issuer/__tests__/openid4vc-issuer.test.ts b/packages/openid4vc/src/openid4vc-issuer/__tests__/openid4vc-issuer.test.ts index 63441cf9e0..6dc0d25f26 100644 --- a/packages/openid4vc/src/openid4vc-issuer/__tests__/openid4vc-issuer.test.ts +++ b/packages/openid4vc/src/openid4vc-issuer/__tests__/openid4vc-issuer.test.ts @@ -1,8 +1,9 @@ -import type { OpenId4VciCredentialRequest, OpenId4VciCredentialSupportedWithId } from '../../shared' import type { - OpenId4VcIssuerMetadata, - OpenId4VciCredentialRequestToCredentialMapper, -} from '../OpenId4VcIssuerServiceOptions' + OpenId4VciCredentialConfigurationSupportedWithFormats, + OpenId4VciCredentialRequest, + OpenId4VciMetadata, +} from '../../shared' +import type { OpenId4VciCredentialRequestToCredentialMapper } from '../OpenId4VcIssuerServiceOptions' import type { OpenId4VcIssuerRecord } from '../repository' import type { AgentContext, @@ -11,7 +12,6 @@ import type { W3cVerifiableCredential, W3cVerifyCredentialResult, } from '@credo-ts/core' -import type { OriginalVerifiableCredential as SphereonW3cVerifiableCredential } from '@sphereon/ssi-types' import { SdJwtVcApi, @@ -32,7 +32,6 @@ import { W3cJwtVerifiableCredential, equalsIgnoreOrder, getJwkFromKey, - getKeyFromVerificationMethod, w3cDate, } from '@credo-ts/core' @@ -40,44 +39,48 @@ import { AskarModule } from '../../../../askar/src' import { askarModuleConfig } from '../../../../askar/tests/helpers' import { agentDependencies } from '../../../../node/src' import { OpenId4VciCredentialFormatProfile } from '../../shared' +import { dateToSeconds, getKeyFromDid } from '../../shared/utils' import { OpenId4VcIssuanceSessionState } from '../OpenId4VcIssuanceSessionState' import { OpenId4VcIssuerModule } from '../OpenId4VcIssuerModule' +import { OpenId4VcIssuerService } from '../OpenId4VcIssuerService' import { OpenId4VcIssuanceSessionRepository } from '../repository' const openBadgeCredential = { - id: 'https://openid4vc-issuer.com/credentials/OpenBadgeCredential', + id: 'openBadgeCredential', format: OpenId4VciCredentialFormatProfile.JwtVcJson, - types: ['VerifiableCredential', 'OpenBadgeCredential'], -} satisfies OpenId4VciCredentialSupportedWithId + credential_definition: { + type: ['VerifiableCredential', 'OpenBadgeCredential'], + }, +} satisfies OpenId4VciCredentialConfigurationSupportedWithFormats const universityDegreeCredential = { - id: 'https://openid4vc-issuer.com/credentials/UniversityDegreeCredential', + id: 'universityDegreeCredential', format: OpenId4VciCredentialFormatProfile.JwtVcJson, - types: ['VerifiableCredential', 'UniversityDegreeCredential'], -} satisfies OpenId4VciCredentialSupportedWithId + credential_definition: { + type: ['VerifiableCredential', 'UniversityDegreeCredential'], + }, +} satisfies OpenId4VciCredentialConfigurationSupportedWithFormats const universityDegreeCredentialLd = { - id: 'https://openid4vc-issuer.com/credentials/UniversityDegreeCredentialLd', + id: 'universityDegreeCredentialLd', format: OpenId4VciCredentialFormatProfile.JwtVcJsonLd, - '@context': [], - types: ['VerifiableCredential', 'UniversityDegreeCredential'], -} satisfies OpenId4VciCredentialSupportedWithId + credential_definition: { + '@context': [], + type: ['VerifiableCredential', 'UniversityDegreeCredential'], + }, +} satisfies OpenId4VciCredentialConfigurationSupportedWithFormats const universityDegreeCredentialSdJwt = { - id: 'https://openid4vc-issuer.com/credentials/UniversityDegreeCredentialSdJwt', + id: 'universityDegreeCredentialSdJwt', format: OpenId4VciCredentialFormatProfile.SdJwtVc, vct: 'UniversityDegreeCredential', -} satisfies OpenId4VciCredentialSupportedWithId +} satisfies OpenId4VciCredentialConfigurationSupportedWithFormats const modules = { openId4VcIssuer: new OpenId4VcIssuerModule({ baseUrl: 'https://openid4vc-issuer.com', - endpoints: { - credential: { - credentialRequestToCredentialMapper: () => { - throw new Error('Not implemented') - }, - }, + credentialRequestToCredentialMapper: () => { + throw new Error('Not implemented') }, }), askar: new AskarModule(askarModuleConfig), @@ -88,14 +91,14 @@ const jwsService = new JwsService() const createCredentialRequest = async ( agentContext: AgentContext, options: { - issuerMetadata: OpenId4VcIssuerMetadata - credentialSupported: OpenId4VciCredentialSupportedWithId + issuerMetadata: OpenId4VciMetadata + credentialConfiguration: OpenId4VciCredentialConfigurationSupportedWithFormats nonce: string kid: string clientId?: string // use with the authorization code flow, } ): Promise => { - const { credentialSupported, kid, nonce, issuerMetadata, clientId } = options + const { credentialConfiguration, kid, nonce, issuerMetadata, clientId } = options const didsApi = agentContext.dependencyManager.resolve(DidsApi) const didDocument = await didsApi.resolveDidDocument(kid) @@ -103,16 +106,15 @@ const createCredentialRequest = async ( throw new CredoError(`No verification method found for kid ${kid}`) } - const verificationMethod = didDocument.dereferenceKey(kid, ['authentication', 'assertionMethod']) - const key = getKeyFromVerificationMethod(verificationMethod) + const key = await getKeyFromDid(agentContext, kid) const jwk = getJwkFromKey(key) const jws = await jwsService.createJwsCompact(agentContext, { protectedHeaderOptions: { alg: jwk.supportedSignatureAlgorithms[0], kid, typ: 'openid4vci-proof+jwt' }, payload: new JwtPayload({ - iat: Math.floor(Date.now() / 1000), // unix time + iat: dateToSeconds(new Date()), iss: clientId, - aud: issuerMetadata.issuerUrl, + aud: issuerMetadata.credentialIssuer.credential_issuer, additionalClaims: { nonce, }, @@ -120,23 +122,23 @@ const createCredentialRequest = async ( key, }) - if (credentialSupported.format === OpenId4VciCredentialFormatProfile.JwtVcJson) { - return { ...credentialSupported, proof: { jwt: jws, proof_type: 'jwt' } } + if (credentialConfiguration.format === OpenId4VciCredentialFormatProfile.JwtVcJson) { + return { ...credentialConfiguration, proof: { jwt: jws, proof_type: 'jwt' } } } else if ( - credentialSupported.format === OpenId4VciCredentialFormatProfile.JwtVcJsonLd || - credentialSupported.format === OpenId4VciCredentialFormatProfile.LdpVc + credentialConfiguration.format === OpenId4VciCredentialFormatProfile.JwtVcJsonLd || + credentialConfiguration.format === OpenId4VciCredentialFormatProfile.LdpVc ) { return { - format: credentialSupported.format, + format: credentialConfiguration.format, credential_definition: { - '@context': credentialSupported['@context'], - types: credentialSupported.types, + '@context': credentialConfiguration.credential_definition['@context'], + types: credentialConfiguration.credential_definition.type, }, proof: { jwt: jws, proof_type: 'jwt' }, } - } else if (credentialSupported.format === OpenId4VciCredentialFormatProfile.SdJwtVc) { - return { ...credentialSupported, proof: { jwt: jws, proof_type: 'jwt' } } + } else if (credentialConfiguration.format === OpenId4VciCredentialFormatProfile.SdJwtVc) { + return { ...credentialConfiguration, proof: { jwt: jws, proof_type: 'jwt' } } } throw new Error('Unsupported format') @@ -211,12 +213,12 @@ describe('OpenId4VcIssuer', () => { issuerVerificationMethod = _issuerVerificationMethod openId4VcIssuer = await issuer.modules.openId4VcIssuer.createIssuer({ - credentialsSupported: [ + credentialConfigurationsSupported: { openBadgeCredential, universityDegreeCredential, universityDegreeCredentialLd, universityDegreeCredentialSdJwt, - ], + }, }) }) @@ -232,12 +234,12 @@ describe('OpenId4VcIssuer', () => { // would be nice to reuse async function handleCredentialResponse( agentContext: AgentContext, - sphereonVerifiableCredential: SphereonW3cVerifiableCredential, - credentialSupported: OpenId4VciCredentialSupportedWithId + credentialInResponse: string | Record | undefined, + credentialConfiguration: OpenId4VciCredentialConfigurationSupportedWithFormats ) { - if (credentialSupported.format === 'vc+sd-jwt' && typeof sphereonVerifiableCredential === 'string') { + if (credentialConfiguration.format === 'vc+sd-jwt' && typeof credentialInResponse === 'string') { const api = agentContext.dependencyManager.resolve(SdJwtVcApi) - await api.verify({ compactSdJwtVc: sphereonVerifiableCredential }) + await api.verify({ compactSdJwtVc: credentialInResponse }) return } @@ -246,17 +248,17 @@ describe('OpenId4VcIssuer', () => { let result: W3cVerifyCredentialResult let w3cVerifiableCredential: W3cVerifiableCredential - if (typeof sphereonVerifiableCredential === 'string') { - if (credentialSupported.format !== 'jwt_vc_json' && credentialSupported.format !== 'jwt_vc_json-ld') { - throw new Error(`Invalid format. ${credentialSupported.format}`) + if (typeof credentialInResponse === 'string') { + if (credentialConfiguration.format !== 'jwt_vc_json' && credentialConfiguration.format !== 'jwt_vc_json-ld') { + throw new Error(`Invalid format. ${credentialConfiguration.format}`) } - w3cVerifiableCredential = W3cJwtVerifiableCredential.fromSerializedJwt(sphereonVerifiableCredential) + w3cVerifiableCredential = W3cJwtVerifiableCredential.fromSerializedJwt(credentialInResponse) result = await w3cCredentialService.verifyCredential(holder.context, { credential: w3cVerifiableCredential }) - } else if (credentialSupported.format === 'ldp_vc') { - if (credentialSupported.format !== 'ldp_vc') throw new Error('Invalid format') + } else if (credentialConfiguration.format === 'ldp_vc') { + if (credentialConfiguration.format !== 'ldp_vc') throw new Error('Invalid format') // validate jwt credentials - w3cVerifiableCredential = JsonTransformer.fromJSON(sphereonVerifiableCredential, W3cJsonLdVerifiableCredential) + w3cVerifiableCredential = JsonTransformer.fromJSON(credentialInResponse, W3cJsonLdVerifiableCredential) result = await w3cCredentialService.verifyCredential(holder.context, { credential: w3cVerifiableCredential }) } else { throw new CredoError(`Unsupported credential format`) @@ -267,7 +269,7 @@ describe('OpenId4VcIssuer', () => { throw new CredoError(`Failed to validate credential, error = ${result.error?.message ?? 'Unknown'}`) } - if (equalsIgnoreOrder(w3cVerifiableCredential.type, credentialSupported.types) === false) { + if (equalsIgnoreOrder(w3cVerifiableCredential.type, credentialConfiguration.credential_definition.type) === false) { throw new Error('Invalid credential type') } return w3cVerifiableCredential @@ -281,13 +283,11 @@ describe('OpenId4VcIssuer', () => { offeredCredentials: [universityDegreeCredentialSdJwt.id], preAuthorizedCodeFlowConfig: { preAuthorizedCode, - userPinRequired: false, }, }) const issuanceSessionRepository = issuer.context.dependencyManager.resolve(OpenId4VcIssuanceSessionRepository) - result.issuanceSession.cNonce = '1234' - result.issuanceSession.cNonceExpiresAt = new Date(Date.now() + 30000) // 30 seconds + const issuerService = issuer.context.dependencyManager.resolve(OpenId4VcIssuerService) await issuanceSessionRepository.update(issuer.context, result.issuanceSession) expect(result).toMatchObject({ @@ -299,64 +299,68 @@ describe('OpenId4VcIssuer', () => { issuanceSession: { credentialOfferPayload: { credential_issuer: `https://openid4vc-issuer.com/${openId4VcIssuer.issuerId}`, - credentials: ['https://openid4vc-issuer.com/credentials/UniversityDegreeCredentialSdJwt'], + credentials: ['universityDegreeCredentialSdJwt'], grants: { 'urn:ietf:params:oauth:grant-type:pre-authorized_code': { 'pre-authorized_code': '1234567890', - user_pin_required: false, }, }, }, }, }) + const { cNonce } = await issuerService.createNonce(issuer.context, openId4VcIssuer) const issuerMetadata = await issuer.modules.openId4VcIssuer.getIssuerMetadata(openId4VcIssuer.issuerId) const credentialRequest = await createCredentialRequest(holder.context, { - credentialSupported: universityDegreeCredentialSdJwt, + credentialConfiguration: universityDegreeCredentialSdJwt, issuerMetadata, kid: holderKid, - nonce: result.issuanceSession.cNonce as string, + nonce: cNonce, }) - const issuanceSession = await issuer.modules.openId4VcIssuer.findIssuanceSessionForCredentialRequest({ - credentialRequest, - issuerId: openId4VcIssuer.issuerId, - }) - - if (!issuanceSession) { - throw new Error('No issuance session found') - } - // We need to update the state, as it is checked and we're skipping the access token step result.issuanceSession.state = OpenId4VcIssuanceSessionState.AccessTokenCreated await issuanceSessionRepository.update(issuer.context, result.issuanceSession) const { credentialResponse } = await issuer.modules.openId4VcIssuer.createCredentialResponse({ - issuanceSessionId: issuanceSession.id, + issuanceSessionId: result.issuanceSession.id, credentialRequest, + authorization: { + authorizationServer: 'https://authorization.com', + accessToken: { + payload: { + active: true, + sub: 'something', + 'pre-authorized_code': 'some', + }, + value: 'the-access-token', + }, + }, credentialRequestToCredentialMapper: () => ({ format: 'vc+sd-jwt', - payload: { vct: 'UniversityDegreeCredential', university: 'innsbruck', degree: 'bachelor' }, - issuer: { method: 'did', didUrl: issuerVerificationMethod.id }, - holder: { method: 'did', didUrl: holderVerificationMethod.id }, - disclosureFrame: { _sd: ['university', 'degree'] }, - credentialSupportedId: universityDegreeCredentialSdJwt.id, + credentials: [ + { + payload: { vct: 'UniversityDegreeCredential', university: 'innsbruck', degree: 'bachelor' }, + issuer: { method: 'did', didUrl: issuerVerificationMethod.id }, + holder: { method: 'did', didUrl: holderVerificationMethod.id }, + disclosureFrame: { _sd: ['university', 'degree'] }, + }, + ], + credentialConfigurationId: universityDegreeCredentialSdJwt.id, }), }) expect(credentialResponse).toEqual({ c_nonce: expect.any(String), - c_nonce_expires_in: 300, + c_nonce_expires_in: 60, credential: expect.any(String), format: 'vc+sd-jwt', + credentials: undefined, + notification_id: undefined, }) - await handleCredentialResponse( - holder.context, - credentialResponse.credential as SphereonW3cVerifiableCredential, - universityDegreeCredentialSdJwt - ) + await handleCredentialResponse(holder.context, credentialResponse.credential, universityDegreeCredentialSdJwt) }) it('pre authorized code flow (sd-jwt-vc) v13', async () => { @@ -377,8 +381,6 @@ describe('OpenId4VcIssuer', () => { }) const issuanceSessionRepository = issuer.context.dependencyManager.resolve(OpenId4VcIssuanceSessionRepository) - result.issuanceSession.cNonce = '1234' - result.issuanceSession.cNonceExpiresAt = new Date(Date.now() + 30000) // 30 seconds await issuanceSessionRepository.update(issuer.context, result.issuanceSession) expect(result).toMatchObject({ @@ -390,7 +392,7 @@ describe('OpenId4VcIssuer', () => { issuanceSession: { credentialOfferPayload: { credential_issuer: `https://openid4vc-issuer.com/${openId4VcIssuer.issuerId}`, - credential_configuration_ids: ['https://openid4vc-issuer.com/credentials/UniversityDegreeCredentialSdJwt'], + credential_configuration_ids: ['universityDegreeCredentialSdJwt'], grants: { 'urn:ietf:params:oauth:grant-type:pre-authorized_code': { 'pre-authorized_code': '1234567890', @@ -407,52 +409,58 @@ describe('OpenId4VcIssuer', () => { const issuerMetadata = await issuer.modules.openId4VcIssuer.getIssuerMetadata(openId4VcIssuer.issuerId) + const issuerService = issuer.context.dependencyManager.resolve(OpenId4VcIssuerService) + const { cNonce } = await issuerService.createNonce(issuer.context, openId4VcIssuer) const credentialRequest = await createCredentialRequest(holder.context, { - credentialSupported: universityDegreeCredentialSdJwt, + credentialConfiguration: universityDegreeCredentialSdJwt, issuerMetadata, kid: holderKid, - nonce: result.issuanceSession.cNonce as string, - }) - - const issuanceSession = await issuer.modules.openId4VcIssuer.findIssuanceSessionForCredentialRequest({ - credentialRequest, - issuerId: openId4VcIssuer.issuerId, + nonce: cNonce, }) - if (!issuanceSession) { - throw new Error('No issuance session found') - } - // We need to update the state, as it is checked and we're skipping the access token step result.issuanceSession.state = OpenId4VcIssuanceSessionState.AccessTokenCreated await issuanceSessionRepository.update(issuer.context, result.issuanceSession) const { credentialResponse } = await issuer.modules.openId4VcIssuer.createCredentialResponse({ - issuanceSessionId: issuanceSession.id, + issuanceSessionId: result.issuanceSession.id, credentialRequest, + authorization: { + authorizationServer: 'https://authorization.com', + accessToken: { + payload: { + active: true, + sub: 'something', + 'pre-authorized_code': 'some', + }, + value: 'the-access-token', + }, + }, credentialRequestToCredentialMapper: () => ({ format: 'vc+sd-jwt', - payload: { vct: 'UniversityDegreeCredential', university: 'innsbruck', degree: 'bachelor' }, - issuer: { method: 'did', didUrl: issuerVerificationMethod.id }, - holder: { method: 'did', didUrl: holderVerificationMethod.id }, - disclosureFrame: { _sd: ['university', 'degree'] }, - credentialSupportedId: universityDegreeCredentialSdJwt.id, + credentials: [ + { + payload: { vct: 'UniversityDegreeCredential', university: 'innsbruck', degree: 'bachelor' }, + issuer: { method: 'did', didUrl: issuerVerificationMethod.id }, + holder: { method: 'did', didUrl: holderVerificationMethod.id }, + disclosureFrame: { _sd: ['university', 'degree'] }, + }, + ], + credentialConfigurationId: universityDegreeCredentialSdJwt.id, }), }) expect(credentialResponse).toEqual({ c_nonce: expect.any(String), - c_nonce_expires_in: 300, + c_nonce_expires_in: 60, credential: expect.any(String), format: 'vc+sd-jwt', // Should not be present in v13, only for v11 compat + credentials: undefined, + notification_id: undefined, }) - await handleCredentialResponse( - holder.context, - credentialResponse.credential as SphereonW3cVerifiableCredential, - universityDegreeCredentialSdJwt - ) + await handleCredentialResponse(holder.context, credentialResponse.credential, universityDegreeCredentialSdJwt) }) it('pre authorized code flow (jwt-vc-json)', async () => { @@ -463,7 +471,6 @@ describe('OpenId4VcIssuer', () => { offeredCredentials: [openBadgeCredential.id], preAuthorizedCodeFlowConfig: { preAuthorizedCode, - userPinRequired: false, }, issuanceMetadata: { myIssuance: 'metadata', @@ -471,17 +478,27 @@ describe('OpenId4VcIssuer', () => { }) const issuanceSessionRepository = issuer.context.dependencyManager.resolve(OpenId4VcIssuanceSessionRepository) - // We need to update the state, as it is checked and we're skipping the access token step - result.issuanceSession.cNonce = '1234' - result.issuanceSession.cNonceExpiresAt = new Date(Date.now() + 30000) // 30 seconds result.issuanceSession.state = OpenId4VcIssuanceSessionState.AccessTokenCreated await issuanceSessionRepository.update(issuer.context, result.issuanceSession) expect(result.credentialOffer).toBeDefined() const issuerMetadata = await issuer.modules.openId4VcIssuer.getIssuerMetadata(openId4VcIssuer.issuerId) + const issuerService = issuer.context.dependencyManager.resolve(OpenId4VcIssuerService) + const { cNonce } = await issuerService.createNonce(issuer.context, openId4VcIssuer) const { credentialResponse } = await issuer.modules.openId4VcIssuer.createCredentialResponse({ issuanceSessionId: result.issuanceSession.id, + authorization: { + authorizationServer: 'https://authorization.com', + accessToken: { + payload: { + active: true, + sub: 'something', + 'pre-authorized_code': 'some', + }, + value: 'the-access-token', + }, + }, credentialRequestToCredentialMapper: ({ issuanceSession }) => { expect(issuanceSession.id).toEqual(result.issuanceSession.id) expect(issuanceSession.issuanceMetadata).toEqual({ @@ -490,36 +507,39 @@ describe('OpenId4VcIssuer', () => { return { format: 'jwt_vc', - credentialSupportedId: openBadgeCredential.id, - credential: new W3cCredential({ - type: openBadgeCredential.types, - issuer: new W3cIssuer({ id: issuerDid }), - credentialSubject: new W3cCredentialSubject({ id: holderDid }), - issuanceDate: w3cDate(Date.now()), - }), - verificationMethod: issuerVerificationMethod.id, + credentialConfigurationId: openBadgeCredential.id, + credentials: [ + { + credential: new W3cCredential({ + type: openBadgeCredential.credential_definition.type, + issuer: new W3cIssuer({ id: issuerDid }), + credentialSubject: new W3cCredentialSubject({ id: holderDid }), + issuanceDate: w3cDate(Date.now()), + }), + verificationMethod: issuerVerificationMethod.id, + }, + ], } }, + credentialRequest: await createCredentialRequest(holder.context, { - credentialSupported: openBadgeCredential, + credentialConfiguration: openBadgeCredential, issuerMetadata, kid: holderKid, - nonce: result.issuanceSession.cNonce as string, + nonce: cNonce, }), }) expect(credentialResponse).toEqual({ c_nonce: expect.any(String), - c_nonce_expires_in: 300, + c_nonce_expires_in: 60, credential: expect.any(String), format: 'jwt_vc_json', + credentials: undefined, + notification_id: undefined, }) - await handleCredentialResponse( - holder.context, - credentialResponse.credential as SphereonW3cVerifiableCredential, - openBadgeCredential - ) + await handleCredentialResponse(holder.context, credentialResponse.credential, openBadgeCredential) }) it('credential id not in credential supported errors', async () => { @@ -531,11 +551,10 @@ describe('OpenId4VcIssuer', () => { offeredCredentials: ['invalid id'], preAuthorizedCodeFlowConfig: { preAuthorizedCode, - userPinRequired: false, }, }) ).rejects.toThrow( - "Offered credential 'invalid id' is not part of credentials_supported/credential_configurations_supported of the issuer metadata." + "Credential configuration ids invalid id not found in the credential issuer metadata 'credential_configurations_supported'. Available ids are openBadgeCredential, universityDegreeCredential, universityDegreeCredentialLd, universityDegreeCredentialSdJwt" ) }) @@ -547,32 +566,42 @@ describe('OpenId4VcIssuer', () => { offeredCredentials: [openBadgeCredential.id], preAuthorizedCodeFlowConfig: { preAuthorizedCode, - userPinRequired: false, }, }) const issuanceSessionRepository = issuer.context.dependencyManager.resolve(OpenId4VcIssuanceSessionRepository) // We need to update the state, as it is checked and we're skipping the access token step result.issuanceSession.state = OpenId4VcIssuanceSessionState.AccessTokenCreated - result.issuanceSession.cNonce = '1234' - result.issuanceSession.cNonceExpiresAt = new Date(Date.now() + 30000) // 30 seconds await issuanceSessionRepository.update(issuer.context, result.issuanceSession) const issuerMetadata = await issuer.modules.openId4VcIssuer.getIssuerMetadata(openId4VcIssuer.issuerId) + const issuerService = issuer.context.dependencyManager.resolve(OpenId4VcIssuerService) + const { cNonce } = await issuerService.createNonce(issuer.context, openId4VcIssuer) await expect( issuer.modules.openId4VcIssuer.createCredentialResponse({ issuanceSessionId: result.issuanceSession.id, + authorization: { + authorizationServer: 'https://authorization.com', + accessToken: { + payload: { + active: true, + sub: 'something', + 'pre-authorized_code': 'some', + }, + value: 'the-access-token', + }, + }, credentialRequest: await createCredentialRequest(holder.context, { - credentialSupported: universityDegreeCredential, + credentialConfiguration: universityDegreeCredential, issuerMetadata, kid: holderKid, - nonce: result.issuanceSession.cNonce as string, + nonce: cNonce, }), credentialRequestToCredentialMapper: () => { throw new Error('Not implemented') }, }) - ).rejects.toThrow('No offered credentials match the credential request.') + ).rejects.toThrow('Credential request does not match any credential configurations from credential offer') }) it('pre authorized code flow using multiple credentials_supported', async () => { @@ -583,51 +612,63 @@ describe('OpenId4VcIssuer', () => { issuerId: openId4VcIssuer.issuerId, preAuthorizedCodeFlowConfig: { preAuthorizedCode, - userPinRequired: false, }, }) const issuanceSessionRepository = issuer.context.dependencyManager.resolve(OpenId4VcIssuanceSessionRepository) // We need to update the state, as it is checked and we're skipping the access token step - result.issuanceSession.cNonce = '1234' - result.issuanceSession.cNonceExpiresAt = new Date(Date.now() + 30000) // 30 seconds result.issuanceSession.state = OpenId4VcIssuanceSessionState.AccessTokenCreated await issuanceSessionRepository.update(issuer.context, result.issuanceSession) + const issuerService = issuer.context.dependencyManager.resolve(OpenId4VcIssuerService) + const { cNonce } = await issuerService.createNonce(issuer.context, openId4VcIssuer) const issuerMetadata = await issuer.modules.openId4VcIssuer.getIssuerMetadata(openId4VcIssuer.issuerId) const { credentialResponse } = await issuer.modules.openId4VcIssuer.createCredentialResponse({ issuanceSessionId: result.issuanceSession.id, credentialRequest: await createCredentialRequest(holder.context, { - credentialSupported: universityDegreeCredentialLd, + credentialConfiguration: universityDegreeCredentialLd, issuerMetadata, kid: holderKid, - nonce: result.issuanceSession.cNonce as string, + nonce: cNonce, }), + authorization: { + authorizationServer: 'https://authorization.com', + accessToken: { + payload: { + active: true, + sub: 'something', + 'pre-authorized_code': 'some', + }, + value: 'the-access-token', + }, + }, credentialRequestToCredentialMapper: () => ({ format: 'jwt_vc', - credential: new W3cCredential({ - type: universityDegreeCredentialLd.types, - issuer: new W3cIssuer({ id: issuerDid }), - credentialSubject: new W3cCredentialSubject({ id: holderDid }), - issuanceDate: w3cDate(Date.now()), - }), - credentialSupportedId: universityDegreeCredentialLd.id, - verificationMethod: issuerVerificationMethod.id, + credentials: [ + { + credential: new W3cCredential({ + type: universityDegreeCredentialLd.credential_definition.type, + issuer: new W3cIssuer({ id: issuerDid }), + credentialSubject: new W3cCredentialSubject({ id: holderDid }), + issuanceDate: w3cDate(Date.now()), + }), + verificationMethod: issuerVerificationMethod.id, + }, + ], + credentialConfigurationId: universityDegreeCredentialLd.id, }), }) expect(credentialResponse).toEqual({ c_nonce: expect.any(String), - c_nonce_expires_in: 300, + c_nonce_expires_in: 60, credential: expect.any(String), format: 'jwt_vc_json-ld', + credentials: undefined, + notification_id: undefined, }) - await handleCredentialResponse( - holder.context, - credentialResponse.credential as SphereonW3cVerifiableCredential, - universityDegreeCredentialLd - ) + await handleCredentialResponse(holder.context, credentialResponse.credential, universityDegreeCredentialLd) }) it('requesting non offered credential errors', async () => { @@ -638,36 +679,48 @@ describe('OpenId4VcIssuer', () => { issuerId: openId4VcIssuer.issuerId, preAuthorizedCodeFlowConfig: { preAuthorizedCode, - userPinRequired: false, }, }) const issuanceSessionRepository = issuer.context.dependencyManager.resolve(OpenId4VcIssuanceSessionRepository) // We need to update the state, as it is checked and we're skipping the access token step result.issuanceSession.state = OpenId4VcIssuanceSessionState.AccessTokenCreated - result.issuanceSession.cNonce = '1234' - result.issuanceSession.cNonceExpiresAt = new Date(Date.now() + 30000) // 30 seconds await issuanceSessionRepository.update(issuer.context, result.issuanceSession) + const issuerService = issuer.context.dependencyManager.resolve(OpenId4VcIssuerService) + const { cNonce } = await issuerService.createNonce(issuer.context, openId4VcIssuer) const issuerMetadata = await issuer.modules.openId4VcIssuer.getIssuerMetadata(openId4VcIssuer.issuerId) await expect( issuer.modules.openId4VcIssuer.createCredentialResponse({ issuanceSessionId: result.issuanceSession.id, + authorization: { + authorizationServer: 'https://authorization.com', + accessToken: { + payload: { + active: true, + sub: 'something', + 'pre-authorized_code': 'some', + }, + value: 'the-access-token', + }, + }, credentialRequest: await createCredentialRequest(holder.context, { - credentialSupported: { + credentialConfiguration: { id: 'someid', format: openBadgeCredential.format, - types: universityDegreeCredential.types, + credential_definition: { + type: universityDegreeCredential.credential_definition.type, + }, }, issuerMetadata, kid: holderKid, - nonce: result.issuanceSession.cNonce as string, + nonce: cNonce, }), credentialRequestToCredentialMapper: () => { throw new Error('Not implemented') }, }) - ).rejects.toThrow('No offered credentials match the credential request.') + ).rejects.toThrow('Credential request does not match any credential configurations from credential offer') }) it('create credential offer and retrieve it from the uri (pre authorized flow)', async () => { @@ -678,7 +731,6 @@ describe('OpenId4VcIssuer', () => { offeredCredentials: [openBadgeCredential.id], preAuthorizedCodeFlowConfig: { preAuthorizedCode, - userPinRequired: false, }, }) @@ -697,13 +749,10 @@ describe('OpenId4VcIssuer', () => { issuerId: openId4VcIssuer.issuerId, preAuthorizedCodeFlowConfig: { preAuthorizedCode, - userPinRequired: false, }, }) const issuanceSessionRepository = issuer.context.dependencyManager.resolve(OpenId4VcIssuanceSessionRepository) - result.issuanceSession.cNonce = '1234' - result.issuanceSession.cNonceExpiresAt = new Date(Date.now() + 30000) // 30 seconds await issuanceSessionRepository.update(issuer.context, result.issuanceSession) const payload = result.issuanceSession.credentialOfferPayload @@ -719,15 +768,18 @@ describe('OpenId4VcIssuer', () => { credentialConfigurationIds[0] === openBadgeCredential.id ? openBadgeCredential : universityDegreeCredential return { format: 'jwt_vc', - credential: new W3cCredential({ - type: credential.types, - issuer: new W3cIssuer({ id: issuerDid }), - credentialSubject: new W3cCredentialSubject({ id: holderDid }), - issuanceDate: w3cDate(Date.now()), - }), - credentialSupportedId: credential.id, - - verificationMethod: issuerVerificationMethod.id, + credentials: [ + { + credential: new W3cCredential({ + type: credential.credential_definition.type, + issuer: new W3cIssuer({ id: issuerDid }), + credentialSubject: new W3cCredentialSubject({ id: holderDid }), + issuanceDate: w3cDate(Date.now()), + }), + verificationMethod: issuerVerificationMethod.id, + }, + ], + credentialConfigurationId: credential.id, } } @@ -735,53 +787,73 @@ describe('OpenId4VcIssuer', () => { result.issuanceSession.state = OpenId4VcIssuanceSessionState.AccessTokenCreated await issuanceSessionRepository.update(issuer.context, result.issuanceSession) + const issuerService = issuer.context.dependencyManager.resolve(OpenId4VcIssuerService) + const { cNonce } = await issuerService.createNonce(issuer.context, openId4VcIssuer) const issuerMetadata = await issuer.modules.openId4VcIssuer.getIssuerMetadata(openId4VcIssuer.issuerId) const { credentialResponse } = await issuer.modules.openId4VcIssuer.createCredentialResponse({ issuanceSessionId: result.issuanceSession.id, credentialRequest: await createCredentialRequest(holder.context, { - credentialSupported: openBadgeCredential, + credentialConfiguration: openBadgeCredential, issuerMetadata, kid: holderKid, - nonce: result.issuanceSession.cNonce as string, + nonce: cNonce, }), + authorization: { + authorizationServer: 'https://authorization.com', + accessToken: { + payload: { + active: true, + sub: 'something', + 'pre-authorized_code': 'some', + }, + value: 'the-access-token', + }, + }, credentialRequestToCredentialMapper, }) expect(credentialResponse).toEqual({ c_nonce: expect.any(String), - c_nonce_expires_in: 300, + c_nonce_expires_in: 60, credential: expect.any(String), format: 'jwt_vc_json', + credentials: undefined, + notification_id: undefined, }) - await handleCredentialResponse( - holder.context, - credentialResponse.credential as SphereonW3cVerifiableCredential, - openBadgeCredential - ) + await handleCredentialResponse(holder.context, credentialResponse.credential, openBadgeCredential) const { credentialResponse: credentialResponse2 } = await issuer.modules.openId4VcIssuer.createCredentialResponse({ issuanceSessionId: result.issuanceSession.id, credentialRequest: await createCredentialRequest(holder.context, { - credentialSupported: universityDegreeCredential, + credentialConfiguration: universityDegreeCredential, issuerMetadata, kid: holderKid, - nonce: credentialResponse.c_nonce ?? (result.issuanceSession.cNonce as string), + nonce: credentialResponse.c_nonce ?? cNonce, }), + authorization: { + authorizationServer: 'https://authorization.com', + accessToken: { + payload: { + active: true, + sub: 'something', + 'pre-authorized_code': 'some', + }, + value: 'the-access-token', + }, + }, credentialRequestToCredentialMapper, }) expect(credentialResponse2).toEqual({ c_nonce: expect.any(String), - c_nonce_expires_in: 300, + c_nonce_expires_in: 60, credential: expect.any(String), format: 'jwt_vc_json', + credentials: undefined, + notification_id: undefined, }) - await handleCredentialResponse( - holder.context, - credentialResponse2.credential as SphereonW3cVerifiableCredential, - universityDegreeCredential - ) + await handleCredentialResponse(holder.context, credentialResponse2.credential, universityDegreeCredential) }) }) diff --git a/packages/openid4vc/src/openid4vc-issuer/repository/OpenId4VcCNonceStateManager.ts b/packages/openid4vc/src/openid4vc-issuer/repository/OpenId4VcCNonceStateManager.ts deleted file mode 100644 index 7473719435..0000000000 --- a/packages/openid4vc/src/openid4vc-issuer/repository/OpenId4VcCNonceStateManager.ts +++ /dev/null @@ -1,123 +0,0 @@ -import type { AgentContext } from '@credo-ts/core' -import type { CNonceState, IStateManager } from '@sphereon/oid4vci-common' - -import { CredoError } from '@credo-ts/core' - -import { OpenId4VcIssuerModuleConfig } from '../OpenId4VcIssuerModuleConfig' - -import { OpenId4VcIssuanceSessionRepository } from './OpenId4VcIssuanceSessionRepository' - -export class OpenId4VcCNonceStateManager implements IStateManager { - private openId4VcIssuanceSessionRepository: OpenId4VcIssuanceSessionRepository - private openId4VcIssuerModuleConfig: OpenId4VcIssuerModuleConfig - - public constructor(private agentContext: AgentContext, private issuerId: string) { - this.openId4VcIssuanceSessionRepository = agentContext.dependencyManager.resolve(OpenId4VcIssuanceSessionRepository) - this.openId4VcIssuerModuleConfig = agentContext.dependencyManager.resolve(OpenId4VcIssuerModuleConfig) - } - - public async set(cNonce: string, stateValue: CNonceState): Promise { - // Just to make sure that the cNonce is the same as the id as that's what we use to query - if (cNonce !== stateValue.cNonce) { - throw new CredoError('Expected the id of the cNonce state to be equal to the cNonce') - } - - if (!stateValue.preAuthorizedCode) { - throw new CredoError("Expected the stateValue to have a 'preAuthorizedCode' property") - } - - // Record MUST exist (otherwise there's no issuance session active yet) - const record = await this.openId4VcIssuanceSessionRepository.getSingleByQuery(this.agentContext, { - // NOTE: once we support authorized flow, we need to add an $or for the issuer state as well - issuerId: this.issuerId, - preAuthorizedCode: stateValue.preAuthorizedCode, - }) - - // cNonce already matches, no need to update - if (record.cNonce === stateValue.cNonce) { - return - } - - const expiresAtDate = new Date( - Date.now() + this.openId4VcIssuerModuleConfig.accessTokenEndpoint.cNonceExpiresInSeconds * 1000 - ) - - record.cNonce = stateValue.cNonce - record.cNonceExpiresAt = expiresAtDate - await this.openId4VcIssuanceSessionRepository.update(this.agentContext, record) - } - - public async get(cNonce: string): Promise { - const record = await this.openId4VcIssuanceSessionRepository.findSingleByQuery(this.agentContext, { - issuerId: this.issuerId, - cNonce, - }) - - if (!record) return undefined - - // NOTE: This should not happen as we query by the credential offer uri - // so it's mostly to make TS happy - if (!record.cNonce) { - throw new CredoError('No cNonce found on record.') - } - - return { - cNonce: record.cNonce, - preAuthorizedCode: record.preAuthorizedCode, - createdAt: record.createdAt.getTime(), - } - } - - public async has(cNonce: string): Promise { - const record = await this.openId4VcIssuanceSessionRepository.findSingleByQuery(this.agentContext, { - issuerId: this.issuerId, - cNonce, - }) - - return record !== undefined - } - - public async delete(cNonce: string): Promise { - const record = await this.openId4VcIssuanceSessionRepository.findSingleByQuery(this.agentContext, { - issuerId: this.issuerId, - cNonce, - }) - - if (!record) return false - - // We only remove the cNonce from the record, we don't want to remove - // the whole issuance session. - record.cNonce = undefined - record.cNonceExpiresAt = undefined - await this.openId4VcIssuanceSessionRepository.update(this.agentContext, record) - return true - } - - public async clearExpired(): Promise { - // FIXME: we should have a way to remove expired records - // or just not return the value in the get if the record is expired - throw new Error('Method not implemented.') - } - - public async clearAll(): Promise { - throw new Error('Method not implemented.') - } - - public async getAsserted(id: string): Promise { - const state = await this.get(id) - - if (!state) { - throw new CredoError(`No cNonce state found for id ${id}`) - } - - return state - } - - public async startCleanupRoutine(): Promise { - throw new Error('Method not implemented.') - } - - public async stopCleanupRoutine(): Promise { - throw new Error('Method not implemented.') - } -} diff --git a/packages/openid4vc/src/openid4vc-issuer/repository/OpenId4VcCredentialOfferSessionStateManager.ts b/packages/openid4vc/src/openid4vc-issuer/repository/OpenId4VcCredentialOfferSessionStateManager.ts deleted file mode 100644 index bef6de43aa..0000000000 --- a/packages/openid4vc/src/openid4vc-issuer/repository/OpenId4VcCredentialOfferSessionStateManager.ts +++ /dev/null @@ -1,228 +0,0 @@ -import type { OpenId4VcIssuanceSessionStateChangedEvent } from '../OpenId4VcIssuerEvents' -import type { AgentContext } from '@credo-ts/core' -import type { CredentialOfferSession, IStateManager } from '@sphereon/oid4vci-common' - -import { CredoError, EventEmitter } from '@credo-ts/core' -import { IssueStatus } from '@sphereon/oid4vci-common' - -import { isCredentialOfferV1Draft13 } from '../../shared/utils' -import { OpenId4VcIssuanceSessionState } from '../OpenId4VcIssuanceSessionState' -import { OpenId4VcIssuerEvents } from '../OpenId4VcIssuerEvents' - -import { OpenId4VcIssuanceSessionRecord } from './OpenId4VcIssuanceSessionRecord' -import { OpenId4VcIssuanceSessionRepository } from './OpenId4VcIssuanceSessionRepository' - -export class OpenId4VcCredentialOfferSessionStateManager implements IStateManager { - private openId4VcIssuanceSessionRepository: OpenId4VcIssuanceSessionRepository - private eventEmitter: EventEmitter - - public constructor(private agentContext: AgentContext, private issuerId: string) { - this.openId4VcIssuanceSessionRepository = agentContext.dependencyManager.resolve(OpenId4VcIssuanceSessionRepository) - this.eventEmitter = agentContext.dependencyManager.resolve(EventEmitter) - } - - public async set(preAuthorizedCode: string, stateValue: CredentialOfferSession): Promise { - // Just to make sure that the preAuthorizedCode is the same as the id as that's what we use to query - // NOTE: once we support authorized flow, we need to also allow the id to be equal to issuer state - if (preAuthorizedCode !== stateValue.preAuthorizedCode) { - throw new CredoError('Expected the id of the credential offer state to be equal to the preAuthorizedCode') - } - - if (!stateValue.preAuthorizedCode) { - throw new CredoError("Expected the stateValue to have a 'preAuthorizedCode' property") - } - - // Record may already exist - let record = await this.openId4VcIssuanceSessionRepository.findSingleByQuery(this.agentContext, { - issuerId: this.issuerId, - preAuthorizedCode: stateValue.preAuthorizedCode, - }) - - const previousState = record?.state ?? null - - let credentialOfferUri = stateValue.credentialOffer.credential_offer_uri - if (!credentialOfferUri) { - throw new CredoError("Expected the stateValue to have a 'credentialOfferUri' property") - } - - if (credentialOfferUri.includes('credential_offer_uri=')) { - // NOTE: it's a bit cumbersome, but the credential_offer_uri is the encoded uri. This seems - // odd to me, as this is the offer payload, which should only contain the hosted URI (I think - // this is a bug in OID4VCI). But for now we have to extract the uri from the payload. - credentialOfferUri = decodeURIComponent(credentialOfferUri.split('credential_offer_uri=')[1].split('=')[0]) - } - - let state = openId4VcIssuanceStateFromSphereon(stateValue.status) - - if (state === OpenId4VcIssuanceSessionState.CredentialsPartiallyIssued) { - // we set the completed state manually when all credentials have been issued - const issuedCredentials = record?.issuedCredentials?.length ?? 0 - const credentialOffer = stateValue.credentialOffer.credential_offer - - const offeredCredentials = isCredentialOfferV1Draft13(credentialOffer) - ? credentialOffer.credential_configuration_ids // v13 - : credentialOffer.credentials // v11 - - if (issuedCredentials >= offeredCredentials.length) { - state = OpenId4VcIssuanceSessionState.Completed - } - } - - // TODO: sphereon currently sets the wrong prop - const userPin = - stateValue.txCode ?? - ('userPin' in stateValue && typeof stateValue.userPin === 'string' ? stateValue.userPin : undefined) - - // NOTE: we don't use clientId at the moment, will become relevant when doing the authorized flow - if (record) { - record.issuanceMetadata = stateValue.credentialDataSupplierInput - record.credentialOfferPayload = stateValue.credentialOffer.credential_offer - record.userPin = userPin - record.preAuthorizedCode = stateValue.preAuthorizedCode - record.errorMessage = stateValue.error - record.credentialOfferUri = credentialOfferUri - record.state = state - await this.openId4VcIssuanceSessionRepository.update(this.agentContext, record) - } else { - record = new OpenId4VcIssuanceSessionRecord({ - issuerId: this.issuerId, - preAuthorizedCode: stateValue.preAuthorizedCode, - issuanceMetadata: stateValue.credentialDataSupplierInput, - credentialOfferPayload: stateValue.credentialOffer.credential_offer, - credentialOfferUri, - userPin: userPin, - errorMessage: stateValue.error, - state: state, - }) - - await this.openId4VcIssuanceSessionRepository.save(this.agentContext, record) - } - - this.emitStateChangedEvent(this.agentContext, record, previousState) - } - - public async get(preAuthorizedCode: string): Promise { - const record = await this.openId4VcIssuanceSessionRepository.findSingleByQuery(this.agentContext, { - issuerId: this.issuerId, - preAuthorizedCode, - }) - - if (!record) return undefined - - // NOTE: This should not happen as we query by the preAuthorizedCode - // so it's mostly to make TS happy - if (!record.preAuthorizedCode) { - throw new CredoError("No 'preAuthorizedCode' found on record.") - } - - if (!record.credentialOfferPayload) { - throw new CredoError("No 'credentialOfferPayload' found on record.") - } - - return { - credentialOffer: { - credential_offer: record.credentialOfferPayload, - credential_offer_uri: record.credentialOfferUri, - }, - notification_id: '', // TODO! This probably needs to have a different structure allowing to receive notifications on a per credential request basis - status: sphereonIssueStatusFromOpenId4VcIssuanceState(record.state), - preAuthorizedCode: record.preAuthorizedCode, - credentialDataSupplierInput: record.issuanceMetadata, - error: record.errorMessage, - txCode: record.userPin, - createdAt: record.createdAt.getTime(), - lastUpdatedAt: record.updatedAt?.getTime() ?? record.createdAt.getTime(), - } - } - - public async has(preAuthorizedCode: string): Promise { - const record = await this.openId4VcIssuanceSessionRepository.findSingleByQuery(this.agentContext, { - issuerId: this.issuerId, - preAuthorizedCode, - }) - - return record !== undefined - } - - public async delete(preAuthorizedCode: string): Promise { - const record = await this.openId4VcIssuanceSessionRepository.findSingleByQuery(this.agentContext, { - issuerId: this.issuerId, - preAuthorizedCode, - }) - - if (!record) return false - - await this.openId4VcIssuanceSessionRepository.deleteById(this.agentContext, record.id) - return true - } - - public async clearExpired(): Promise { - // FIXME: we should have a way to remove expired records - // or just not return the value in the get if the record is expired - throw new Error('Method not implemented.') - } - - public async clearAll(): Promise { - throw new Error('Method not implemented.') - } - - public async getAsserted(preAuthorizedCode: string): Promise { - const state = await this.get(preAuthorizedCode) - - if (!state) { - throw new CredoError(`No credential offer state found for id ${preAuthorizedCode}`) - } - - return state - } - - public async startCleanupRoutine(): Promise { - throw new Error('Method not implemented.') - } - - public async stopCleanupRoutine(): Promise { - throw new Error('Method not implemented.') - } - - protected emitStateChangedEvent( - agentContext: AgentContext, - issuanceSession: OpenId4VcIssuanceSessionRecord, - previousState: OpenId4VcIssuanceSessionState | null - ) { - this.eventEmitter.emit(agentContext, { - type: OpenId4VcIssuerEvents.IssuanceSessionStateChanged, - payload: { - issuanceSession: issuanceSession.clone(), - previousState, - }, - }) - } -} - -function openId4VcIssuanceStateFromSphereon(stateValue: IssueStatus): OpenId4VcIssuanceSessionState { - if (stateValue === IssueStatus.OFFER_CREATED) return OpenId4VcIssuanceSessionState.OfferCreated - if (stateValue === IssueStatus.OFFER_URI_RETRIEVED) return OpenId4VcIssuanceSessionState.OfferUriRetrieved - if (stateValue === IssueStatus.ACCESS_TOKEN_REQUESTED) return OpenId4VcIssuanceSessionState.AccessTokenRequested - if (stateValue === IssueStatus.ACCESS_TOKEN_CREATED) return OpenId4VcIssuanceSessionState.AccessTokenCreated - if (stateValue === IssueStatus.CREDENTIAL_REQUEST_RECEIVED) - return OpenId4VcIssuanceSessionState.CredentialRequestReceived - // we set the completed state manually when all credentials have been issued - if (stateValue === IssueStatus.CREDENTIAL_ISSUED) return OpenId4VcIssuanceSessionState.CredentialsPartiallyIssued - if (stateValue === IssueStatus.ERROR) return OpenId4VcIssuanceSessionState.Error - - throw new CredoError(`Unknown state value: ${stateValue}`) -} - -function sphereonIssueStatusFromOpenId4VcIssuanceState(state: OpenId4VcIssuanceSessionState): IssueStatus { - if (state === OpenId4VcIssuanceSessionState.OfferCreated) return IssueStatus.OFFER_CREATED - if (state === OpenId4VcIssuanceSessionState.OfferUriRetrieved) return IssueStatus.OFFER_URI_RETRIEVED - if (state === OpenId4VcIssuanceSessionState.AccessTokenRequested) return IssueStatus.ACCESS_TOKEN_REQUESTED - if (state === OpenId4VcIssuanceSessionState.AccessTokenCreated) return IssueStatus.ACCESS_TOKEN_CREATED - if (state === OpenId4VcIssuanceSessionState.CredentialRequestReceived) return IssueStatus.CREDENTIAL_REQUEST_RECEIVED - // sphereon does not have a completed state indicating that all credentials have been issued - if (state === OpenId4VcIssuanceSessionState.CredentialsPartiallyIssued) return IssueStatus.CREDENTIAL_ISSUED - if (state === OpenId4VcIssuanceSessionState.Completed) return IssueStatus.CREDENTIAL_ISSUED - if (state === OpenId4VcIssuanceSessionState.Error) return IssueStatus.ERROR - - throw new CredoError(`Unknown state value: ${state}`) -} diff --git a/packages/openid4vc/src/openid4vc-issuer/repository/OpenId4VcCredentialOfferUriStateManager.ts b/packages/openid4vc/src/openid4vc-issuer/repository/OpenId4VcCredentialOfferUriStateManager.ts deleted file mode 100644 index 33b53641bf..0000000000 --- a/packages/openid4vc/src/openid4vc-issuer/repository/OpenId4VcCredentialOfferUriStateManager.ts +++ /dev/null @@ -1,82 +0,0 @@ -import type { AgentContext } from '@credo-ts/core' -import type { IStateManager, URIState } from '@sphereon/oid4vci-common' - -import { CredoError } from '@credo-ts/core' - -import { OpenId4VcIssuanceSessionRepository } from './OpenId4VcIssuanceSessionRepository' - -export class OpenId4VcCredentialOfferUriStateManager implements IStateManager { - private openId4VcIssuanceSessionRepository: OpenId4VcIssuanceSessionRepository - - public constructor(private agentContext: AgentContext, private issuerId: string) { - this.openId4VcIssuanceSessionRepository = agentContext.dependencyManager.resolve(OpenId4VcIssuanceSessionRepository) - } - - public async set(uri: string, stateValue: URIState): Promise { - // Just to make sure that the uri is the same as the id as that's what we use to query - if (uri !== stateValue.uri) { - throw new CredoError('Expected the uri of the uri state to be equal to the id') - } - - // NOTE: we're currently not ding anything here, as we store the uri in the record - // when the credential offer session is stored. - } - - public async get(uri: string): Promise { - const record = await this.openId4VcIssuanceSessionRepository.findSingleByQuery(this.agentContext, { - issuerId: this.issuerId, - credentialOfferUri: uri, - }) - - if (!record) return undefined - - return { - preAuthorizedCode: record.preAuthorizedCode, - uri: record.credentialOfferUri, - createdAt: record.createdAt.getTime(), - } - } - - public async has(uri: string): Promise { - const record = await this.openId4VcIssuanceSessionRepository.findSingleByQuery(this.agentContext, { - issuerId: this.issuerId, - credentialOfferUri: uri, - }) - - return record !== undefined - } - - public async delete(): Promise { - // NOTE: we're not doing anything here as the uri is stored in the credential offer session - // Not sure how to best handle this, but for now we just don't delete it - return false - } - - public async clearExpired(): Promise { - // FIXME: we should have a way to remove expired records - // or just not return the value in the get if the record is expired - throw new Error('Method not implemented.') - } - - public async clearAll(): Promise { - throw new Error('Method not implemented.') - } - - public async getAsserted(id: string): Promise { - const state = await this.get(id) - - if (!state) { - throw new CredoError(`No uri state found for id ${id}`) - } - - return state - } - - public async startCleanupRoutine(): Promise { - throw new Error('Method not implemented.') - } - - public async stopCleanupRoutine(): Promise { - throw new Error('Method not implemented.') - } -} diff --git a/packages/openid4vc/src/openid4vc-issuer/repository/OpenId4VcIssuanceSessionRecord.ts b/packages/openid4vc/src/openid4vc-issuer/repository/OpenId4VcIssuanceSessionRecord.ts index 3de3af4313..fa20b3c2a1 100644 --- a/packages/openid4vc/src/openid4vc-issuer/repository/OpenId4VcIssuanceSessionRecord.ts +++ b/packages/openid4vc/src/openid4vc-issuer/repository/OpenId4VcIssuanceSessionRecord.ts @@ -1,19 +1,77 @@ import type { OpenId4VciCredentialOfferPayload } from '../../shared' import type { RecordTags, TagsBase } from '@credo-ts/core' -import { CredoError, BaseRecord, utils, DateTransformer } from '@credo-ts/core' -import { Transform } from 'class-transformer' +import { PkceCodeChallengeMethod } from '@animo-id/oauth2' +import { CredoError, BaseRecord, utils, isJsonObject } from '@credo-ts/core' +import { Transform, TransformationType } from 'class-transformer' import { OpenId4VcIssuanceSessionState } from '../OpenId4VcIssuanceSessionState' export type OpenId4VcIssuanceSessionRecordTags = RecordTags +export interface OpenId4VcIssuanceSessionAuthorization { + code?: string + + /** + * @todo: I saw in google's library that for codes they encrypt an id with expiration time. + * You now the code was created by you because you can decrypt it, and you don't have to store + * additional metadata on your server. It's similar to the signed / encrypted nonce + */ + codeExpiresAt?: Date + + /** + * String value created by the Credential Issuer and opaque to the Wallet that + * is used to bind the subsequent Authorization Request with the Credential Issuer to a context set up during previous steps. + */ + issuerState?: string + + /** + * Scopes that are granted when the authorization is complete. + */ + scopes?: string[] + + /** + * Subject the issuance session is bound to. For internal authorization this will be defined + * from the moment the token is issued. For external authorization this will be defined after + * the first time the credential endpoint has been called. + */ + subject?: string +} + +export interface OpenId4VcIssuanceSessionPresentation { + /** + * Whether presentation during issuance is required. + */ + required: true + + /** + * Auth session for the presentation during issuance flow + */ + authSession?: string + + /** + * The id of the `OpenId4VcVerificationSessionRecord` record this issuance session is linked to + */ + openId4VcVerificationSessionId?: string +} + export type DefaultOpenId4VcIssuanceSessionRecordTags = { issuerId: string cNonce?: string - preAuthorizedCode?: string state: OpenId4VcIssuanceSessionState - credentialOfferUri: string + credentialOfferUri?: string + + // pre-auth flow + preAuthorizedCode?: string + + // auth flow + authorizationCode?: string + issuerState?: string + + authorizationSubject?: string + + // presentation during issuance + presentationAuthSession?: string } export interface OpenId4VcIssuanceSessionRecordProps { @@ -21,19 +79,40 @@ export interface OpenId4VcIssuanceSessionRecordProps { createdAt?: Date tags?: TagsBase + state: OpenId4VcIssuanceSessionState issuerId: string - cNonce?: string - cNonceExpiresAt?: Date + /** + * Client id will mostly be used when doing auth flow + */ + clientId?: string + // Pre auth flow preAuthorizedCode?: string userPin?: string - credentialOfferUri: string + // Auth flow (move to authorization?) + pkce?: { + codeChallengeMethod: PkceCodeChallengeMethod + codeChallenge: string + } + + /** + * When authorization code flow is used, this links the authorization + */ + authorization?: OpenId4VcIssuanceSessionAuthorization + + /** + * When presentation during issuance is required this should link the + * `OpenId4VcVerificationSessionRecord` and state + */ + presentation?: OpenId4VcIssuanceSessionPresentation + + credentialOfferUri?: string + credentialOfferPayload: OpenId4VciCredentialOfferPayload issuanceMetadata?: Record - state: OpenId4VcIssuanceSessionState errorMessage?: string } @@ -65,26 +144,60 @@ export class OpenId4VcIssuanceSessionRecord extends BaseRecord { + if (type === TransformationType.PLAIN_TO_CLASS && isJsonObject(value) && typeof value.codeExpiresAt === 'string') { + return { + ...value, + codeExpiresAt: new Date(value.codeExpiresAt), + } + } + if (type === TransformationType.CLASS_TO_CLASS && isJsonObject(value) && value.codeExpiresAt instanceof Date) { + return { + ...value, + codeExpiresAt: new Date(value.codeExpiresAt.getTime()), + } + } + if (type === TransformationType.CLASS_TO_PLAIN && isJsonObject(value) && value.codeExpiresAt instanceof Date) { + return { + ...value, + codeExpiresAt: value.codeExpiresAt.toISOString(), + } + } + + return value + }) + public authorization?: OpenId4VcIssuanceSessionAuthorization + + /** + * Presentation during issuance specific metadata values + */ + public presentation?: OpenId4VcIssuanceSessionPresentation /** * User-defined metadata that will be provided to the credential request to credential mapper @@ -102,7 +215,7 @@ export class OpenId4VcIssuanceSessionRecord extends BaseRecord @@ -15,16 +16,6 @@ export type DefaultOpenId4VcIssuerRecordTags = { issuerId: string } -export interface OpenId4VcIssuerRecordCredentialSupportedProps { - credentialsSupported: OpenId4VciCredentialSupportedWithId[] - credentialConfigurationsSupported?: never -} - -export interface OpenId4VcIssuerRecordCredentialConfigurationsSupportedProps { - credentialsSupported?: never - credentialConfigurationsSupported: OpenId4VciCredentialConfigurationsSupported -} - export type OpenId4VcIssuerRecordProps = { id?: string createdAt?: Date @@ -44,11 +35,19 @@ export type OpenId4VcIssuerRecordProps = { */ dpopSigningAlgValuesSupported?: [JwaSignatureAlgorithm, ...JwaSignatureAlgorithm[]] - display?: OpenId4VciIssuerMetadataDisplay[] -} & (OpenId4VcIssuerRecordCredentialSupportedProps | OpenId4VcIssuerRecordCredentialConfigurationsSupportedProps) + display?: OpenId4VciCredentialIssuerMetadataDisplay[] + authorizationServerConfigs?: OpenId4VciAuthorizationServerConfig[] + + credentialConfigurationsSupported: OpenId4VciCredentialConfigurationsSupportedWithFormats + + /** + * Indicate support for batch issuane of credentials + */ + batchCredentialIssuance?: OpenId4VciBatchCredentialIssuanceOptions +} /** - * For OID4VC you need to expos metadata files. Each issuer needs to host this metadata. This is not the case for DIDComm where we can just have one /didcomm endpoint. + * For OID4VC you need to expose metadata files. Each issuer needs to host this metadata. This is not the case for DIDComm where we can just have one /didcomm endpoint. * So we create a record per openid issuer/verifier that you want, and each tenant can create multiple issuers/verifiers which have different endpoints * and metadata files * */ @@ -59,10 +58,45 @@ export class OpenId4VcIssuerRecord extends BaseRecord) { + if (this.credentialConfigurationsSupported) return + + this.credentialConfigurationsSupported = + // eslint-disable-next-line @typescript-eslint/no-explicit-any + credentialsSupportedToCredentialConfigurationsSupported(credentialsSupported as any) as any + } + + public credentialConfigurationsSupported!: OpenId4VciCredentialConfigurationsSupportedWithFormats + + // Draft 11 to draft 13+ syntax + @Transform(({ type, value }) => { + if (type === TransformationType.PLAIN_TO_CLASS && Array.isArray(value)) { + return value.map((display) => { + if (display.logo?.uri) return display + + const { url, ...logoRest } = display.logo ?? {} + return { + ...display, + logo: url + ? { + ...logoRest, + uri: url, + } + : undefined, + } + }) + } + + return value + }) + public display?: OpenId4VciCredentialIssuerMetadataDisplay[] + public authorizationServerConfigs?: OpenId4VciAuthorizationServerConfig[] public dpopSigningAlgValuesSupported?: [JwaSignatureAlgorithm, ...JwaSignatureAlgorithm[]] + public batchCredentialIssuance?: OpenId4VciBatchCredentialIssuanceOptions public constructor(props: OpenId4VcIssuerRecordProps) { super() @@ -74,11 +108,11 @@ export class OpenId4VcIssuerRecord extends BaseRecord { + test('should correctly transform credentials supported to credential configurations supported', () => { + const instance = JsonTransformer.fromJSON( + { + credentialsSupported: [ + { + id: 'd2662472-891c-413d-b3c6-e2f0109001c5', + format: 'ldp_vc', + '@context': [], + types: ['VerifiableCredential', 'OpenBadgeCredential'], + cryptographic_binding_methods_supported: ['did:key'], + cryptographic_suites_supported: ['Ed25519Signature2018'], + display: [ + { + name: 'Example University Degree', + description: 'JFF Plugfest 3 OpenBadge Credential', + background_color: '#464c49', + logo: {}, + }, + ], + }, + { + id: '613ecbbb-0a4c-4041-bb78-c64943139d5f', + format: 'jwt_vc_json', + types: ['VerifiableCredential', 'OpenBadgeCredential'], + cryptographic_binding_methods_supported: ['did:key'], + cryptographic_suites_supported: ['EdDSA'], + display: [ + { + name: 'Example University Degree', + description: 'JFF Plugfest 3 OpenBadge Credential', + background_color: '#464c49', + logo: {}, + }, + ], + }, + { + id: '904afaa1-f319-4a12-9c3c-0a6081c3feb0', + format: 'mso_mdoc', + doctype: 'some-doc-type', + cryptographic_binding_methods_supported: ['did:key'], + cryptographic_suites_supported: ['EdDSA'], + display: [ + { + name: 'Passport', + description: 'Passport of the Kingdom of Kākāpō', + background_color: '#171717', + logo: {}, + }, + ], + }, + { + id: 'c3db5513-ae2b-46e9-8a0d-fbfd0ce52b6a', + format: 'vc+sd-jwt', + vct: 'something', + cryptographic_binding_methods_supported: ['did:key'], + cryptographic_suites_supported: ['EdDSA'], + display: [ + { + name: 'Passport', + description: 'Passport of the Kingdom of Kākāpō', + background_color: '#171717', + logo: { url: 'https://static.mattr.global/credential-assets/government-of-kakapo/web/logo.svg' }, + }, + ], + }, + ], + }, + OpenId4VcIssuerRecord + ) + + expect(instance.credentialConfigurationsSupported).toEqual({ + 'd2662472-891c-413d-b3c6-e2f0109001c5': { + format: 'ldp_vc', + credential_definition: { + '@context': [], + type: ['VerifiableCredential', 'OpenBadgeCredential'], + }, + cryptographic_binding_methods_supported: ['did:key'], + credential_signing_alg_values_supported: ['Ed25519Signature2018'], + display: [ + { + name: 'Example University Degree', + description: 'JFF Plugfest 3 OpenBadge Credential', + background_color: '#464c49', + }, + ], + }, + '613ecbbb-0a4c-4041-bb78-c64943139d5f': { + format: 'jwt_vc_json', + credential_definition: { + type: ['VerifiableCredential', 'OpenBadgeCredential'], + }, + cryptographic_binding_methods_supported: ['did:key'], + credential_signing_alg_values_supported: ['EdDSA'], + display: [ + { + name: 'Example University Degree', + description: 'JFF Plugfest 3 OpenBadge Credential', + background_color: '#464c49', + }, + ], + }, + '904afaa1-f319-4a12-9c3c-0a6081c3feb0': { + format: 'mso_mdoc', + doctype: 'some-doc-type', + cryptographic_binding_methods_supported: ['did:key'], + credential_signing_alg_values_supported: ['EdDSA'], + display: [ + { + name: 'Passport', + description: 'Passport of the Kingdom of Kākāpō', + background_color: '#171717', + }, + ], + }, + 'c3db5513-ae2b-46e9-8a0d-fbfd0ce52b6a': { + format: 'vc+sd-jwt', + vct: 'something', + cryptographic_binding_methods_supported: ['did:key'], + credential_signing_alg_values_supported: ['EdDSA'], + display: [ + { + name: 'Passport', + description: 'Passport of the Kingdom of Kākāpō', + background_color: '#171717', + logo: { uri: 'https://static.mattr.global/credential-assets/government-of-kakapo/web/logo.svg' }, + }, + ], + }, + }) + }) + + test('does not transform credentials supported to credential configurations supported if already present', () => { + const instance = JsonTransformer.fromJSON( + { + credentialsSupported: [ + { + id: 'd2662472-891c-413d-b3c6-e2f0109001c5', + format: 'ldp_vc', + '@context': [], + types: ['VerifiableCredential', 'OpenBadgeCredential'], + cryptographic_binding_methods_supported: ['did:key'], + cryptographic_suites_supported: ['Ed25519Signature2018'], + display: [ + { + name: 'Example University Degree', + description: 'JFF Plugfest 3 OpenBadge Credential', + background_color: '#464c49', + logo: {}, + }, + ], + }, + ], + credentialConfigurationsSupported: {}, + }, + OpenId4VcIssuerRecord + ) + + expect(instance.credentialConfigurationsSupported).toEqual({}) + }) + + test('should correctly transform display metadata', () => { + const instance = JsonTransformer.fromJSON( + { + display: [ + { + name: 'hello', + logo: { + url: 'https://something.com', + }, + }, + ], + }, + OpenId4VcIssuerRecord + ) + + expect(instance.display).toEqual([ + { + name: 'hello', + logo: { + uri: 'https://something.com', + }, + }, + ]) + }) + + test('removes logo if empty objct in display metadata', () => { + const instance = JsonTransformer.fromJSON( + { + display: [ + { + name: 'hello', + logo: {}, + }, + ], + }, + OpenId4VcIssuerRecord + ) + + expect(instance.display).toEqual([ + { + name: 'hello', + }, + ]) + }) + + test('does not transfrom display metadata if already using uri', () => { + const instance = JsonTransformer.fromJSON( + { + display: [ + { + name: 'hello', + logo: { + uri: 'uri', + }, + }, + ], + }, + OpenId4VcIssuerRecord + ) + + expect(instance.display).toEqual([ + { + name: 'hello', + logo: { + uri: 'uri', + }, + }, + ]) + }) +}) diff --git a/packages/openid4vc/src/openid4vc-issuer/router/accessTokenEndpoint.ts b/packages/openid4vc/src/openid4vc-issuer/router/accessTokenEndpoint.ts index 762ecb5cf3..5148d1a830 100644 --- a/packages/openid4vc/src/openid4vc-issuer/router/accessTokenEndpoint.ts +++ b/packages/openid4vc/src/openid4vc-issuer/router/accessTokenEndpoint.ts @@ -1,224 +1,215 @@ import type { OpenId4VcIssuanceRequest } from './requestContext' -import type { AgentContext } from '@credo-ts/core' -import type { JWK, SigningAlgo } from '@sphereon/oid4vc-common' -import type { AccessTokenRequest, JWTSignerCallback } from '@sphereon/oid4vci-common' +import type { OpenId4VcIssuerModuleConfig } from '../OpenId4VcIssuerModuleConfig' +import type { HttpMethod, VerifyAccessTokenRequestReturn } from '@animo-id/oauth2' import type { NextFunction, Response, Router } from 'express' import { - getJwkFromKey, - CredoError, - JwsService, - JwtPayload, - getJwkClassFromKeyType, - Key, - joinUriParts, -} from '@credo-ts/core' -import { verifyDPoP } from '@sphereon/oid4vc-common' + authorizationCodeGrantIdentifier, + Oauth2ErrorCodes, + Oauth2ServerErrorResponseError, + preAuthorizedCodeGrantIdentifier, +} from '@animo-id/oauth2' +import { getJwkFromKey, joinUriParts, Key, utils } from '@credo-ts/core' + import { - GrantTypes, - IssueStatus, - PRE_AUTHORIZED_CODE_REQUIRED_ERROR, - PRE_AUTH_CODE_LITERAL, - TokenError, - TokenErrorResponse, -} from '@sphereon/oid4vci-common' -import { assertValidAccessTokenRequest, createAccessTokenResponse } from '@sphereon/oid4vci-issuer' - -import { getRequestContext, sendErrorResponse } from '../../shared/router' -import { getVerifyJwtCallback } from '../../shared/utils' -import { OpenId4VcIssuerModuleConfig } from '../OpenId4VcIssuerModuleConfig' + getRequestContext, + sendJsonResponse, + sendOauth2ErrorResponse, + sendUnknownServerErrorResponse, +} from '../../shared/router' +import { addSecondsToDate } from '../../shared/utils' +import { OpenId4VcIssuanceSessionState } from '../OpenId4VcIssuanceSessionState' import { OpenId4VcIssuerService } from '../OpenId4VcIssuerService' -import { OpenId4VcCNonceStateManager } from '../repository/OpenId4VcCNonceStateManager' -import { OpenId4VcCredentialOfferSessionStateManager } from '../repository/OpenId4VcCredentialOfferSessionStateManager' - -export interface OpenId4VciAccessTokenEndpointConfig { - /** - * The path at which the token endpoint should be made available. Note that it will be - * hosted at a subpath to take into account multiple tenants and issuers. - * - * @default /token - */ - endpointPath: string - - /** - * The maximum amount of time in seconds that the pre-authorized code is valid. - * @default 360 (5 minutes) - */ - preAuthorizedCodeExpirationInSeconds: number - - /** - * The time after which the cNonce from the access token response will - * expire. - * - * @default 360 (5 minutes) - */ - cNonceExpiresInSeconds: number - - /** - * The time after which the token will expire. - * - * @default 360 (5 minutes) - */ - tokenExpiresInSeconds: number -} - -export function configureAccessTokenEndpoint(router: Router, config: OpenId4VciAccessTokenEndpointConfig) { - router.post( - config.endpointPath, - verifyTokenRequest({ preAuthorizedCodeExpirationInSeconds: config.preAuthorizedCodeExpirationInSeconds }), - handleTokenRequest(config) - ) -} - -function getJwtSignerCallback( - agentContext: AgentContext, - signerPublicKey: Key, - config: OpenId4VciAccessTokenEndpointConfig -): JWTSignerCallback { - return async (jwt, _kid) => { - if (_kid) { - throw new CredoError('Kid should not be supplied externally.') - } - if (jwt.header.kid || jwt.header.jwk) { - throw new CredoError('kid or jwk should not be present in access token header before signing') - } - - const jwsService = agentContext.dependencyManager.resolve(JwsService) - - const alg = getJwkClassFromKeyType(signerPublicKey.keyType)?.supportedSignatureAlgorithms[0] - if (!alg) { - throw new CredoError(`No supported signature algorithms for key type: ${signerPublicKey.keyType}`) - } +import { OpenId4VcIssuanceSessionRepository } from '../repository' - // FIXME: the iat and exp implementation in OID4VCI is incorrect so we override the values here - // https://github.com/Sphereon-Opensource/OID4VCI/pull/99 - // https://github.com/Sphereon-Opensource/OID4VCI/pull/101 - const iat = Math.floor(new Date().getTime() / 1000) - jwt.payload.iat = iat - jwt.payload.exp = iat + config.tokenExpiresInSeconds - - const jwk = getJwkFromKey(signerPublicKey) - const signedJwt = await jwsService.createJwsCompact(agentContext, { - protectedHeaderOptions: { ...jwt.header, jwk, alg }, - payload: JwtPayload.fromJson(jwt.payload), - key: signerPublicKey, - }) - - return signedJwt - } +export function configureAccessTokenEndpoint(router: Router, config: OpenId4VcIssuerModuleConfig) { + router.post(config.accessTokenEndpointPath, handleTokenRequest(config)) } -export function handleTokenRequest(config: OpenId4VciAccessTokenEndpointConfig) { - const { tokenExpiresInSeconds, cNonceExpiresInSeconds } = config - +export function handleTokenRequest(config: OpenId4VcIssuerModuleConfig) { return async (request: OpenId4VcIssuanceRequest, response: Response, next: NextFunction) => { response.set({ 'Cache-Control': 'no-store', Pragma: 'no-cache' }) - const requestContext = getRequestContext(request) const { agentContext, issuer } = requestContext - const body = request.body as AccessTokenRequest - if (body.grant_type !== GrantTypes.PRE_AUTHORIZED_CODE) { - return sendErrorResponse( - response, - agentContext.config.logger, - 400, - TokenErrorResponse.invalid_request, - PRE_AUTHORIZED_CODE_REQUIRED_ERROR - ) - } - const openId4VcIssuerService = agentContext.dependencyManager.resolve(OpenId4VcIssuerService) - const issuerMetadata = openId4VcIssuerService.getIssuerMetadata(agentContext, issuer) + const issuanceSessionRepository = agentContext.dependencyManager.resolve(OpenId4VcIssuanceSessionRepository) + const issuerMetadata = await openId4VcIssuerService.getIssuerMetadata(agentContext, issuer) const accessTokenSigningKey = Key.fromFingerprint(issuer.accessTokenPublicKeyFingerprint) + const oauth2AuthorizationServer = openId4VcIssuerService.getOauth2AuthorizationServer(agentContext) + + const fullRequestUrl = joinUriParts(issuerMetadata.credentialIssuer.credential_issuer, [ + config.accessTokenEndpointPath, + ]) + const requestLike = { + headers: new Headers(request.headers as Record), + method: request.method as HttpMethod, + url: fullRequestUrl, + } as const + + const { accessTokenRequest, grant, dpopJwt, pkceCodeVerifier } = oauth2AuthorizationServer.parseAccessTokenRequest({ + accessTokenRequest: request.body, + request: requestLike, + }) - let dpopJwk: JWK | undefined - if (request.headers.dpop) { - try { - const issuerConfig = agentContext.dependencyManager.resolve(OpenId4VcIssuerModuleConfig) - - const fullUrl = joinUriParts(issuerConfig.baseUrl, [requestContext.issuer.issuerId, request.url]) - dpopJwk = await verifyDPoP( - { method: request.method, headers: request.headers, fullUrl }, - { - jwtVerifyCallback: getVerifyJwtCallback(agentContext), - expectAccessToken: false, - maxIatAgeInSeconds: undefined, - acceptedAlgorithms: issuerMetadata.dpopSigningAlgValuesSupported as SigningAlgo[] | undefined, - } - ) - } catch (error) { - return sendErrorResponse( - response, - agentContext.config.logger, - 400, - TokenErrorResponse.invalid_dpop_proof, - error instanceof Error ? error.message : 'Unknown error' - ) - } + const issuanceSession = await issuanceSessionRepository.findSingleByQuery(agentContext, { + preAuthorizedCode: grant.grantType === preAuthorizedCodeGrantIdentifier ? grant.preAuthorizedCode : undefined, + authorizationCode: grant.grantType === authorizationCodeGrantIdentifier ? grant.code : undefined, + }) + const allowedStates = + grant.grantType === preAuthorizedCodeGrantIdentifier + ? [OpenId4VcIssuanceSessionState.OfferCreated, OpenId4VcIssuanceSessionState.OfferUriRetrieved] + : [OpenId4VcIssuanceSessionState.AuthorizationGranted] + if (!issuanceSession || !allowedStates.includes(issuanceSession.state)) { + throw new Oauth2ServerErrorResponseError({ + error: Oauth2ErrorCodes.InvalidGrant, + error_description: 'Invalid authorization code', + }) } - try { - const accessTokenResponse = await createAccessTokenResponse(request.body, { - credentialOfferSessions: new OpenId4VcCredentialOfferSessionStateManager(agentContext, issuer.issuerId), - tokenExpiresIn: tokenExpiresInSeconds, - accessTokenIssuer: issuerMetadata.issuerUrl, - cNonce: await agentContext.wallet.generateNonce(), - cNonceExpiresIn: cNonceExpiresInSeconds, - cNonces: new OpenId4VcCNonceStateManager(agentContext, issuer.issuerId), - accessTokenSignerCallback: getJwtSignerCallback(agentContext, accessTokenSigningKey, config), - dPoPJwk: dpopJwk, + if ( + Date.now() > + addSecondsToDate(issuanceSession.createdAt, config.statefullCredentialOfferExpirationInSeconds).getTime() + ) { + issuanceSession.errorMessage = 'Credential offer has expired' + await openId4VcIssuerService.updateState(agentContext, issuanceSession, OpenId4VcIssuanceSessionState.Error) + throw new Oauth2ServerErrorResponseError({ + // What is the best error here? + error: Oauth2ErrorCodes.InvalidGrant, + error_description: 'Session expired', }) - response.status(200).json(accessTokenResponse) - } catch (error) { - sendErrorResponse(response, agentContext.config.logger, 400, TokenErrorResponse.invalid_request, error) } - // NOTE: if we don't call next, the agentContext session handler will NOT be called - next() - } -} - -export function verifyTokenRequest(options: { preAuthorizedCodeExpirationInSeconds: number }) { - return async (request: OpenId4VcIssuanceRequest, response: Response, next: NextFunction) => { - const { agentContext, issuer } = getRequestContext(request) - + let verificationResult: VerifyAccessTokenRequestReturn try { - const credentialOfferSessions = new OpenId4VcCredentialOfferSessionStateManager(agentContext, issuer.issuerId) - const credentialOfferSession = await credentialOfferSessions.getAsserted(request.body[PRE_AUTH_CODE_LITERAL]) - if (![IssueStatus.OFFER_CREATED, IssueStatus.OFFER_URI_RETRIEVED].includes(credentialOfferSession.status)) { - throw new TokenError(400, TokenErrorResponse.invalid_request, 'Access token has already been retrieved') + if (grant.grantType === preAuthorizedCodeGrantIdentifier) { + if (!issuanceSession.preAuthorizedCode) { + throw new Oauth2ServerErrorResponseError( + { + error: Oauth2ErrorCodes.InvalidGrant, + error_description: 'Invalid authorization code', + }, + { + internalMessage: + 'Found issuance session without preAuthorizedCode. This should not happen as the issuance session is fetched based on the pre authorized code', + } + ) + } + + verificationResult = await oauth2AuthorizationServer.verifyPreAuthorizedCodeAccessTokenRequest({ + accessTokenRequest, + expectedPreAuthorizedCode: issuanceSession.preAuthorizedCode, + grant, + request: requestLike, + dpop: { + jwt: dpopJwt, + // This will only have effect when DPoP is not present. + // If it is present it will always be verified + required: config.dpopRequired, + }, + expectedTxCode: issuanceSession.userPin, + preAuthorizedCodeExpiresAt: addSecondsToDate( + issuanceSession.createdAt, + config.statefullCredentialOfferExpirationInSeconds + ), + }) + } else if (grant.grantType === authorizationCodeGrantIdentifier) { + if (!issuanceSession.authorization?.code || !issuanceSession.authorization?.codeExpiresAt) { + throw new Oauth2ServerErrorResponseError( + { + error: Oauth2ErrorCodes.InvalidGrant, + error_description: 'Invalid authorization code', + }, + { + internalMessage: + 'Found issuance session without authorization.code or authorization.codeExpiresAt. This should not happen as the issuance session is fetched based on the authorization code', + } + ) + } + verificationResult = await oauth2AuthorizationServer.verifyAuthorizationCodeAccessTokenRequest({ + accessTokenRequest, + expectedCode: issuanceSession.authorization.code, + codeExpiresAt: issuanceSession.authorization.codeExpiresAt, + grant, + request: requestLike, + dpop: { + jwt: dpopJwt, + // This will only have effect when DPoP is not present. + // If it is present it will always be verified + required: config.dpopRequired, + }, + pkce: issuanceSession.pkce + ? { + codeChallenge: issuanceSession.pkce.codeChallenge, + codeChallengeMethod: issuanceSession.pkce.codeChallengeMethod, + codeVerifier: pkceCodeVerifier, + } + : undefined, + }) + } else { + throw new Oauth2ServerErrorResponseError({ + error: Oauth2ErrorCodes.UnsupportedGrantType, + error_description: 'Unsupported grant type', + }) } - const { preAuthSession } = await assertValidAccessTokenRequest(request.body, { - // It should actually be in seconds. but the oid4vci library has some bugs related - // to seconds vs milliseconds. We pass it as ms for now, but once the fix is released - // we should pass it as seconds. We have an extra check below, so that we won't have - // an security issue once the fix is released. - // FIXME: https://github.com/Sphereon-Opensource/OID4VCI/pull/104 - expirationDuration: options.preAuthorizedCodeExpirationInSeconds * 1000, - credentialOfferSessions, + + await openId4VcIssuerService.updateState( + agentContext, + issuanceSession, + OpenId4VcIssuanceSessionState.AccessTokenRequested + ) + const { cNonce, cNonceExpiresInSeconds } = await openId4VcIssuerService.createNonce(agentContext, issuer) + + // for authorization code flow we take the authorization scopes. For pre-auth we don't use scopes (we just + // use the offered credential configuration ids so a scope is not required) + const scopes = + grant.grantType === authorizationCodeGrantIdentifier ? issuanceSession.authorization?.scopes : undefined + const subject = `credo:${utils.uuid()}` + + const signerJwk = getJwkFromKey(accessTokenSigningKey) + const accessTokenResponse = await oauth2AuthorizationServer.createAccessTokenResponse({ + audience: issuerMetadata.credentialIssuer.credential_issuer, + authorizationServer: issuerMetadata.credentialIssuer.credential_issuer, + expiresInSeconds: config.accessTokenExpiresInSeconds, + signer: { + method: 'jwk', + alg: signerJwk.supportedSignatureAlgorithms[0], + publicJwk: signerJwk.toJson(), + }, + dpopJwk: verificationResult.dpopJwk, + scope: scopes?.join(' '), + clientId: issuanceSession.clientId, + + additionalAccessTokenPayload: { + 'pre-authorized_code': + grant.grantType === preAuthorizedCodeGrantIdentifier ? grant.preAuthorizedCode : undefined, + issuer_state: issuanceSession.authorization?.issuerState, + }, + // We generate a random subject for each access token and bind the issuance session to this. + subject, + + // NOTE: these have been removed in newer drafts. Keeping them in for now + cNonce, + cNonceExpiresIn: cNonceExpiresInSeconds, }) - // TODO: remove once above PR is merged and released - const expiresAt = preAuthSession.createdAt + options.preAuthorizedCodeExpirationInSeconds * 1000 - if (Date.now() > expiresAt) { - throw new TokenError(400, TokenErrorResponse.invalid_grant, 'Pre-authorized code has expired') + issuanceSession.authorization = { + ...issuanceSession.authorization, + subject, } + await openId4VcIssuerService.updateState( + agentContext, + issuanceSession, + OpenId4VcIssuanceSessionState.AccessTokenCreated + ) + + return sendJsonResponse(response, next, accessTokenResponse) } catch (error) { - if (error instanceof TokenError) { - sendErrorResponse( - response, - agentContext.config.logger, - error.statusCode, - error.responseError, - error.getDescription() - ) - } else { - sendErrorResponse(response, agentContext.config.logger, 400, TokenErrorResponse.invalid_request, error) + if (error instanceof Oauth2ServerErrorResponseError) { + return sendOauth2ErrorResponse(response, next, agentContext.config.logger, error) } - } - // NOTE: if we don't call next, the agentContext session handler will NOT be called - next() + return sendUnknownServerErrorResponse(response, next, agentContext.config.logger, error) + } } } diff --git a/packages/openid4vc/src/openid4vc-issuer/router/authorizationChallengeEndpoint.ts b/packages/openid4vc/src/openid4vc-issuer/router/authorizationChallengeEndpoint.ts new file mode 100644 index 0000000000..544b2049ce --- /dev/null +++ b/packages/openid4vc/src/openid4vc-issuer/router/authorizationChallengeEndpoint.ts @@ -0,0 +1,333 @@ +import type { OpenId4VcIssuanceRequest } from './requestContext' +import type { OpenId4VciCredentialConfigurationsSupportedWithFormats } from '../../shared' +import type { OpenId4VcIssuerRecord } from '../repository' +import type { AuthorizationChallengeRequest } from '@animo-id/oauth2' +import type { AgentContext } from '@credo-ts/core' +import type { NextFunction, Response, Router } from 'express' + +import { Oauth2ErrorCodes, Oauth2ServerErrorResponseError } from '@animo-id/oauth2' +import { TypedArrayEncoder } from '@credo-ts/core' + +import { + OpenId4VcVerificationSessionRepository, + OpenId4VcVerificationSessionState, + OpenId4VcVerifierApi, +} from '../../openid4vc-verifier' +import { + getAllowedAndRequestedScopeValues, + getCredentialConfigurationsSupportedForScopes, + getOfferedCredentials, + getScopesFromCredentialConfigurationsSupported, +} from '../../shared' +import { + getRequestContext, + sendJsonResponse, + sendOauth2ErrorResponse, + sendUnknownServerErrorResponse, +} from '../../shared/router' +import { addSecondsToDate } from '../../shared/utils' +import { OpenId4VcIssuanceSessionState } from '../OpenId4VcIssuanceSessionState' +import { OpenId4VcIssuerModuleConfig } from '../OpenId4VcIssuerModuleConfig' +import { OpenId4VcIssuerService } from '../OpenId4VcIssuerService' + +export function configureAuthorizationChallengeEndpoint(router: Router, config: OpenId4VcIssuerModuleConfig) { + router.post( + config.authorizationChallengeEndpointPath, + async (request: OpenId4VcIssuanceRequest, response: Response, next: NextFunction) => { + const requestContext = getRequestContext(request) + const { agentContext, issuer } = requestContext + + try { + const openId4VcIssuerService = agentContext.dependencyManager.resolve(OpenId4VcIssuerService) + const authorizationServer = openId4VcIssuerService.getOauth2AuthorizationServer(agentContext) + + const { authorizationChallengeRequest } = authorizationServer.parseAuthorizationChallengeRequest({ + authorizationChallengeRequest: request.body, + }) + + if (authorizationChallengeRequest.auth_session) { + await handleAuthorizationChallengeWithAuthSession({ + response, + next, + authorizationChallengeRequest: { + // For type inference + ...authorizationChallengeRequest, + auth_session: authorizationChallengeRequest.auth_session, + }, + agentContext, + issuer, + }) + } else { + // First call, no auth_sesion yet + await handleAuthorizationChallengeNoAuthSession({ + authorizationChallengeRequest, + agentContext, + issuer, + }) + } + } catch (error) { + if (error instanceof Oauth2ServerErrorResponseError) { + return sendOauth2ErrorResponse(response, next, agentContext.config.logger, error) + } + return sendUnknownServerErrorResponse(response, next, agentContext.config.logger, error) + } + } + ) +} + +async function handleAuthorizationChallengeNoAuthSession(options: { + agentContext: AgentContext + issuer: OpenId4VcIssuerRecord + authorizationChallengeRequest: AuthorizationChallengeRequest +}) { + const { agentContext, issuer, authorizationChallengeRequest } = options + + // First call, no auth_sesion yet + + const openId4VcIssuerService = agentContext.dependencyManager.resolve(OpenId4VcIssuerService) + const config = agentContext.dependencyManager.resolve(OpenId4VcIssuerModuleConfig) + const issuerMetadata = await openId4VcIssuerService.getIssuerMetadata(agentContext, issuer) + const authorizationServer = openId4VcIssuerService.getOauth2AuthorizationServer(agentContext) + + if (!config.getVerificationSessionForIssuanceSessionAuthorization) { + throw new Oauth2ServerErrorResponseError( + { + error: Oauth2ErrorCodes.ServerError, + }, + { + internalMessage: `Missing required 'getVerificationSessionForIssuanceSessionAuthorization' callback in openid4vc issuer module config. This callback is required for presentation during issuance flows.`, + } + ) + } + + if (!authorizationChallengeRequest.scope) { + throw new Oauth2ServerErrorResponseError({ + error: Oauth2ErrorCodes.InvalidScope, + error_description: `Missing required 'scope' parameter`, + }) + } + + if (!authorizationChallengeRequest.issuer_state) { + throw new Oauth2ServerErrorResponseError({ + error: Oauth2ErrorCodes.InvalidRequest, + error_description: `Missing required 'issuer_state' parameter. Only requests initiated by a credential offer are supported for authorization challenge.`, + }) + } + + // FIXME: we need to authenticate the client. Could be either using client_id/client_secret + // but that doesn't make sense for wallets. So for now we just allow any client_id and we will + // need OAuth2 Attestation Based Client Auth and dynamically allow client_ids based on wallet providers + // we trust. Will add this in a follow up PR (basically we do no client authentication at the moment) + // if (!authorizationChallengeRequest.client_id) { + // throw new Oauth2ServerErrorResponseError({ + // error: Oauth2ErrorCodes.InvalidRequest, + // error_description: `Missing required 'client_id' parameter..`, + // }) + // } + + const issuanceSession = await openId4VcIssuerService.findSingleIssuancSessionByQuery(agentContext, { + issuerId: issuer.issuerId, + issuerState: authorizationChallengeRequest.issuer_state, + }) + const allowedStates = [OpenId4VcIssuanceSessionState.OfferCreated, OpenId4VcIssuanceSessionState.OfferUriRetrieved] + if (!issuanceSession || !allowedStates.includes(issuanceSession.state)) { + throw new Oauth2ServerErrorResponseError( + { + error: Oauth2ErrorCodes.InvalidRequest, + error_description: `Invalid 'issuer_state' parameter`, + }, + { + internalMessage: !issuanceSession + ? `Issuance session not found for 'issuer_state' parameter '${authorizationChallengeRequest.issuer_state}'` + : `Issuance session '${issuanceSession.id}' has state '${ + issuanceSession.state + }' but expected one of ${allowedStates.join(', ')}`, + } + ) + } + + const offeredCredentialConfigurations = getOfferedCredentials( + issuanceSession.credentialOfferPayload.credential_configuration_ids, + issuerMetadata.credentialIssuer.credential_configurations_supported + ) + + const allowedScopes = getScopesFromCredentialConfigurationsSupported(offeredCredentialConfigurations) + const requestedScopes = getAllowedAndRequestedScopeValues({ + allowedScopes, + requestedScope: authorizationChallengeRequest.scope, + }) + const requestedCredentialConfigurations = getCredentialConfigurationsSupportedForScopes( + offeredCredentialConfigurations, + requestedScopes + ) as OpenId4VciCredentialConfigurationsSupportedWithFormats + + if (requestedScopes.length === 0 || Object.keys(requestedCredentialConfigurations).length === 0) { + throw new Oauth2ServerErrorResponseError({ + error: Oauth2ErrorCodes.InvalidScope, + error_description: `No requested 'scope' values match with offered credential configurations.`, + }) + } + + const { + authorizationRequest, + verificationSession, + scopes: presentationScopes, + } = await config.getVerificationSessionForIssuanceSessionAuthorization({ + agentContext, + issuanceSession, + requestedCredentialConfigurations, + scopes: requestedScopes, + }) + + // Store presentation during issuance session on the record + verificationSession.presentationDuringIssuanceSession = TypedArrayEncoder.toBase64URL( + agentContext.wallet.getRandomValues(32) + ) + await agentContext.dependencyManager + .resolve(OpenId4VcVerificationSessionRepository) + .update(agentContext, verificationSession) + + const authSession = TypedArrayEncoder.toBase64URL(agentContext.wallet.getRandomValues(32)) + issuanceSession.authorization = { + ...issuanceSession.authorization, + scopes: presentationScopes, + } + issuanceSession.presentation = { + required: true, + authSession, + openId4VcVerificationSessionId: verificationSession.id, + } + + // NOTE: should only allow authenticated clients in the future. + issuanceSession.clientId = authorizationChallengeRequest.client_id + + await openId4VcIssuerService.updateState( + agentContext, + issuanceSession, + OpenId4VcIssuanceSessionState.AuthorizationInitiated + ) + + const authorizationChallengeErrorResponse = authorizationServer.createAuthorizationChallengePresentationErrorResponse( + { + authSession, + presentation: authorizationRequest, + errorDescription: 'Presentation required before issuance', + } + ) + throw new Oauth2ServerErrorResponseError(authorizationChallengeErrorResponse) +} + +async function handleAuthorizationChallengeWithAuthSession(options: { + response: Response + agentContext: AgentContext + issuer: OpenId4VcIssuerRecord + authorizationChallengeRequest: AuthorizationChallengeRequest & { auth_session: string } + next: NextFunction +}) { + const { agentContext, issuer, authorizationChallengeRequest, response, next } = options + + const openId4VcIssuerService = agentContext.dependencyManager.resolve(OpenId4VcIssuerService) + const config = agentContext.dependencyManager.resolve(OpenId4VcIssuerModuleConfig) + const authorizationServer = openId4VcIssuerService.getOauth2AuthorizationServer(agentContext) + const verifierApi = agentContext.dependencyManager.resolve(OpenId4VcVerifierApi) + + // NOTE: we ignore scope, issuer_state etc.. parameters if auth_session is present + // should we validate that these are not in the request? I'm not sure what best practive would be here + + const issuanceSession = await openId4VcIssuerService.findSingleIssuancSessionByQuery(agentContext, { + issuerId: issuer.issuerId, + presentationAuthSession: authorizationChallengeRequest.auth_session, + }) + const allowedStates = [OpenId4VcIssuanceSessionState.AuthorizationInitiated] + if ( + !issuanceSession?.presentation || + !issuanceSession.presentation.openId4VcVerificationSessionId || + !issuanceSession.presentation.authSession || + !allowedStates.includes(issuanceSession.state) + ) { + throw new Oauth2ServerErrorResponseError( + { + error: Oauth2ErrorCodes.InvalidSession, + error_description: `Invalid 'auth_session'`, + }, + { + internalMessage: !issuanceSession + ? `Issuance session not found for 'auth_session' parameter '${authorizationChallengeRequest.auth_session}'` + : !issuanceSession?.presentation + ? `Issuance session '${issuanceSession.id}' has no 'presentation'. This should not happen and means state is corrupted` + : `Issuance session '${issuanceSession.id}' has state '${ + issuanceSession.state + }' but expected one of ${allowedStates.join(', ')}`, + } + ) + } + + const { openId4VcVerificationSessionId } = issuanceSession.presentation + + await verifierApi + .getVerificationSessionById(openId4VcVerificationSessionId) + .catch(async () => { + // Issuance session is corrupted + issuanceSession.errorMessage = `Associated openId4VcVeificationSessionRecord with id '${openId4VcVerificationSessionId}' does not exist` + await openId4VcIssuerService.updateState(agentContext, issuanceSession, OpenId4VcIssuanceSessionState.Error) + + throw new Oauth2ServerErrorResponseError( + { + error: Oauth2ErrorCodes.InvalidSession, + error_description: `Invalid 'auth_session'`, + }, + { + internalMessage: `Openid4vc verification session with id '${openId4VcVerificationSessionId}' not found during issuance session with id '${issuanceSession.id}'`, + } + ) + }) + .then(async (verificationSession) => { + // Issuance session cannot be used anymore + if (verificationSession.state === OpenId4VcVerificationSessionState.Error) { + issuanceSession.errorMessage = `Associated openId4VcVerificationSessionRecord with id '${openId4VcVerificationSessionId}' has error state` + await openId4VcIssuerService.updateState(agentContext, issuanceSession, OpenId4VcIssuanceSessionState.Error) + } + + if ( + verificationSession.state !== OpenId4VcVerificationSessionState.ResponseVerified || + authorizationChallengeRequest.presentation_during_issuance_session !== + verificationSession.presentationDuringIssuanceSession + ) { + throw new Oauth2ServerErrorResponseError( + { + error: Oauth2ErrorCodes.InvalidSession, + error_description: `Invalid presentation for 'auth_session'`, + }, + { + internalMessage: + verificationSession.state !== OpenId4VcVerificationSessionState.ResponseVerified + ? `Openid4vc verification session with id '${openId4VcVerificationSessionId}' has state '${verificationSession.state}', while '${OpenId4VcVerificationSessionState.ResponseVerified}' was expected.` + : `Openid4vc verification session with id '${openId4VcVerificationSessionId}' has 'presentation_during_issuance_session' '${verificationSession.presentationDuringIssuanceSession}', but authorization challenge request provided value '${authorizationChallengeRequest.presentation_during_issuance_session}'.`, + } + ) + } + }) + + // Grant authorization + const authorizationCode = TypedArrayEncoder.toBase64URL(agentContext.wallet.getRandomValues(32)) + const authorizationCodeExpiresAt = addSecondsToDate(new Date(), config.authorizationCodeExpiresInSeconds) + + issuanceSession.authorization = { + ...issuanceSession.authorization, + code: authorizationCode, + codeExpiresAt: authorizationCodeExpiresAt, + } + + // TODO: we need to start using locks so we can't get corrupted state + await openId4VcIssuerService.updateState( + agentContext, + issuanceSession, + OpenId4VcIssuanceSessionState.AuthorizationGranted + ) + + const { authorizationChallengeResponse } = authorizationServer.createAuthorizationChallengeResponse({ + authorizationCode, + }) + + return sendJsonResponse(response, next, authorizationChallengeResponse) +} diff --git a/packages/openid4vc/src/openid4vc-issuer/router/authorizationServerMetadataEndpoint.ts b/packages/openid4vc/src/openid4vc-issuer/router/authorizationServerMetadataEndpoint.ts new file mode 100644 index 0000000000..b3de123291 --- /dev/null +++ b/packages/openid4vc/src/openid4vc-issuer/router/authorizationServerMetadataEndpoint.ts @@ -0,0 +1,32 @@ +import type { OpenId4VcIssuanceRequest } from './requestContext' +import type { Router, Response } from 'express' + +import { getAuthorizationServerMetadataFromList } from '@animo-id/oauth2' + +import { getRequestContext, sendJsonResponse, sendUnknownServerErrorResponse } from '../../shared/router' +import { OpenId4VcIssuerService } from '../OpenId4VcIssuerService' + +/** + * This is the credo authorization server metadata. It is only used for pre-authorized + * code flow. + */ +export function configureOAuthAuthorizationServerMetadataEndpoint(router: Router) { + router.get( + '/.well-known/oauth-authorization-server', + async (_request: OpenId4VcIssuanceRequest, response: Response, next) => { + const { agentContext, issuer } = getRequestContext(_request) + try { + const openId4VcIssuerService = agentContext.dependencyManager.resolve(OpenId4VcIssuerService) + const issuerMetadata = await openId4VcIssuerService.getIssuerMetadata(agentContext, issuer) + const issuerAuthorizationServer = getAuthorizationServerMetadataFromList( + issuerMetadata.authorizationServers, + issuerMetadata.credentialIssuer.credential_issuer + ) + + return sendJsonResponse(response, next, issuerAuthorizationServer) + } catch (e) { + return sendUnknownServerErrorResponse(response, next, agentContext.config.logger, e) + } + } + ) +} diff --git a/packages/openid4vc/src/openid4vc-issuer/router/credentialEndpoint.ts b/packages/openid4vc/src/openid4vc-issuer/router/credentialEndpoint.ts index 6f731f05f2..fc27ced0ed 100644 --- a/packages/openid4vc/src/openid4vc-issuer/router/credentialEndpoint.ts +++ b/packages/openid4vc/src/openid4vc-issuer/router/credentialEndpoint.ts @@ -1,83 +1,267 @@ import type { OpenId4VcIssuanceRequest } from './requestContext' -import type { OpenId4VciCredentialRequest } from '../../shared' -import type { OpenId4VciCredentialRequestToCredentialMapper } from '../OpenId4VcIssuerServiceOptions' +import type { OpenId4VcIssuerModuleConfig } from '../OpenId4VcIssuerModuleConfig' +import type { HttpMethod } from '@animo-id/oauth2' import type { Router, Response } from 'express' -import { getRequestContext, sendErrorResponse } from '../../shared/router' +import { + Oauth2ErrorCodes, + Oauth2ServerErrorResponseError, + Oauth2ResourceUnauthorizedError, + SupportedAuthenticationScheme, +} from '@animo-id/oauth2' +import { getCredentialConfigurationsMatchingRequestFormat } from '@animo-id/oid4vci' +import { joinUriParts } from '@credo-ts/core' + +import { getCredentialConfigurationsSupportedForScopes } from '../../shared' +import { + getRequestContext, + sendJsonResponse, + sendOauth2ErrorResponse, + sendUnauthorizedError, + sendUnknownServerErrorResponse, +} from '../../shared/router' +import { addSecondsToDate } from '../../shared/utils' +import { OpenId4VcIssuanceSessionState } from '../OpenId4VcIssuanceSessionState' import { OpenId4VcIssuerService } from '../OpenId4VcIssuerService' -import { getCNonceFromCredentialRequest } from '../util/credentialRequest' - -import { verifyResourceRequest } from './verifyResourceRequest' - -export interface OpenId4VciCredentialEndpointConfig { - /** - * The path at which the credential endpoint should be made available. Note that it will be - * hosted at a subpath to take into account multiple tenants and issuers. - * - * @default /credential - */ - endpointPath: string - - /** - * A function mapping a credential request to the credential to be issued. - */ - credentialRequestToCredentialMapper: OpenId4VciCredentialRequestToCredentialMapper -} +import { OpenId4VcIssuanceSessionRecord, OpenId4VcIssuanceSessionRepository } from '../repository' -export function configureCredentialEndpoint(router: Router, config: OpenId4VciCredentialEndpointConfig) { - router.post(config.endpointPath, async (request: OpenId4VcIssuanceRequest, response: Response, next) => { +export function configureCredentialEndpoint(router: Router, config: OpenId4VcIssuerModuleConfig) { + router.post(config.credentialEndpointPath, async (request: OpenId4VcIssuanceRequest, response: Response, next) => { const { agentContext, issuer } = getRequestContext(request) const openId4VcIssuerService = agentContext.dependencyManager.resolve(OpenId4VcIssuerService) + const issuerMetadata = await openId4VcIssuerService.getIssuerMetadata(agentContext, issuer, true) + const vcIssuer = openId4VcIssuerService.getIssuer(agentContext) + const resourceServer = openId4VcIssuerService.getResourceServer(agentContext, issuer) - let preAuthorizedCode: string + const fullRequestUrl = joinUriParts(issuerMetadata.credentialIssuer.credential_issuer, [ + config.credentialEndpointPath, + ]) + const resourceRequestResult = await resourceServer + .verifyResourceRequest({ + authorizationServers: issuerMetadata.authorizationServers, + resourceServer: issuerMetadata.credentialIssuer.credential_issuer, + allowedAuthenticationSchemes: config.dpopRequired ? [SupportedAuthenticationScheme.DPoP] : undefined, + request: { + headers: new Headers(request.headers as Record), + method: request.method as HttpMethod, + url: fullRequestUrl, + }, + }) + .catch((error) => { + sendUnauthorizedError(response, next, agentContext.config.logger, error) + }) + if (!resourceRequestResult) return + const { tokenPayload, accessToken, scheme, authorizationServer } = resourceRequestResult - // Verify the access token (should at some point be moved to a middleware function or something) - try { - preAuthorizedCode = (await verifyResourceRequest(agentContext, issuer, request)).preAuthorizedCode - } catch (error) { - return sendErrorResponse(response, agentContext.config.logger, 401, 'unauthorized', error) + const credentialRequest = request.body + const issuanceSessionRepository = agentContext.dependencyManager.resolve(OpenId4VcIssuanceSessionRepository) + + const parsedCredentialRequest = vcIssuer.parseCredentialRequest({ + credentialRequest, + }) + + let issuanceSession: OpenId4VcIssuanceSessionRecord | null = null + const preAuthorizedCode = + typeof tokenPayload['pre-authorized_code'] === 'string' ? tokenPayload['pre-authorized_code'] : undefined + const issuerState = typeof tokenPayload.issuer_state === 'string' ? tokenPayload.issuer_state : undefined + + const subject = tokenPayload.sub + if (!subject) { + return sendOauth2ErrorResponse( + response, + next, + agentContext.config.logger, + new Oauth2ServerErrorResponseError( + { + error: Oauth2ErrorCodes.ServerError, + }, + { + internalMessage: `Received token without 'sub' claim. Subject is required for binding issuance session`, + } + ) + ) } - try { - const credentialRequest = request.body as OpenId4VciCredentialRequest + // Already handle request without format. Simplifies next code sections + if (!parsedCredentialRequest.format) { + return sendOauth2ErrorResponse( + response, + next, + agentContext.config.logger, + new Oauth2ServerErrorResponseError({ + error: parsedCredentialRequest.credentialIdentifier + ? Oauth2ErrorCodes.InvalidCredentialRequest + : Oauth2ErrorCodes.UnsupportedCredentialFormat, + error_description: parsedCredentialRequest.credentialIdentifier + ? `Credential request containing 'credential_identifier' not supported` + : `Credential format '${parsedCredentialRequest.credentialRequest.format}' not supported`, + }) + ) + } - const issuanceSession = await openId4VcIssuerService.findIssuanceSessionForCredentialRequest(agentContext, { + if (preAuthorizedCode || issuerState) { + issuanceSession = await issuanceSessionRepository.findSingleByQuery(agentContext, { issuerId: issuer.issuerId, - credentialRequest, + preAuthorizedCode, + issuerState, }) - if (issuanceSession?.preAuthorizedCode !== preAuthorizedCode) { + if (!issuanceSession) { agentContext.config.logger.warn( - `Credential request used access token with for credential offer with different pre-authorized code than was used for the issuance session ${issuanceSession?.id}` + `No issuance session found for incoming credential request for issuer ${ + issuer.issuerId + } but access token data has ${ + issuerState ? 'issuer_state' : 'pre-authorized_code' + }. Returning error response`, + { + tokenPayload, + } ) - return sendErrorResponse( + + return sendOauth2ErrorResponse( response, + next, agentContext.config.logger, - 401, - 'unauthorized', - 'Access token is not valid for this credential request' + new Oauth2ServerErrorResponseError( + { + error: Oauth2ErrorCodes.CredentialRequestDenied, + }, + { + internalMessage: `No issuance session found for incoming credential request for issuer ${issuer.issuerId} and access token data`, + } + ) ) } - if (!issuanceSession) { - const cNonce = getCNonceFromCredentialRequest(credentialRequest) - agentContext.config.logger.warn( - `No issuance session found for incoming credential request with cNonce ${cNonce} and issuer ${issuer.issuerId}` + // Verify the issuance session subject + if (issuanceSession.authorization?.subject) { + if (issuanceSession.authorization.subject !== tokenPayload.sub) { + return sendOauth2ErrorResponse( + response, + next, + agentContext.config.logger, + new Oauth2ServerErrorResponseError( + { + error: Oauth2ErrorCodes.CredentialRequestDenied, + }, + { + internalMessage: `Issuance session authorization subject does not match with the token payload subject for issuance session '${issuanceSession.id}'. Returning error response`, + } + ) + ) + } + } + // Statefull session expired + else if ( + Date.now() > + addSecondsToDate(issuanceSession.createdAt, config.statefullCredentialOfferExpirationInSeconds).getTime() + ) { + issuanceSession.errorMessage = 'Credential offer has expired' + await openId4VcIssuerService.updateState(agentContext, issuanceSession, OpenId4VcIssuanceSessionState.Error) + throw new Oauth2ServerErrorResponseError({ + // What is the best error here? + error: Oauth2ErrorCodes.CredentialRequestDenied, + error_description: 'Session expired', + }) + } else { + issuanceSession.authorization = { + ...issuanceSession.authorization, + subject: tokenPayload.sub, + } + await issuanceSessionRepository.update(agentContext, issuanceSession) + } + } + + if (!issuanceSession && config.allowDynamicIssuanceSessions) { + agentContext.config.logger.warn( + `No issuance session found for incoming credential request for issuer ${issuer.issuerId} and access token data has no issuer_state or pre-authorized_code. Creating on-demand issuance session`, + { + tokenPayload, + } + ) + + // All credential configurations that match the request scope and credential request + // This is just so we don't create an issuance session that will fail immediately after + const credentialConfigurationsForToken = getCredentialConfigurationsMatchingRequestFormat({ + credentialConfigurations: getCredentialConfigurationsSupportedForScopes( + issuerMetadata.credentialIssuer.credential_configurations_supported, + tokenPayload.scope?.split(' ') ?? [] + ), + requestFormat: parsedCredentialRequest.format, + }) + + if (Object.keys(credentialConfigurationsForToken).length === 0) { + return sendUnauthorizedError( + response, + next, + agentContext.config.logger, + new Oauth2ResourceUnauthorizedError( + 'No credential configurationss match credential request and access token scope', + { + scheme, + error: Oauth2ErrorCodes.InsufficientScope, + } + ), + // Forbidden for InsufficientScope + 403 ) - return sendErrorResponse(response, agentContext.config.logger, 404, 'invalid_request', null) } + issuanceSession = new OpenId4VcIssuanceSessionRecord({ + credentialOfferPayload: { + credential_configuration_ids: Object.keys(credentialConfigurationsForToken), + credential_issuer: issuerMetadata.credentialIssuer.credential_issuer, + }, + issuerId: issuer.issuerId, + state: OpenId4VcIssuanceSessionState.CredentialRequestReceived, + clientId: tokenPayload.client_id, + authorization: { + subject: tokenPayload.sub, + }, + }) + + // Save and update + await issuanceSessionRepository.save(agentContext, issuanceSession) + openId4VcIssuerService.emitStateChangedEvent(agentContext, issuanceSession, null) + } else if (!issuanceSession) { + return sendOauth2ErrorResponse( + response, + next, + agentContext.config.logger, + new Oauth2ServerErrorResponseError( + { + error: Oauth2ErrorCodes.CredentialRequestDenied, + }, + { + internalMessage: `Access token without 'issuer_state' or 'pre-authorized_code' issued by external authorization server provided, but 'allowDynamicIssuanceSessions' is disabled. Either bind the access token to a statefull credential offer, or enable 'allowDynamicIssuanceSessions'.`, + } + ) + ) + } + + try { const { credentialResponse } = await openId4VcIssuerService.createCredentialResponse(agentContext, { issuanceSession, credentialRequest, + authorization: { + authorizationServer, + accessToken: { + payload: tokenPayload, + value: accessToken, + }, + }, }) - response.json(credentialResponse) + return sendJsonResponse(response, next, credentialResponse) } catch (error) { - sendErrorResponse(response, agentContext.config.logger, 500, 'invalid_request', error) - } + if (error instanceof Oauth2ServerErrorResponseError) { + return sendOauth2ErrorResponse(response, next, agentContext.config.logger, error) + } + if (error instanceof Oauth2ResourceUnauthorizedError) { + return sendUnauthorizedError(response, next, agentContext.config.logger, error) + } - // NOTE: if we don't call next, the agentContext session handler will NOT be called - next() + return sendUnknownServerErrorResponse(response, next, agentContext.config.logger, error) + } }) } diff --git a/packages/openid4vc/src/openid4vc-issuer/router/credentialOfferEndpoint.ts b/packages/openid4vc/src/openid4vc-issuer/router/credentialOfferEndpoint.ts index f1e316d1f3..354a74525e 100644 --- a/packages/openid4vc/src/openid4vc-issuer/router/credentialOfferEndpoint.ts +++ b/packages/openid4vc/src/openid4vc-issuer/router/credentialOfferEndpoint.ts @@ -1,35 +1,30 @@ import type { OpenId4VcIssuanceRequest } from './requestContext' -import type { OpenId4VcIssuanceSessionStateChangedEvent } from '../OpenId4VcIssuerEvents' +import type { OpenId4VcIssuerModuleConfig } from '../OpenId4VcIssuerModuleConfig' import type { Router, Response } from 'express' -import { joinUriParts, EventEmitter } from '@credo-ts/core' +import { joinUriParts } from '@credo-ts/core' -import { getRequestContext, sendErrorResponse } from '../../shared/router' +import { + getRequestContext, + sendErrorResponse, + sendJsonResponse, + sendNotFoundResponse, + sendUnknownServerErrorResponse, +} from '../../shared/router' import { OpenId4VcIssuanceSessionState } from '../OpenId4VcIssuanceSessionState' -import { OpenId4VcIssuerEvents } from '../OpenId4VcIssuerEvents' -import { OpenId4VcIssuerModuleConfig } from '../OpenId4VcIssuerModuleConfig' import { OpenId4VcIssuerService } from '../OpenId4VcIssuerService' import { OpenId4VcIssuanceSessionRepository } from '../repository' -export interface OpenId4VciCredentialOfferEndpointConfig { - /** - * The path at which the credential offer should should be made available. Note that it will be - * hosted at a subpath to take into account multiple tenants and issuers. - * - * @default /offers - */ - endpointPath: string -} - -export function configureCredentialOfferEndpoint(router: Router, config: OpenId4VciCredentialOfferEndpointConfig) { +export function configureCredentialOfferEndpoint(router: Router, config: OpenId4VcIssuerModuleConfig) { router.get( - joinUriParts(config.endpointPath, [':credentialOfferId']), + joinUriParts(config.credentialOfferEndpointPath, [':credentialOfferId']), async (request: OpenId4VcIssuanceRequest, response: Response, next) => { const { agentContext, issuer } = getRequestContext(request) if (!request.params.credentialOfferId || typeof request.params.credentialOfferId !== 'string') { return sendErrorResponse( response, + next, agentContext.config.logger, 400, 'invalid_request', @@ -39,14 +34,13 @@ export function configureCredentialOfferEndpoint(router: Router, config: OpenId4 try { const issuerService = agentContext.dependencyManager.resolve(OpenId4VcIssuerService) - const issuerMetadata = issuerService.getIssuerMetadata(agentContext, issuer) + const issuerMetadata = await issuerService.getIssuerMetadata(agentContext, issuer) const openId4VcIssuanceSessionRepository = agentContext.dependencyManager.resolve( OpenId4VcIssuanceSessionRepository ) - const issuerConfig = agentContext.dependencyManager.resolve(OpenId4VcIssuerModuleConfig) - const fullCredentialOfferUri = joinUriParts(issuerMetadata.issuerUrl, [ - issuerConfig.credentialOfferEndpoint.endpointPath, + const fullCredentialOfferUri = joinUriParts(issuerMetadata.credentialIssuer.credential_issuer, [ + config.credentialOfferEndpointPath, request.params.credentialOfferId, ]) @@ -54,50 +48,30 @@ export function configureCredentialOfferEndpoint(router: Router, config: OpenId4 issuerId: issuer.issuerId, credentialOfferUri: fullCredentialOfferUri, }) - - if (!openId4VcIssuanceSession || !openId4VcIssuanceSession.credentialOfferPayload) { - return sendErrorResponse(response, agentContext.config.logger, 404, 'not_found', 'Credential offer not found') + if (!openId4VcIssuanceSession) { + return sendNotFoundResponse(response, next, agentContext.config.logger, 'Credential offer not found') } if ( - ![OpenId4VcIssuanceSessionState.OfferCreated, OpenId4VcIssuanceSessionState.OfferUriRetrieved].includes( - openId4VcIssuanceSession.state - ) + openId4VcIssuanceSession.state !== OpenId4VcIssuanceSessionState.OfferCreated && + openId4VcIssuanceSession.state !== OpenId4VcIssuanceSessionState.OfferUriRetrieved ) { - return sendErrorResponse( - response, - agentContext.config.logger, - 400, - 'invalid_request', - 'Invalid state for credential offer' - ) + return sendNotFoundResponse(response, next, agentContext.config.logger, 'Invalid state for credential offer') } // It's okay to retrieve the offer multiple times. So we only update the state if it's not already retrieved if (openId4VcIssuanceSession.state !== OpenId4VcIssuanceSessionState.OfferUriRetrieved) { - const previousState = openId4VcIssuanceSession.state - - openId4VcIssuanceSession.state = OpenId4VcIssuanceSessionState.OfferUriRetrieved - await openId4VcIssuanceSessionRepository.update(agentContext, openId4VcIssuanceSession) - - agentContext.dependencyManager - .resolve(EventEmitter) - .emit(agentContext, { - type: OpenId4VcIssuerEvents.IssuanceSessionStateChanged, - payload: { - issuanceSession: openId4VcIssuanceSession.clone(), - previousState, - }, - }) + await issuerService.updateState( + agentContext, + openId4VcIssuanceSession, + OpenId4VcIssuanceSessionState.OfferUriRetrieved + ) } - response.json(openId4VcIssuanceSession.credentialOfferPayload) + return sendJsonResponse(response, next, openId4VcIssuanceSession.credentialOfferPayload) } catch (error) { - sendErrorResponse(response, agentContext.config.logger, 500, 'invalid_request', error) + return sendUnknownServerErrorResponse(response, next, agentContext.config.logger, error) } - - // NOTE: if we don't call next, the agentContext session handler will NOT be called - next() } ) } diff --git a/packages/openid4vc/src/openid4vc-issuer/router/index.ts b/packages/openid4vc/src/openid4vc-issuer/router/index.ts index d261c81c50..93b7c21888 100644 --- a/packages/openid4vc/src/openid4vc-issuer/router/index.ts +++ b/packages/openid4vc/src/openid4vc-issuer/router/index.ts @@ -1,5 +1,9 @@ -export { configureAccessTokenEndpoint, OpenId4VciAccessTokenEndpointConfig } from './accessTokenEndpoint' -export { configureCredentialEndpoint, OpenId4VciCredentialEndpointConfig } from './credentialEndpoint' -export { configureIssuerMetadataEndpoint } from './metadataEndpoint' -export { configureCredentialOfferEndpoint, OpenId4VciCredentialOfferEndpointConfig } from './credentialOfferEndpoint' +export { configureAccessTokenEndpoint } from './accessTokenEndpoint' +export { configureCredentialEndpoint } from './credentialEndpoint' +export { configureIssuerMetadataEndpoint } from './issuerMetadataEndpoint' +export { configureOAuthAuthorizationServerMetadataEndpoint } from './authorizationServerMetadataEndpoint' +export { configureCredentialOfferEndpoint } from './credentialOfferEndpoint' export { OpenId4VcIssuanceRequest } from './requestContext' +export { configureJwksEndpoint } from './jwksEndpoint' +export { configureNonceEndpoint } from './nonceEndpoint' +export { configureAuthorizationChallengeEndpoint } from './authorizationChallengeEndpoint' diff --git a/packages/openid4vc/src/openid4vc-issuer/router/issuerMetadataEndpoint.ts b/packages/openid4vc/src/openid4vc-issuer/router/issuerMetadataEndpoint.ts new file mode 100644 index 0000000000..8e993c4fdd --- /dev/null +++ b/packages/openid4vc/src/openid4vc-issuer/router/issuerMetadataEndpoint.ts @@ -0,0 +1,40 @@ +import type { OpenId4VcIssuanceRequest } from './requestContext' +import type { OpenId4VciCredentialIssuerMetadata } from '../../shared' +import type { Router, Response } from 'express' + +import { getAuthorizationServerMetadataFromList } from '@animo-id/oauth2' + +import { getRequestContext, sendJsonResponse, sendUnknownServerErrorResponse } from '../../shared/router' +import { OpenId4VcIssuerService } from '../OpenId4VcIssuerService' + +export function configureIssuerMetadataEndpoint(router: Router) { + router.get( + '/.well-known/openid-credential-issuer', + async (_request: OpenId4VcIssuanceRequest, response: Response, next) => { + const { agentContext, issuer } = getRequestContext(_request) + try { + const openId4VcIssuerService = agentContext.dependencyManager.resolve(OpenId4VcIssuerService) + const issuerMetadata = await openId4VcIssuerService.getIssuerMetadata(agentContext, issuer) + const vcIssuer = openId4VcIssuerService.getIssuer(agentContext) + const issuerAuthorizationServer = getAuthorizationServerMetadataFromList( + issuerMetadata.authorizationServers, + issuerMetadata.credentialIssuer.credential_issuer + ) + + const transformedMetadata = { + // Get the draft 11 metadata (it also contains drfat 14) + ...vcIssuer.getCredentialIssuerMetadataDraft11(issuerMetadata.credentialIssuer), + + // TOOD: these values should be removed, as they need to be hosted in the oauth-authorization-server + // metadata. For backwards compatiblity we will keep them in now. + token_endpoint: issuerAuthorizationServer.token_endpoint, + dpop_signing_alg_values_supported: issuerAuthorizationServer.dpop_signing_alg_values_supported, + } satisfies OpenId4VciCredentialIssuerMetadata + + return sendJsonResponse(response, next, transformedMetadata) + } catch (e) { + return sendUnknownServerErrorResponse(response, next, agentContext.config.logger, e) + } + } + ) +} diff --git a/packages/openid4vc/src/openid4vc-issuer/router/jwksEndpoint.ts b/packages/openid4vc/src/openid4vc-issuer/router/jwksEndpoint.ts new file mode 100644 index 0000000000..2cc6fcfe92 --- /dev/null +++ b/packages/openid4vc/src/openid4vc-issuer/router/jwksEndpoint.ts @@ -0,0 +1,23 @@ +import type { OpenId4VcIssuanceRequest } from './requestContext' +import type { OpenId4VcIssuerModuleConfig } from '../OpenId4VcIssuerModuleConfig' +import type { JwkSet } from '@animo-id/oauth2' +import type { Router, Response } from 'express' + +import { getJwkFromKey, Key } from '@credo-ts/core' + +import { getRequestContext, sendJsonResponse, sendUnknownServerErrorResponse } from '../../shared/router' + +export function configureJwksEndpoint(router: Router, config: OpenId4VcIssuerModuleConfig) { + router.get(config.jwksEndpointPath, async (_request: OpenId4VcIssuanceRequest, response: Response, next) => { + const { agentContext, issuer } = getRequestContext(_request) + try { + const jwks = { + keys: [getJwkFromKey(Key.fromFingerprint(issuer.accessTokenPublicKeyFingerprint)).toJson()], + } satisfies JwkSet + + return sendJsonResponse(response, next, jwks, 'application/jwk-set+json') + } catch (e) { + return sendUnknownServerErrorResponse(response, next, agentContext.config.logger, e) + } + }) +} diff --git a/packages/openid4vc/src/openid4vc-issuer/router/metadataEndpoint.ts b/packages/openid4vc/src/openid4vc-issuer/router/metadataEndpoint.ts deleted file mode 100644 index 4f61bb3666..0000000000 --- a/packages/openid4vc/src/openid4vc-issuer/router/metadataEndpoint.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { OpenId4VcIssuanceRequest } from './requestContext' -import type { CredentialIssuerMetadata } from '@sphereon/oid4vci-common' -import type { Router, Response } from 'express' - -import { getRequestContext, sendErrorResponse } from '../../shared/router' -import { OpenId4VcIssuerService } from '../OpenId4VcIssuerService' - -export function configureIssuerMetadataEndpoint(router: Router) { - router.get( - '/.well-known/openid-credential-issuer', - (_request: OpenId4VcIssuanceRequest, response: Response, next) => { - const { agentContext, issuer } = getRequestContext(_request) - try { - const openId4VcIssuerService = agentContext.dependencyManager.resolve(OpenId4VcIssuerService) - const issuerMetadata = openId4VcIssuerService.getIssuerMetadata(agentContext, issuer) - const transformedMetadata = { - credential_issuer: issuerMetadata.issuerUrl, - token_endpoint: issuerMetadata.tokenEndpoint, - credential_endpoint: issuerMetadata.credentialEndpoint, - authorization_server: issuerMetadata.authorizationServer, - authorization_servers: issuerMetadata.authorizationServer ? [issuerMetadata.authorizationServer] : undefined, - credentials_supported: issuerMetadata.credentialsSupported, - credential_configurations_supported: issuerMetadata.credentialConfigurationsSupported, - display: issuerMetadata.issuerDisplay, - dpop_signing_alg_values_supported: issuerMetadata.dpopSigningAlgValuesSupported, - } satisfies CredentialIssuerMetadata - - response.status(200).json(transformedMetadata) - } catch (e) { - sendErrorResponse(response, agentContext.config.logger, 500, 'invalid_request', e) - } - - // NOTE: if we don't call next, the agentContext session handler will NOT be called - next() - } - ) -} diff --git a/packages/openid4vc/src/openid4vc-issuer/router/nonceEndpoint.ts b/packages/openid4vc/src/openid4vc-issuer/router/nonceEndpoint.ts new file mode 100644 index 0000000000..ff641378d0 --- /dev/null +++ b/packages/openid4vc/src/openid4vc-issuer/router/nonceEndpoint.ts @@ -0,0 +1,33 @@ +import type { OpenId4VcIssuanceRequest } from './requestContext' +import type { OpenId4VcIssuerModuleConfig } from '../OpenId4VcIssuerModuleConfig' +import type { NextFunction, Response, Router } from 'express' + +import { getRequestContext, sendJsonResponse, sendUnknownServerErrorResponse } from '../../shared/router' +import { OpenId4VcIssuerService } from '../OpenId4VcIssuerService' + +export function configureNonceEndpoint(router: Router, config: OpenId4VcIssuerModuleConfig) { + router.post( + config.nonceEndpointPath, + async (request: OpenId4VcIssuanceRequest, response: Response, next: NextFunction) => { + response.set({ 'Cache-Control': 'no-store', Pragma: 'no-cache' }) + const requestContext = getRequestContext(request) + const { agentContext, issuer } = requestContext + + try { + const openId4VcIssuerService = agentContext.dependencyManager.resolve(OpenId4VcIssuerService) + const vcIssuer = openId4VcIssuerService.getIssuer(agentContext) + + const { cNonce, cNonceExpiresInSeconds } = await openId4VcIssuerService.createNonce(agentContext, issuer) + + const nonceResponse = vcIssuer.createNonceResponse({ + cNonce, + cNonceExpiresIn: cNonceExpiresInSeconds, + }) + + return sendJsonResponse(response, next, nonceResponse) + } catch (error) { + return sendUnknownServerErrorResponse(response, next, agentContext.config.logger, error) + } + } + ) +} diff --git a/packages/openid4vc/src/openid4vc-issuer/router/verifyResourceRequest.ts b/packages/openid4vc/src/openid4vc-issuer/router/verifyResourceRequest.ts deleted file mode 100644 index ff007ec7d6..0000000000 --- a/packages/openid4vc/src/openid4vc-issuer/router/verifyResourceRequest.ts +++ /dev/null @@ -1,72 +0,0 @@ -import type { OpenId4VcIssuanceRequest } from './requestContext' -import type { OpenId4VcIssuerRecord } from '../repository' -import type { AgentContext } from '@credo-ts/core' -import type { SigningAlgo } from '@sphereon/oid4vc-common' - -import { CredoError, joinUriParts, JwsService, Jwt } from '@credo-ts/core' -import { verifyResourceDPoP } from '@sphereon/oid4vc-common' - -import { getVerifyJwtCallback } from '../../shared/utils' -import { OpenId4VcIssuerModuleConfig } from '../OpenId4VcIssuerModuleConfig' -import { OpenId4VcIssuerService } from '../OpenId4VcIssuerService' - -export async function verifyResourceRequest( - agentContext: AgentContext, - issuer: OpenId4VcIssuerRecord, - request: OpenId4VcIssuanceRequest -) { - const openId4VcIssuerService = agentContext.dependencyManager.resolve(OpenId4VcIssuerService) - const authorizationHeader = request.headers.authorization - - if (!authorizationHeader) { - throw new CredoError('No access token provided in the authorization header') - } - - if (!authorizationHeader.startsWith('Bearer ') && !authorizationHeader.startsWith('DPoP ')) { - throw new CredoError(`Invalid access token scheme. Expected Bearer or DPoP.`) - } - - const issuerMetadata = openId4VcIssuerService.getIssuerMetadata(agentContext, issuer) - const accessToken = Jwt.fromSerializedJwt(authorizationHeader.replace('Bearer ', '').replace('DPoP ', '')) - const jwsService = agentContext.dependencyManager.resolve(JwsService) - - const { isValid, signerKeys } = await jwsService.verifyJws(agentContext, { - jws: accessToken.serializedJwt, - jwkResolver: () => { - throw new Error('No JWK resolver available for access token verification') - }, - }) - - const issuerConfig = agentContext.dependencyManager.resolve(OpenId4VcIssuerModuleConfig) - const fullUrl = joinUriParts(issuerConfig.baseUrl, [issuer.issuerId, request.url]) - await verifyResourceDPoP( - { method: request.method, headers: request.headers, fullUrl }, - { - jwtVerifyCallback: getVerifyJwtCallback(agentContext), - acceptedAlgorithms: issuerMetadata.dpopSigningAlgValuesSupported as SigningAlgo[] | undefined, - } - ) - - if (!isValid) { - throw new CredoError('Signature on access token is invalid') - } - - if (!signerKeys.map((key) => key.fingerprint).includes(issuer.accessTokenPublicKeyFingerprint)) { - throw new CredoError('Access token was not signed by the expected issuer') - } - - // Finally validate the JWT payload (expiry etc..) - accessToken.payload.validate() - - if (accessToken.payload.iss !== issuerMetadata.issuerUrl) { - throw new CredoError('Access token was not issued by the expected issuer') - } - - if (typeof accessToken.payload.additionalClaims.preAuthorizedCode !== 'string') { - throw new CredoError('No preAuthorizedCode present in access token') - } - - return { - preAuthorizedCode: accessToken.payload.additionalClaims.preAuthorizedCode, - } -} diff --git a/packages/openid4vc/src/openid4vc-issuer/util/credentialRequest.ts b/packages/openid4vc/src/openid4vc-issuer/util/credentialRequest.ts deleted file mode 100644 index 61614148d7..0000000000 --- a/packages/openid4vc/src/openid4vc-issuer/util/credentialRequest.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { OpenId4VciCredentialRequest } from '../../shared' - -import { Jwt, CredoError } from '@credo-ts/core' - -/** - * Extract the 'nonce' parameter from the JWT payload of the credential request. - */ -export function getCNonceFromCredentialRequest(credentialRequest: OpenId4VciCredentialRequest) { - if (!credentialRequest.proof?.jwt) throw new CredoError('No jwt in the credentialRequest proof.') - const jwt = Jwt.fromSerializedJwt(credentialRequest.proof.jwt) - if (!jwt.payload.additionalClaims.nonce || typeof jwt.payload.additionalClaims.nonce !== 'string') - throw new CredoError('No nonce in the credentialRequest JWT proof payload.') - return jwt.payload.additionalClaims.nonce -} diff --git a/packages/openid4vc/src/openid4vc-issuer/util/txCode.ts b/packages/openid4vc/src/openid4vc-issuer/util/txCode.ts new file mode 100644 index 0000000000..db159105b9 --- /dev/null +++ b/packages/openid4vc/src/openid4vc-issuer/util/txCode.ts @@ -0,0 +1,19 @@ +import type { OpenId4VciTxCode } from '../../shared' +import type { AgentContext } from '@credo-ts/core' + +export function generateTxCode(agentContext: AgentContext, txCode: OpenId4VciTxCode) { + const length = txCode.length ?? 4 + const inputMode = txCode.input_mode ?? 'numeric' + + const numbers = '0123456789' + const letters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' + const characters = inputMode === 'numeric' ? numbers : numbers + letters + const random = agentContext.wallet.getRandomValues(length) + + let result = '' + for (let i = 0; i < length; i++) { + result += characters[random[i] % characters.length] + } + + return result +} diff --git a/packages/openid4vc/src/openid4vc-verifier/OpenId4VcSiopVerifierService.ts b/packages/openid4vc/src/openid4vc-verifier/OpenId4VcSiopVerifierService.ts index 7ff3e15a64..9f31ff5771 100644 --- a/packages/openid4vc/src/openid4vc-verifier/OpenId4VcSiopVerifierService.ts +++ b/packages/openid4vc/src/openid4vc-verifier/OpenId4VcSiopVerifierService.ts @@ -43,6 +43,7 @@ import { MdocDeviceResponse, TypedArrayEncoder, Jwt, + extractPresentationsWithDescriptorsFromSubmission, } from '@credo-ts/core' import { AuthorizationRequest, @@ -148,7 +149,7 @@ export class OpenId4VcSiopVerifierService { ) } - const relyingParty = await this.getRelyingParty(agentContext, options.verifier.verifierId, { + const relyingParty = await this.getRelyingParty(agentContext, options.verifier, { presentationDefinition: options.presentationExchange?.definition, authorizationResponseUrl, clientId, @@ -206,7 +207,6 @@ export class OpenId4VcSiopVerifierService { } const verificationSession = await verificationSessionCreatedPromise - return { authorizationRequest: authorizationRequestUri, verificationSession, @@ -230,6 +230,7 @@ export class OpenId4VcSiopVerifierService { options.verificationSession.authorizationRequestJwt ) + const verifier = await this.getVerifierByVerifierId(agentContext, options.verificationSession.verifierId) const requestClientId = await authorizationRequest.getMergedProperty('client_id') const requestNonce = await authorizationRequest.getMergedProperty('nonce') const requestState = await authorizationRequest.getMergedProperty('state') @@ -247,7 +248,7 @@ export class OpenId4VcSiopVerifierService { this.config.authorizationEndpoint.endpointPath, ]) - const relyingParty = await this.getRelyingParty(agentContext, options.verificationSession.verifierId, { + const relyingParty = await this.getRelyingParty(agentContext, verifier, { presentationDefinition: presentationDefinitionsWithLocation?.[0]?.definition, authorizationResponseUrl, clientId: requestClientId, @@ -309,8 +310,6 @@ export class OpenId4VcSiopVerifierService { } } - // TODO: we can also choose to store this in the verification session, however we can easily derive it - // so it's probably easier to make changes in the future if we just store the raw payload. public async getVerifiedAuthorizationResponse( verificationSession: OpenId4VcVerificationSessionRecord ): Promise { @@ -332,7 +331,7 @@ export class OpenId4VcSiopVerifierService { const presentationDefinitions = await authorizationRequest.getPresentationDefinitions() if (presentationDefinitions && presentationDefinitions.length > 0) { - const presentations = authorizationResponse.payload.vp_token + const rawPresentations = authorizationResponse.payload.vp_token ? await extractPresentationsFromVpToken(authorizationResponse.payload.vp_token, { hasher: Hasher.hash, }) @@ -346,12 +345,18 @@ export class OpenId4VcSiopVerifierService { } // FIXME: should return type be an array? As now it doesn't always match the submission - const presentationsArray = Array.isArray(presentations) ? presentations : [presentations] + const verifiablePresentations = Array.isArray(rawPresentations) + ? rawPresentations.map(getVerifiablePresentationFromSphereonWrapped) + : getVerifiablePresentationFromSphereonWrapped(rawPresentations) + const definition = presentationDefinitions[0].definition presentationExchange = { - definition: presentationDefinitions[0].definition, - presentations: presentationsArray.map(getVerifiablePresentationFromSphereonWrapped), + definition, submission, + // We always return this as an array + presentations: Array.isArray(verifiablePresentations) ? verifiablePresentations : [verifiablePresentations], + + descriptors: extractPresentationsWithDescriptorsFromSubmission(verifiablePresentations, submission, definition), } } @@ -444,6 +449,7 @@ export class OpenId4VcSiopVerifierService { public async createVerifier(agentContext: AgentContext, options?: OpenId4VcSiopCreateVerifierOptions) { const openId4VcVerifier = new OpenId4VcVerifierRecord({ verifierId: options?.verifierId ?? utils.uuid(), + clientMetadata: options?.clientMetadata, }) await this.openId4VcVerifierRepository.save(agentContext, openId4VcVerifier) @@ -465,7 +471,7 @@ export class OpenId4VcSiopVerifierService { private async getRelyingParty( agentContext: AgentContext, - verifierId: string, + verifier: OpenId4VcVerifierRecord, { idToken, presentationDefinition, @@ -510,7 +516,7 @@ export class OpenId4VcSiopVerifierService { // all the events are handled, and that the correct context is used for the events. const sphereonEventEmitter = agentContext.dependencyManager .resolve(OpenId4VcRelyingPartyEventHandler) - .getEventEmitterForVerifier(agentContext.contextCorrelationId, verifierId) + .getEventEmitterForVerifier(agentContext.contextCorrelationId, verifier.verifierId) const mode = !responseMode || responseMode === 'direct_post' @@ -550,7 +556,7 @@ export class OpenId4VcSiopVerifierService { // FIXME: should allow verification of revocation // .withRevocationVerificationCallback() .withRevocationVerification(RevocationVerification.NEVER) - .withSessionManager(new OpenId4VcRelyingPartySessionManager(agentContext, verifierId)) + .withSessionManager(new OpenId4VcRelyingPartySessionManager(agentContext, verifier.verifierId)) .withEventEmitter(sphereonEventEmitter) .withResponseType(responseTypes) .withCreateJwtCallback(getCreateJwtCallback(agentContext)) @@ -559,14 +565,18 @@ export class OpenId4VcSiopVerifierService { // TODO: we should probably allow some dynamic values here .withClientMetadata({ ...jarmClientMetadata, + ...verifier.clientMetadata, // FIXME: not passing client_id here means it will not be added // to the authorization request url (not the signed payload). Need // to fix that in Sphereon lib client_id: clientId, passBy: PassBy.VALUE, response_types_supported: [ResponseType.VP_TOKEN], - subject_syntax_types_supported: supportedDidMethods.map((m) => `did:${m}`), - vp_formats_supported: { + subject_syntax_types_supported: [ + 'urn:ietf:params:oauth:jwk-thumbprint', + ...supportedDidMethods.map((m) => `did:${m}`), + ], + vp_formats: { mso_mdoc: { alg: supportedAlgs, }, @@ -576,6 +586,9 @@ export class OpenId4VcSiopVerifierService { jwt_vc_json: { alg: supportedAlgs, }, + jwt_vp_json: { + alg: supportedAlgs, + }, jwt_vp: { alg: supportedAlgs, }, @@ -684,15 +697,21 @@ export class OpenId4VcSiopVerifierService { reason = verificationResult.error?.message } + if (!isValid) { + throw new Error(reason) + } + return { - verified: isValid, - reason, + verified: true, } } catch (error) { agentContext.config.logger.warn('Error occurred during verification of presentation', { error, }) - throw error + return { + verified: false, + reason: error.message, + } } } } diff --git a/packages/openid4vc/src/openid4vc-verifier/OpenId4VcSiopVerifierServiceOptions.ts b/packages/openid4vc/src/openid4vc-verifier/OpenId4VcSiopVerifierServiceOptions.ts index a29ccced72..b0a3d87818 100644 --- a/packages/openid4vc/src/openid4vc-verifier/OpenId4VcSiopVerifierServiceOptions.ts +++ b/packages/openid4vc/src/openid4vc-verifier/OpenId4VcSiopVerifierServiceOptions.ts @@ -1,4 +1,4 @@ -import type { OpenId4VcVerificationSessionRecord } from './repository' +import type { OpenId4VcVerificationSessionRecord, OpenId4VcVerifierRecordProps } from './repository' import type { OpenId4VcIssuerX5c, OpenId4VcJwtIssuer, @@ -10,6 +10,7 @@ import type { DifPresentationExchangeSubmission, DifPresentationExchangeDefinitionV2, VerifiablePresentation, + DifPexPresentationWithDescriptor, } from '@credo-ts/core' export type ResponseMode = 'direct_post' | 'direct_post.jwt' @@ -60,6 +61,14 @@ export interface OpenId4VcSiopCreateAuthorizationRequestReturn { verificationSession: OpenId4VcVerificationSessionRecord } +export interface OpenId4VcSiopVerifiedAuthorizationResponsePresentationExchange { + submission: DifPresentationExchangeSubmission + definition: DifPresentationExchangeDefinition + presentations: Array + + descriptors: DifPexPresentationWithDescriptor[] +} + /** * Either `idToken` and/or `presentationExchange` will be present. */ @@ -68,11 +77,15 @@ export interface OpenId4VcSiopVerifiedAuthorizationResponse { payload: OpenId4VcSiopIdTokenPayload } - presentationExchange?: { - submission: DifPresentationExchangeSubmission - definition: DifPresentationExchangeDefinition - presentations: Array - } + presentationExchange?: OpenId4VcSiopVerifiedAuthorizationResponsePresentationExchange +} + +/** + * Verifier metadata that will be send when creating a request + */ +export interface OpenId4VcSiopVerifierClientMetadata { + client_name?: string + logo_uri?: string } export interface OpenId4VcSiopCreateVerifierOptions { @@ -80,4 +93,11 @@ export interface OpenId4VcSiopCreateVerifierOptions { * Id of the verifier, not the id of the verifier record. Will be exposed publicly */ verifierId?: string + + /** + * Optional client metadata that will be included in requests + */ + clientMetadata?: OpenId4VcSiopVerifierClientMetadata } + +export type OpenId4VcUpdateVerifierRecordOptions = Pick diff --git a/packages/openid4vc/src/openid4vc-verifier/OpenId4VcVerifierApi.ts b/packages/openid4vc/src/openid4vc-verifier/OpenId4VcVerifierApi.ts index 03a67d083e..353450794c 100644 --- a/packages/openid4vc/src/openid4vc-verifier/OpenId4VcVerifierApi.ts +++ b/packages/openid4vc/src/openid4vc-verifier/OpenId4VcVerifierApi.ts @@ -3,6 +3,7 @@ import type { OpenId4VcSiopVerifyAuthorizationResponseOptions, OpenId4VcSiopCreateAuthorizationRequestReturn, OpenId4VcSiopCreateVerifierOptions, + OpenId4VcUpdateVerifierRecordOptions, } from './OpenId4VcSiopVerifierServiceOptions' import type { OpenId4VcVerificationSessionRecord } from './repository' import type { OpenId4VcSiopAuthorizationResponsePayload } from '../shared' @@ -45,6 +46,16 @@ export class OpenId4VcVerifierApi { return this.openId4VcSiopVerifierService.createVerifier(this.agentContext, options) } + public async updateVerifierMetadata(options: OpenId4VcUpdateVerifierRecordOptions) { + const { verifierId, clientMetadata } = options + + const verifier = await this.openId4VcSiopVerifierService.getVerifierByVerifierId(this.agentContext, verifierId) + + verifier.clientMetadata = clientMetadata + + return this.openId4VcSiopVerifierService.updateVerifier(this.agentContext, verifier) + } + public async findVerificationSessionsByQuery( query: Query, queryOptions?: QueryOptions diff --git a/packages/openid4vc/src/openid4vc-verifier/OpenId4VcVerifierModule.ts b/packages/openid4vc/src/openid4vc-verifier/OpenId4VcVerifierModule.ts index 4e44b2883e..22ea4f3ade 100644 --- a/packages/openid4vc/src/openid4vc-verifier/OpenId4VcVerifierModule.ts +++ b/packages/openid4vc/src/openid4vc-verifier/OpenId4VcVerifierModule.ts @@ -3,6 +3,7 @@ import type { OpenId4VcVerificationRequest } from './router' import type { AgentContext, DependencyManager, Module } from '@credo-ts/core' import type { NextFunction } from 'express' +import { setGlobalConfig } from '@animo-id/oauth2' import { AgentConfig } from '@credo-ts/core' import { getAgentContextForActorId, getRequestContext, importExpress } from '../shared/router' @@ -27,15 +28,22 @@ export class OpenId4VcVerifierModule implements Module { } /** - * Registers the dependencies of the question answer module on the dependency manager. + * Registers the dependencies of the openid4vc verifier module on the dependency manager. */ public register(dependencyManager: DependencyManager) { + const agentConfig = dependencyManager.resolve(AgentConfig) + // Warn about experimental module - const logger = dependencyManager.resolve(AgentConfig).logger - logger.warn( - "The '@credo-ts/openid4vc' Verifier module is experimental and could have unexpected breaking changes. When using this module, make sure to use strict versions for all @credo-ts packages." + agentConfig.logger.warn( + "The '@credo-ts/openid4vc' Holder module is experimental and could have unexpected breaking changes. When using this module, make sure to use strict versions for all @credo-ts packages." ) + if (agentConfig.allowInsecureHttpUrls) { + setGlobalConfig({ + allowInsecureUrls: true, + }) + } + // Register config dependencyManager.registerInstance(OpenId4VcVerifierModuleConfig, this.config) diff --git a/packages/openid4vc/src/openid4vc-verifier/__tests__/openid4vc-verifier.test.ts b/packages/openid4vc/src/openid4vc-verifier/__tests__/openid4vc-verifier.test.ts index e40ef70579..a2a03f8c9c 100644 --- a/packages/openid4vc/src/openid4vc-verifier/__tests__/openid4vc-verifier.test.ts +++ b/packages/openid4vc/src/openid4vc-verifier/__tests__/openid4vc-verifier.test.ts @@ -1,6 +1,5 @@ import { Jwt } from '@credo-ts/core' import { SigningAlgo } from '@sphereon/oid4vc-common' -import { cleanAll, enableNetConnect } from 'nock' import { AskarModule } from '../../../../askar/src' import { askarModuleConfig } from '../../../../askar/tests/helpers' @@ -28,11 +27,6 @@ describe('OpenId4VcVerifier', () => { }) describe('Verification', () => { - afterEach(() => { - cleanAll() - enableNetConnect() - }) - it('check openid proof request format (vp token)', async () => { const openIdVerifier = await verifier.agent.modules.openId4VcVerifier.createVerifier() const { authorizationRequest, verificationSession } = diff --git a/packages/openid4vc/src/openid4vc-verifier/repository/OpenId4VcVerificationSessionRecord.ts b/packages/openid4vc/src/openid4vc-verifier/repository/OpenId4VcVerificationSessionRecord.ts index 24205c1099..0fedfa4706 100644 --- a/packages/openid4vc/src/openid4vc-verifier/repository/OpenId4VcVerificationSessionRecord.ts +++ b/packages/openid4vc/src/openid4vc-verifier/repository/OpenId4VcVerificationSessionRecord.ts @@ -27,6 +27,12 @@ export interface OpenId4VcVerificationSessionRecordProps { authorizationRequestJwt: string authorizationResponsePayload?: OpenId4VcSiopAuthorizationResponsePayload + + /** + * Presentation during issuance session. This is used when issuance of a credential requires a presentation, and helps + * prevent session fixation attacks + */ + presentationDuringIssuanceSession?: string } export class OpenId4VcVerificationSessionRecord extends BaseRecord { @@ -64,6 +70,12 @@ export class OpenId4VcVerificationSessionRecord extends BaseRecord { - describe('credentialsSupportedV13toV11', () => { - test('should correctly transform from v13 to v11 format', () => { - expect( - credentialsSupportedV13ToV11({ - 'pid-sd-jwt': { - scope: 'pid', - cryptographic_binding_methods_supported: ['jwk'], - credential_signing_alg_values_supported: ['ES256'], - proof_types_supported: { - jwt: { - proof_signing_alg_values_supported: ['ES256'], - }, - }, - vct: 'urn:eu.europa.ec.eudi:pid:1', - format: 'vc+sd-jwt', - }, - }) - ).toEqual([ - { - id: 'pid-sd-jwt', - scope: 'pid', - cryptographic_binding_methods_supported: ['jwk'], - cryptographic_suites_supported: ['ES256'], - vct: 'urn:eu.europa.ec.eudi:pid:1', - format: 'vc+sd-jwt', - }, - ]) - }) - }) - - describe('credentialsSupportedV11toV13', () => { - test('should correctly transform from v11 to v13 format', () => { - expect( - credentialsSupportedV11ToV13(agentContext, [ - { - id: 'pid-sd-jwt', - scope: 'pid', - cryptographic_binding_methods_supported: ['jwk'], - cryptographic_suites_supported: ['ES256'], - vct: 'urn:eu.europa.ec.eudi:pid:1', - format: 'vc+sd-jwt', - }, - ]) - ).toEqual({ - 'pid-sd-jwt': { - scope: 'pid', - cryptographic_binding_methods_supported: ['jwk'], - credential_signing_alg_values_supported: ['ES256'], - proof_types_supported: { - jwt: { - proof_signing_alg_values_supported: ['ES256'], - }, - }, - vct: 'urn:eu.europa.ec.eudi:pid:1', - format: 'vc+sd-jwt', - order: undefined, - display: undefined, - claims: undefined, - }, - }) - }) - }) -}) diff --git a/packages/openid4vc/src/shared/callbacks.ts b/packages/openid4vc/src/shared/callbacks.ts new file mode 100644 index 0000000000..ccade5d668 --- /dev/null +++ b/packages/openid4vc/src/shared/callbacks.ts @@ -0,0 +1,110 @@ +import type { OpenId4VcIssuerRecord } from '../openid4vc-issuer/repository' +import type { + CallbackContext, + ClientAuthenticationCallback, + SignJwtCallback, + VerifyJwtCallback, +} from '@animo-id/oauth2' +import type { AgentContext } from '@credo-ts/core' + +import { clientAuthenticationDynamic, clientAuthenticationNone } from '@animo-id/oauth2' +import { CredoError, getJwkFromJson, getJwkFromKey, Hasher, JsonEncoder, JwsService } from '@credo-ts/core' + +import { getKeyFromDid } from './utils' + +export function getOid4vciJwtVerifyCallback(agentContext: AgentContext): VerifyJwtCallback { + const jwsService = agentContext.dependencyManager.resolve(JwsService) + + return async (signer, { compact }) => { + const { isValid } = await jwsService.verifyJws(agentContext, { + jws: compact, + // Only handles kid as did resolution. JWK is handled by jws service + jwkResolver: async () => { + if (signer.method === 'jwk') { + return getJwkFromJson(signer.publicJwk) + } else if (signer.method === 'did') { + const key = await getKeyFromDid(agentContext, signer.didUrl) + return getJwkFromKey(key) + } + + throw new CredoError(`Unexpected call to jwk resolver for signer method ${signer.method}`) + }, + }) + + return isValid + } +} + +export function getOid4vciJwtSignCallback(agentContext: AgentContext): SignJwtCallback { + const jwsService = agentContext.dependencyManager.resolve(JwsService) + + return async (signer, { payload, header }) => { + if (signer.method === 'custom' || signer.method === 'x5c') { + throw new CredoError(`Jwt signer method 'custom' and 'x5c' are not supported for jwt signer.`) + } + + const key = + signer.method === 'did' ? await getKeyFromDid(agentContext, signer.didUrl) : getJwkFromJson(signer.publicJwk).key + const jwk = getJwkFromKey(key) + + if (!jwk.supportsSignatureAlgorithm(signer.alg)) { + throw new CredoError(`key type '${jwk.keyType}', does not support the JWS signature alg '${signer.alg}'`) + } + + const jwt = await jwsService.createJwsCompact(agentContext, { + protectedHeaderOptions: { + ...header, + jwk: header.jwk ? getJwkFromJson(header.jwk) : undefined, + }, + payload: JsonEncoder.toBuffer(payload), + key, + }) + + return jwt + } +} + +export function getOid4vciCallbacks(agentContext: AgentContext) { + return { + hash: (data, alg) => Hasher.hash(data, alg.toLowerCase()), + generateRandom: (length) => agentContext.wallet.getRandomValues(length), + signJwt: getOid4vciJwtSignCallback(agentContext), + clientAuthentication: clientAuthenticationNone(), + verifyJwt: getOid4vciJwtVerifyCallback(agentContext), + fetch: agentContext.config.agentDependencies.fetch, + } satisfies Partial +} + +/** + * Allows us to authenticate when making requests to an external + * authorizatin server + */ +export function dynamicOid4vciClientAuthentication( + agentContext: AgentContext, + issuerRecord: OpenId4VcIssuerRecord +): ClientAuthenticationCallback { + return (callbackOptions) => { + const authorizationServer = issuerRecord.authorizationServerConfigs?.find( + (a) => a.issuer === callbackOptions.authorizationServerMetata.issuer + ) + + if (!authorizationServer) { + // No client authentication if authorization server is not configured + agentContext.config.logger.debug( + `Unknown authorization server '${callbackOptions.authorizationServerMetata.issuer}' for issuer '${issuerRecord.issuerId}' for request to '${callbackOptions.url}'` + ) + return + } + + if (!authorizationServer.clientAuthentication) { + throw new CredoError( + `Unable to authenticate to authorization server '${authorizationServer.issuer}' for issuer '${issuerRecord.issuerId}' for request to '${callbackOptions.url}'. Make sure to configure a 'clientId' and 'clientSecret' for the authorization server on the issuer record.` + ) + } + + return clientAuthenticationDynamic({ + clientId: authorizationServer.clientAuthentication.clientId, + clientSecret: authorizationServer.clientAuthentication.clientSecret, + })(callbackOptions) + } +} diff --git a/packages/openid4vc/src/shared/issuerMetadataUtils.ts b/packages/openid4vc/src/shared/issuerMetadataUtils.ts index 81bf5f42f8..5379eddec6 100644 --- a/packages/openid4vc/src/shared/issuerMetadataUtils.ts +++ b/packages/openid4vc/src/shared/issuerMetadataUtils.ts @@ -1,260 +1,75 @@ import type { OpenId4VciCredentialConfigurationsSupported, - OpenId4VciCredentialConfigurationSupported, - OpenId4VciCredentialSupported, - OpenId4VciCredentialSupportedWithId, + OpenId4VciCredentialConfigurationsSupportedWithFormats, } from './models' -import type { AgentContext, JwaSignatureAlgorithm } from '@credo-ts/core' -import type { CredentialOfferFormatV1_0_11 } from '@sphereon/oid4vci-common' -import { CredoError } from '@credo-ts/core' - -import { getSupportedJwaSignatureAlgorithms } from './utils' +import { type CredentialConfigurationsSupported } from '@animo-id/oid4vci' /** - * Get all `types` from a `CredentialSupported` object. - * - * Depending on the format, the types may be nested, or have different a different name/type + * Returns all entries from the credential offer with the associated metadata resolved. */ -export function getTypesFromCredentialSupported( - credentialSupported: OpenId4VciCredentialConfigurationSupported -): string[] | undefined { - if ( - credentialSupported.format === 'jwt_vc_json-ld' || - credentialSupported.format === 'ldp_vc' || - credentialSupported.format === 'jwt_vc_json' || - credentialSupported.format === 'jwt_vc' - ) { - if (!credentialSupported.credential_definition || !Array.isArray(credentialSupported.credential_definition.type)) { - throw Error( - `Unable to extract types from credentials supported for format ${credentialSupported.format}. credential_definition.type is not defined` - ) - } - - return credentialSupported.credential_definition.type - } else if (credentialSupported.format === 'vc+sd-jwt') { - if (!credentialSupported.vct) { - throw Error( - `Unable to extract types from credentials supported for format ${credentialSupported.format}. vct is not defined` - ) - } - return credentialSupported.vct ? [credentialSupported.vct] : undefined - } else if (credentialSupported.format === 'mso_mdoc') { - if (!credentialSupported.doctype) { - throw Error( - `Unable to extract types from credentials supported for format ${credentialSupported.format}. Doctype is not defined` - ) - } - return [credentialSupported.doctype] - } - - throw Error(`Unable to extract types from credentials supported. Unknown format ${credentialSupported.format}`) -} - -export function credentialConfigurationSupportedToCredentialSupported( - id: string, - config: OpenId4VciCredentialConfigurationSupported -): OpenId4VciCredentialSupportedWithId { - const baseConfig = { - id, - scope: config.scope, - cryptographic_binding_methods_supported: config.cryptographic_binding_methods_supported, - cryptographic_suites_supported: config.credential_signing_alg_values_supported, - display: config.display, - order: config.order, - } - - if (config.format === 'mso_mdoc') { - return { - ...baseConfig, - doctype: config.doctype, - format: config.format, - claims: config.claims, - } - } else if (config.format === 'jwt_vc_json' || config.format === 'jwt_vc') { - return { - ...baseConfig, - format: config.format, - credentialSubject: config.credential_definition?.credentialSubject, - types: config.credential_definition?.type ?? [], - } - } else if (config.format === 'ldp_vc' || config.format === 'jwt_vc_json-ld') { - if (!config.credential_definition?.['@context']) { - throw new Error( - `Unable to transform from draft 13 credential configuration to draft 11 credential supported for format ${config.format}. credential_definition.@context is not defined` - ) - } +export function getOfferedCredentials< + Configurations extends + | OpenId4VciCredentialConfigurationsSupported + | OpenId4VciCredentialConfigurationsSupportedWithFormats +>( + offeredCredentialConfigurationIds: Array, + credentialConfigurationsSupported: Configurations, + { ignoreNotFoundIds = false }: { ignoreNotFoundIds?: boolean } = {} +): Configurations extends OpenId4VciCredentialConfigurationsSupportedWithFormats + ? OpenId4VciCredentialConfigurationsSupportedWithFormats + : OpenId4VciCredentialConfigurationsSupported { + const offeredCredentialConfigurations: OpenId4VciCredentialConfigurationsSupported = {} + for (const offeredCredentialConfigurationId of offeredCredentialConfigurationIds) { + const foundCredentialConfiguration = credentialConfigurationsSupported[offeredCredentialConfigurationId] - return { - ...baseConfig, - format: config.format, - '@context': config.credential_definition['@context'], - credentialSubject: config.credential_definition.credentialSubject, - types: config.credential_definition.type, - } - } else if (config.format === 'vc+sd-jwt') { - if (!config.vct) { - throw new Error( - `Unable to transform from draft 13 credential configuration to draft 11 credential supported for format ${config.format}. vct is not defined` - ) + // Make sure the issuer metadata includes the offered credential. + if (!foundCredentialConfiguration) { + if (!ignoreNotFoundIds) { + throw new Error( + `Offered credential configuration id '${offeredCredentialConfigurationId}' is not part of credential_configurations_supported of the issuer metadata.` + ) + } + + continue } - return { - ...baseConfig, - format: config.format, - vct: config.vct, - claims: config.claims, - } + offeredCredentialConfigurations[offeredCredentialConfigurationId] = foundCredentialConfiguration } - throw new CredoError(`Unsupported credential format ${config.format}`) + return offeredCredentialConfigurations as Configurations extends OpenId4VciCredentialConfigurationsSupportedWithFormats + ? OpenId4VciCredentialConfigurationsSupportedWithFormats + : OpenId4VciCredentialConfigurationsSupported } -export function credentialSupportedToCredentialConfigurationSupported( - agentContext: AgentContext, - credentialSupported: OpenId4VciCredentialSupportedWithId -): OpenId4VciCredentialConfigurationSupported { - const supportedJwaSignatureAlgorithms = getSupportedJwaSignatureAlgorithms(agentContext) - - // We assume the jwt proof_types_supported is the same as the cryptographic_suites_supported when converting from v11 to v13 - const proofSigningAlgValuesSupported = - credentialSupported.cryptographic_suites_supported?.filter((alg) => - supportedJwaSignatureAlgorithms.includes(alg as JwaSignatureAlgorithm) - ) ?? supportedJwaSignatureAlgorithms - - // proof_types_supported was not available in v11. We assume jwt proof type supported - const proofTypesSupported = { - jwt: { - proof_signing_alg_values_supported: proofSigningAlgValuesSupported, - }, - } as const - - const baseCredentialConfigurationSupported = { - scope: credentialSupported.scope, - cryptographic_binding_methods_supported: credentialSupported.cryptographic_binding_methods_supported, - credential_signing_alg_values_supported: credentialSupported.cryptographic_suites_supported, - // This is not necessarily true, but the best we can do for now - proof_types_supported: proofTypesSupported, - display: credentialSupported.display, - order: credentialSupported.order, - } - - if (credentialSupported.format === 'jwt_vc_json' || credentialSupported.format === 'jwt_vc') { - return { - ...baseCredentialConfigurationSupported, - format: credentialSupported.format, - credential_definition: { - credentialSubject: credentialSupported.credentialSubject, - type: credentialSupported.types, - }, - } - } else if (credentialSupported.format === 'ldp_vc' || credentialSupported.format === 'jwt_vc_json-ld') { - return { - ...baseCredentialConfigurationSupported, - format: credentialSupported.format, - credential_definition: { - '@context': credentialSupported['@context'] as string[], - credentialSubject: credentialSupported.credentialSubject, - type: credentialSupported.types, - }, - } - } else if (credentialSupported.format === 'vc+sd-jwt') { - return { - ...baseCredentialConfigurationSupported, - format: credentialSupported.format, - vct: credentialSupported.vct, - claims: credentialSupported.claims, - } - } else if (credentialSupported.format === 'mso_mdoc') { - return { - ...baseCredentialConfigurationSupported, - format: credentialSupported.format, - doctype: credentialSupported.doctype, - claims: credentialSupported.claims, - } - } - - throw new CredoError(`Unsupported credential format ${credentialSupported.format}`) +export function getScopesFromCredentialConfigurationsSupported( + credentialConfigurationsSupported: CredentialConfigurationsSupported +): string[] { + return Array.from( + new Set( + Object.values(credentialConfigurationsSupported) + .map((configuration) => configuration.scope) + .filter((scope): scope is string => scope !== undefined) + ) + ) } -export function credentialsSupportedV13ToV11( - credentialConfigurationSupported: OpenId4VciCredentialConfigurationsSupported -): OpenId4VciCredentialSupportedWithId[] { - const credentialsSupportedWithId: OpenId4VciCredentialSupportedWithId[] = [] +export function getAllowedAndRequestedScopeValues(options: { requestedScope: string; allowedScopes: string[] }) { + const requestedScopeValues = options.requestedScope.split(' ') + const allowedAndRequestedScopeValues = options.allowedScopes.filter((allowedScope) => + requestedScopeValues.includes(allowedScope) + ) - for (const [id, credentialConfiguration] of Object.entries(credentialConfigurationSupported)) { - credentialsSupportedWithId.push(credentialConfigurationSupportedToCredentialSupported(id, credentialConfiguration)) - } - - return credentialsSupportedWithId + return allowedAndRequestedScopeValues } -export function credentialsSupportedV11ToV13( - agentContext: AgentContext, - credentialsSupported: OpenId4VciCredentialSupportedWithId[] -): OpenId4VciCredentialConfigurationsSupported { - const credentialConfigurationsSupported: OpenId4VciCredentialConfigurationsSupported = {} - - for (const credentialSupported of credentialsSupported) { - credentialConfigurationsSupported[credentialSupported.id] = credentialSupportedToCredentialConfigurationSupported( - agentContext, - credentialSupported +export function getCredentialConfigurationsSupportedForScopes( + credentialConfigurationsSupported: CredentialConfigurationsSupported, + scopes: string[] +) { + return Object.fromEntries( + Object.entries(credentialConfigurationsSupported).filter( + ([, configuration]) => configuration.scope && scopes.includes(configuration.scope) ) - } - - return credentialConfigurationsSupported -} - -/** - * Returns all entries from the credential offer with the associated metadata resolved. For 'id' entries, the associated `credentials_supported` object is resolved from the issuer metadata. - * For inline entries, an error is thrown. - */ -export function getOfferedCredentials( - agentContext: AgentContext, - offeredCredentials: Array, - credentialsSupportedOrConfigurations: OpenId4VciCredentialConfigurationsSupported | OpenId4VciCredentialSupported[] -): { - credentialsSupported: OpenId4VciCredentialSupportedWithId[] - credentialConfigurationsSupported: OpenId4VciCredentialConfigurationsSupported -} { - const offeredCredentialConfigurations: OpenId4VciCredentialConfigurationsSupported = {} - const offeredCredentialsSupported: OpenId4VciCredentialSupportedWithId[] = [] - - const credentialsSupported = Array.isArray(credentialsSupportedOrConfigurations) - ? credentialsSupportedOrConfigurations.filter((s): s is OpenId4VciCredentialSupportedWithId => s.id !== undefined) - : credentialsSupportedV13ToV11(credentialsSupportedOrConfigurations) - - const credentialConfigurationsSupported = Array.isArray(credentialsSupportedOrConfigurations) - ? credentialsSupportedV11ToV13( - agentContext, - credentialsSupportedOrConfigurations.filter((s): s is OpenId4VciCredentialSupportedWithId => s.id !== undefined) - ) - : credentialsSupportedOrConfigurations - - for (const offeredCredential of offeredCredentials) { - // In draft 12 inline credential offers are removed. It's easier to already remove support now. - if (typeof offeredCredential !== 'string') { - throw new CredoError( - 'Only referenced credentials pointing to an id in credentials_supported issuer metadata are supported' - ) - } - - const foundCredentialConfiguration = credentialConfigurationsSupported[offeredCredential] - const foundCredentialSupported = credentialsSupported.find((supported) => supported.id === offeredCredential) - - // Make sure the issuer metadata includes the offered credential. - if (!foundCredentialConfiguration || !foundCredentialSupported) { - throw new Error( - `Offered credential '${offeredCredential}' is not part of credentials_supported/credential_configurations_supported of the issuer metadata.` - ) - } - - offeredCredentialConfigurations[offeredCredential] = foundCredentialConfiguration - offeredCredentialsSupported.push(foundCredentialSupported) - } - - return { - credentialConfigurationsSupported: offeredCredentialConfigurations, - credentialsSupported: offeredCredentialsSupported, - } + ) } diff --git a/packages/openid4vc/src/shared/models/CredentialHolderBinding.ts b/packages/openid4vc/src/shared/models/CredentialHolderBinding.ts index 2c174dab9e..76d622c5e1 100644 --- a/packages/openid4vc/src/shared/models/CredentialHolderBinding.ts +++ b/packages/openid4vc/src/shared/models/CredentialHolderBinding.ts @@ -1,4 +1,4 @@ -import type { Jwk } from '@credo-ts/core' +import type { Jwk, Key } from '@credo-ts/core' export type OpenId4VcCredentialHolderDidBinding = { method: 'did' @@ -11,3 +11,4 @@ export type OpenId4VcCredentialHolderJwkBinding = { } export type OpenId4VcCredentialHolderBinding = OpenId4VcCredentialHolderDidBinding | OpenId4VcCredentialHolderJwkBinding +export type OpenId4VcCredentialHolderBindingWithKey = OpenId4VcCredentialHolderBinding & { key: Key } diff --git a/packages/openid4vc/src/shared/models/OpenId4VciAuthorizationServerConfig.ts b/packages/openid4vc/src/shared/models/OpenId4VciAuthorizationServerConfig.ts new file mode 100644 index 0000000000..d9bd136455 --- /dev/null +++ b/packages/openid4vc/src/shared/models/OpenId4VciAuthorizationServerConfig.ts @@ -0,0 +1,12 @@ +export interface OpenId4VciAuthorizationServerConfig { + // The base Url of your OAuth Server + issuer: string + + /** + * Optional client authentication for token introspection + */ + clientAuthentication?: { + clientId: string + clientSecret: string + } +} diff --git a/packages/openid4vc/src/shared/models/index.ts b/packages/openid4vc/src/shared/models/index.ts index 98ae4f161a..c99cbeab96 100644 --- a/packages/openid4vc/src/shared/models/index.ts +++ b/packages/openid4vc/src/shared/models/index.ts @@ -1,50 +1,48 @@ +import type { + CredentialConfigurationSupported, + CredentialConfigurationSupportedWithFormats, + CredentialIssuerMetadata, + CredentialIssuerMetadataDisplayEntry, + CredentialOfferPreAuthorizedCodeGrantTxCode, + CredentialRequest, + CredentialRequestFormatSpecific, + CredentialRequestWithFormats, + IssuerMetadataResult, + ParseCredentialRequestReturn, + CredentialOfferObject, +} from '@animo-id/oid4vci' import type { VerifiedAuthorizationRequest, AuthorizationRequestPayload, AuthorizationResponsePayload, IDTokenPayload, } from '@sphereon/did-auth-siop' -import type { - AssertedUniformCredentialOffer, - CredentialConfigurationSupportedV1_0_13, - CredentialIssuerMetadataV1_0_11, - CredentialIssuerMetadataV1_0_13, - CredentialOfferPayloadV1_0_11, - CredentialOfferPayloadV1_0_13, - CredentialRequestJwtVcJson, - CredentialRequestJwtVcJsonLdAndLdpVc, - CredentialRequestJwtVcJsonLdAndLdpVcV1_0_13, - CredentialRequestJwtVcJsonV1_0_13, - CredentialRequestSdJwtVc, - CredentialsSupportedLegacy, - MetadataDisplay, - TxCode, - UniformCredentialRequest, -} from '@sphereon/oid4vci-common' - -export type OpenId4VciCredentialSupportedWithId = OpenId4VciCredentialSupported & { id: string } -export type OpenId4VciCredentialSupported = CredentialsSupportedLegacy -export type OpenId4VciCredentialConfigurationSupported = CredentialConfigurationSupportedV1_0_13 + +export { preAuthorizedCodeGrantIdentifier, authorizationCodeGrantIdentifier } from '@animo-id/oauth2' + +export type OpenId4VciCredentialConfigurationSupportedWithFormats = CredentialConfigurationSupportedWithFormats +export type OpenId4VciCredentialConfigurationSupported = CredentialConfigurationSupported + export type OpenId4VciCredentialConfigurationsSupported = Record -export type OpenId4VciTxCode = TxCode +export type OpenId4VciCredentialConfigurationsSupportedWithFormats = Record< + string, + OpenId4VciCredentialConfigurationSupportedWithFormats +> -export type OpenId4VciIssuerMetadataV1Draft11 = CredentialIssuerMetadataV1_0_11 -export type OpenId4VciIssuerMetadataV1Draft13 = CredentialIssuerMetadataV1_0_13 -export type OpenId4VciIssuerMetadata = OpenId4VciIssuerMetadataV1Draft11 | OpenId4VciIssuerMetadataV1Draft13 +export type OpenId4VciMetadata = IssuerMetadataResult -export type OpenId4VciIssuerMetadataDisplay = MetadataDisplay +export type OpenId4VciTxCode = CredentialOfferPreAuthorizedCodeGrantTxCode +export type OpenId4VciCredentialIssuerMetadata = CredentialIssuerMetadata -export type OpenId4VciCredentialRequest = UniformCredentialRequest +export type OpenId4VciParsedCredentialRequest = ParseCredentialRequestReturn +export type OpenId4VciCredentialRequestFormatSpecific = CredentialRequestFormatSpecific -export type OpenId4VciCredentialRequestJwtVcJson = CredentialRequestJwtVcJson | CredentialRequestJwtVcJsonV1_0_13 +export type OpenId4VciCredentialIssuerMetadataDisplay = CredentialIssuerMetadataDisplayEntry -export type OpenId4VciCredentialRequestJwtVcJsonLdAndLdpVc = - | CredentialRequestJwtVcJsonLdAndLdpVc - | CredentialRequestJwtVcJsonLdAndLdpVcV1_0_13 +export type OpenId4VciCredentialRequest = CredentialRequest +export type OpenId4VciCredentialRequestWithFormats = CredentialRequestWithFormats -export type OpenId4VciCredentialRequestSdJwtVc = CredentialRequestSdJwtVc -export type OpenId4VciCredentialOffer = AssertedUniformCredentialOffer -export type OpenId4VciCredentialOfferPayload = CredentialOfferPayloadV1_0_11 | CredentialOfferPayloadV1_0_13 +export type OpenId4VciCredentialOfferPayload = CredentialOfferObject export type OpenId4VcSiopVerifiedAuthorizationRequest = VerifiedAuthorizationRequest export type OpenId4VcSiopAuthorizationRequestPayload = AuthorizationRequestPayload @@ -54,3 +52,4 @@ export type OpenId4VcSiopIdTokenPayload = IDTokenPayload export * from './OpenId4VcJwtIssuer' export * from './CredentialHolderBinding' export * from './OpenId4VciCredentialFormatProfile' +export * from './OpenId4VciAuthorizationServerConfig' diff --git a/packages/openid4vc/src/shared/router/context.ts b/packages/openid4vc/src/shared/router/context.ts index 0bf538a69d..81890bb635 100644 --- a/packages/openid4vc/src/shared/router/context.ts +++ b/packages/openid4vc/src/shared/router/context.ts @@ -1,6 +1,8 @@ +import type { Oauth2ErrorCodes, Oauth2ServerErrorResponseError } from '@animo-id/oauth2' import type { AgentContext, Logger } from '@credo-ts/core' -import type { Response, Request } from 'express' +import type { Response, Request, NextFunction } from 'express' +import { Oauth2ResourceUnauthorizedError, SupportedAuthenticationScheme } from '@animo-id/oauth2' import { CredoError } from '@credo-ts/core' export interface OpenId4VcRequest = Record> extends Request { @@ -11,16 +13,102 @@ export interface OpenId4VcRequestContext { agentContext: AgentContext } -export function sendErrorResponse(response: Response, logger: Logger, code: number, message: string, error: unknown) { - const error_description = - error instanceof Error ? error.message : typeof error === 'string' ? error : 'An unknown error occurred.' +export function sendUnauthorizedError( + response: Response, + next: NextFunction, + logger: Logger, + error: unknown | Oauth2ResourceUnauthorizedError, + status?: number +) { + const errorMessage = error instanceof Error ? error.message : error + logger.warn(`[OID4VC] Sending authorization error response: ${JSON.stringify(errorMessage)}`, { + error, + }) + + const unauhorizedError = + error instanceof Oauth2ResourceUnauthorizedError + ? error + : new Oauth2ResourceUnauthorizedError('Unknown error occured', [ + { scheme: SupportedAuthenticationScheme.DPoP }, + { scheme: SupportedAuthenticationScheme.Bearer }, + ]) + + response + .setHeader('WWW-Authenticate', unauhorizedError.toHeaderValue()) + .status(status ?? 403) + .send() + next(error) +} + +export function sendOauth2ErrorResponse( + response: Response, + next: NextFunction, + logger: Logger, + error: Oauth2ServerErrorResponseError +) { + logger.warn(`[OID4VC] Sending oauth2 error response: ${JSON.stringify(error.message)}`, { + error, + }) - const body = { error: message, error_description } - logger.warn(`[OID4VCI] Sending error response: ${JSON.stringify(body)}`, { + response.status(error.status).json(error.errorResponse) + next(error) +} +export function sendUnknownServerErrorResponse(response: Response, next: NextFunction, logger: Logger, error: unknown) { + logger.error(`[OID4VC] Sending unknown server error response`, { error, }) - return response.status(code).json(body) + response.status(500).json({ + error: 'server_error', + }) + + const throwError = + error instanceof Error ? error : new CredoError('Unknown error in openid4vc error response handler') + next(throwError) +} + +export function sendNotFoundResponse(response: Response, next: NextFunction, logger: Logger, internalReason: string) { + logger.debug(`[OID4VC] Sending not found response: ${internalReason}`) + + response.status(404).send() + next(new CredoError(internalReason)) +} + +export function sendErrorResponse( + response: Response, + next: NextFunction, + logger: Logger, + status: number, + message: Oauth2ErrorCodes | string, + error: unknown, + additionalPayload?: Record +) { + const body = { error: message, ...additionalPayload } + logger.warn(`[OID4VC] Sending error response: ${JSON.stringify(body)}`, { + error, + }) + + response.status(status).json(body) + + const throwError = + error instanceof Error ? error : new CredoError('Unknown error in openid4vc error response handler') + next(throwError) +} + +export function sendJsonResponse( + response: Response, + next: NextFunction, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + body: any, + contentType?: string, + status?: number +) { + response + .setHeader('Content-Type', contentType ?? 'application/json') + .status(status ?? 200) + .send(JSON.stringify(body)) + + next() } // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/packages/openid4vc/src/shared/utils.ts b/packages/openid4vc/src/shared/utils.ts index f47fca1d1f..e67e54c4b5 100644 --- a/packages/openid4vc/src/shared/utils.ts +++ b/packages/openid4vc/src/shared/utils.ts @@ -1,8 +1,7 @@ import type { OpenId4VcIssuerX5c, OpenId4VcJwtIssuer } from './models' -import type { AgentContext, JwaSignatureAlgorithm, JwkJson, Key } from '@credo-ts/core' +import type { AgentContext, DidPurpose, JwaSignatureAlgorithm, JwkJson, Key } from '@credo-ts/core' import type { JwtIssuerWithContext as VpJwtIssuerWithContext, VerifyJwtCallback } from '@sphereon/did-auth-siop' import type { DPoPJwtIssuerWithContext, CreateJwtCallback, JwtIssuer } from '@sphereon/oid4vc-common' -import type { CredentialOfferPayloadV1_0_11, CredentialOfferPayloadV1_0_13 } from '@sphereon/oid4vci-common' import { CredoError, @@ -41,10 +40,14 @@ export function getSupportedJwaSignatureAlgorithms(agentContext: AgentContext): return supportedJwaSignatureAlgorithms } -async function getKeyFromDid(agentContext: AgentContext, didUrl: string) { +export async function getKeyFromDid( + agentContext: AgentContext, + didUrl: string, + allowedPurposes: DidPurpose[] = ['authentication'] +) { const didsApi = agentContext.dependencyManager.resolve(DidsApi) const didDocument = await didsApi.resolveDidDocument(didUrl) - const verificationMethod = didDocument.dereferenceKey(didUrl, ['authentication']) + const verificationMethod = didDocument.dereferenceKey(didUrl, allowedPurposes) return getKeyFromVerificationMethod(verificationMethod) } @@ -136,16 +139,27 @@ export async function openIdTokenIssuerToJwtIssuer( throw new CredoError(`No supported signature algorithms found key type: '${jwk.keyType}'`) } - if (!openId4VcTokenIssuer.issuer.startsWith('https://')) { + if ( + !openId4VcTokenIssuer.issuer.startsWith('https://') && + !(openId4VcTokenIssuer.issuer.startsWith('http://') && agentContext.config.allowInsecureHttpUrls) + ) { throw new CredoError('The X509 certificate issuer must be a HTTPS URI.') } if ( - !leafCertificate.sanUriNames?.includes(openId4VcTokenIssuer.issuer) && - !leafCertificate.sanDnsNames?.includes(getDomainFromUrl(openId4VcTokenIssuer.issuer)) + !leafCertificate.sanUriNames.includes(openId4VcTokenIssuer.issuer) && + !leafCertificate.sanDnsNames.includes(getDomainFromUrl(openId4VcTokenIssuer.issuer)) ) { + const sanUriMessage = + leafCertificate.sanUriNames.length > 0 + ? `SAN-URI names are ${leafCertificate.sanUriNames.join(', ')}` + : 'there are no SAN-URI names' + const sanDnsMessage = + leafCertificate.sanDnsNames.length > 0 + ? `SAN-DNS names are ${leafCertificate.sanDnsNames.join(', ')}` + : 'there are no SAN-DNS names' throw new Error( - `The 'iss' claim in the payload does not match a 'SAN-URI' or 'SAN-DNS' name in the x5c certificate.` + `The 'iss' claim in the payload does not match a 'SAN-URI' or 'SAN-DNS' name in the x5c certificate. 'iss' value is '${openId4VcTokenIssuer.issuer}', ${sanUriMessage}, ${sanDnsMessage} (for SAN-DNS only domain has to match)` ) } @@ -179,8 +193,10 @@ export function getProofTypeFromKey(agentContext: AgentContext, key: Key) { return supportedSignatureSuites[0].proofType } -export const isCredentialOfferV1Draft13 = ( - credentialOffer: CredentialOfferPayloadV1_0_11 | CredentialOfferPayloadV1_0_13 -): credentialOffer is CredentialOfferPayloadV1_0_13 => { - return 'credential_configuration_ids' in credentialOffer +export function addSecondsToDate(date: Date, seconds: number) { + return new Date(date.getTime() + seconds * 1000) +} + +export function dateToSeconds(date: Date) { + return Math.floor(date.getTime() * 1000) } diff --git a/packages/openid4vc/tests/openid4vc-batch-issuance.e2e.test.ts b/packages/openid4vc/tests/openid4vc-batch-issuance.e2e.test.ts new file mode 100644 index 0000000000..e90f587ba9 --- /dev/null +++ b/packages/openid4vc/tests/openid4vc-batch-issuance.e2e.test.ts @@ -0,0 +1,182 @@ +import type { AgentType } from './utils' +import type { OpenId4VciCredentialBindingResolver } from '../src/openid4vc-holder' + +import { getJwkFromKey } from '@credo-ts/core' +import express, { type Express } from 'express' + +import { setupNockToExpress } from '../../../tests/nockToExpress' +import { AskarModule } from '../../askar/src' +import { askarModuleConfig } from '../../askar/tests/helpers' +import { + OpenId4VcHolderModule, + OpenId4VcIssuanceSessionState, + OpenId4VcIssuerModule, + OpenId4VciCredentialFormatProfile, +} from '../src' + +import { waitForCredentialIssuanceSessionRecordSubject, createAgentFromModules } from './utils' +import { universityDegreeCredentialConfigurationSupportedMdoc } from './utilsVci' + +const baseUrl = 'http://localhost:3991' +const issuerBaseUrl = `${baseUrl}/oid4vci` + +describe('OpenId4Vc Presentation During Issuance', () => { + let expressApp: Express + let clearNock: () => void + + let issuer: AgentType<{ + openId4VcIssuer: OpenId4VcIssuerModule + askar: AskarModule + }> + + let holder: AgentType<{ + openId4VcHolder: OpenId4VcHolderModule + askar: AskarModule + }> + + beforeEach(async () => { + expressApp = express() + + issuer = await createAgentFromModules('issuer', { + openId4VcIssuer: new OpenId4VcIssuerModule({ + baseUrl: issuerBaseUrl, + credentialRequestToCredentialMapper: async ({ + credentialRequestFormat, + holderBindings, + credentialConfigurationIds, + }) => { + const credentialConfigurationId = credentialConfigurationIds[0] + + if (credentialRequestFormat?.format === OpenId4VciCredentialFormatProfile.MsoMdoc) { + return { + credentialConfigurationId, + format: OpenId4VciCredentialFormatProfile.MsoMdoc, + credentials: holderBindings.map((holderBinding, index) => ({ + docType: credentialRequestFormat.doctype, + holderKey: holderBinding.key, + issuerCertificate: issuer.certificate.toString('base64'), + namespaces: { + [credentialRequestFormat.doctype]: { + index, + }, + }, + validityInfo: { + validFrom: new Date('2024-01-01'), + validUntil: new Date('2050-01-01'), + }, + })), + } + } + + throw new Error('not supported') + }, + }), + askar: new AskarModule(askarModuleConfig), + }) + + holder = await createAgentFromModules('holder', { + openId4VcHolder: new OpenId4VcHolderModule(), + askar: new AskarModule(askarModuleConfig), + }) + + await holder.agent.x509.addTrustedCertificate(issuer.certificate.toString('base64')) + await issuer.agent.x509.addTrustedCertificate(issuer.certificate.toString('base64')) + + // We let AFJ create the router, so we have a fresh one each time + expressApp.use('/oid4vci', issuer.agent.modules.openId4VcIssuer.config.router) + clearNock = setupNockToExpress(baseUrl, expressApp) + }) + + afterEach(async () => { + clearNock() + await issuer.agent.shutdown() + await issuer.agent.wallet.delete() + + await holder.agent.shutdown() + await holder.agent.wallet.delete() + }) + + const credentialBindingResolver: OpenId4VciCredentialBindingResolver = async ({ agentContext, keyTypes }) => ({ + method: 'jwk', + jwk: getJwkFromKey(await agentContext.wallet.createKey({ keyType: keyTypes[0] })), + }) + + it('e2e flow issuing a batch of mdoc', async () => { + const issuerRecord = await issuer.agent.modules.openId4VcIssuer.createIssuer({ + issuerId: '2f9c0385-7191-4c50-aa22-40cf5839d52b', + batchCredentialIssuance: { + batchSize: 10, + }, + credentialConfigurationsSupported: { + universityDegree: universityDegreeCredentialConfigurationSupportedMdoc, + }, + }) + + // Create offer for university degree + const { issuanceSession, credentialOffer } = await issuer.agent.modules.openId4VcIssuer.createCredentialOffer({ + issuerId: issuerRecord.issuerId, + offeredCredentials: ['universityDegree'], + preAuthorizedCodeFlowConfig: {}, + }) + + // Resolve offer + const resolvedCredentialOffer = await holder.agent.modules.openId4VcHolder.resolveCredentialOffer(credentialOffer) + + // Request access token + const tokenResponse = await holder.agent.modules.openId4VcHolder.requestToken({ + resolvedCredentialOffer, + }) + + // Request credentials + const credentialResponse = await holder.agent.modules.openId4VcHolder.requestCredentials({ + resolvedCredentialOffer, + ...tokenResponse, + requestBatch: true, + credentialBindingResolver, + }) + + await waitForCredentialIssuanceSessionRecordSubject(issuer.replaySubject, { + state: OpenId4VcIssuanceSessionState.Completed, + issuanceSessionId: issuanceSession.id, + }) + + expect(credentialResponse.credentials).toHaveLength(1) + expect(credentialResponse.credentials[0].credentials).toHaveLength(10) + }) + + it('e2e flow requesting a batch of mdoc larger than max batch size', async () => { + const issuerRecord = await issuer.agent.modules.openId4VcIssuer.createIssuer({ + issuerId: '2f9c0385-7191-4c50-aa22-40cf5839d52b', + batchCredentialIssuance: { + batchSize: 10, + }, + credentialConfigurationsSupported: { + universityDegree: universityDegreeCredentialConfigurationSupportedMdoc, + }, + }) + + const { credentialOffer } = await issuer.agent.modules.openId4VcIssuer.createCredentialOffer({ + issuerId: issuerRecord.issuerId, + offeredCredentials: ['universityDegree'], + preAuthorizedCodeFlowConfig: {}, + }) + + // Resolve offer + const resolvedCredentialOffer = await holder.agent.modules.openId4VcHolder.resolveCredentialOffer(credentialOffer) + + // Request access token + const tokenResponse = await holder.agent.modules.openId4VcHolder.requestToken({ + resolvedCredentialOffer, + }) + + // Request credentials + await expect( + holder.agent.modules.openId4VcHolder.requestCredentials({ + resolvedCredentialOffer, + ...tokenResponse, + requestBatch: 12, + credentialBindingResolver, + }) + ).rejects.toThrow(`the max batch size is '10'. A total of '12' proofs were provided.`) + }) +}) diff --git a/packages/openid4vc/tests/openid4vc-presentation-during-issuance.e2e.test.ts b/packages/openid4vc/tests/openid4vc-presentation-during-issuance.e2e.test.ts new file mode 100644 index 0000000000..329292773e --- /dev/null +++ b/packages/openid4vc/tests/openid4vc-presentation-during-issuance.e2e.test.ts @@ -0,0 +1,408 @@ +import type { AgentType } from './utils' +import type { OpenId4VciSignSdJwtCredentials } from '../src' +import type { OpenId4VciCredentialBindingResolver } from '../src/openid4vc-holder' +import type { DifPresentationExchangeDefinitionV2, SdJwtVc, SdJwtVcIssuer } from '@credo-ts/core' + +import { AuthorizationFlow } from '@animo-id/oid4vci' +import { ClaimFormat, getJwkFromKey } from '@credo-ts/core' +import express, { type Express } from 'express' + +import { setupNockToExpress } from '../../../tests/nockToExpress' +import { AskarModule } from '../../askar/src' +import { askarModuleConfig } from '../../askar/tests/helpers' +import { + OpenId4VcVerifierModule, + OpenId4VcHolderModule, + OpenId4VcIssuanceSessionState, + OpenId4VcIssuerModule, + getScopesFromCredentialConfigurationsSupported, +} from '../src' + +import { waitForCredentialIssuanceSessionRecordSubject, createAgentFromModules } from './utils' +import { universityDegreeCredentialConfigurationSupported } from './utilsVci' + +const presentationDefinition = { + id: 'a34cff9d-a825-4283-9d9a-e84f97ebdd08', + input_descriptors: [ + { + id: 'e498bd12-be8f-4884-8ffe-2704176b99be', + name: 'Identity Credential', + purpose: 'To issue your university degree we need to verify your identity', + constraints: { + limit_disclosure: 'required', + fields: [ + { + path: ['$.vct'], + filter: { + type: 'string', + const: 'urn:eu.europa.ec.eudi:pid:1', + }, + }, + { + path: ['$.given_name'], + filter: { + type: 'string', + }, + }, + { + path: ['$.family_name'], + filter: { + type: 'string', + }, + }, + ], + }, + }, + ], +} as const satisfies DifPresentationExchangeDefinitionV2 + +const baseUrl = 'http://localhost:4871' +const issuerBaseUrl = `${baseUrl}/oid4vci` +const verifierBaseUrl = `${baseUrl}/oid4vp` + +describe('OpenId4Vc Presentation During Issuance', () => { + let expressApp: Express + let clearNock: () => void + + let issuer: AgentType<{ + openId4VcIssuer: OpenId4VcIssuerModule + openId4VcVerifier: OpenId4VcVerifierModule + askar: AskarModule + }> + + let holder: AgentType<{ + openId4VcHolder: OpenId4VcHolderModule + askar: AskarModule + }> + + beforeEach(async () => { + expressApp = express() + + issuer = await createAgentFromModules('issuer', { + openId4VcIssuer: new OpenId4VcIssuerModule({ + baseUrl: issuerBaseUrl, + getVerificationSessionForIssuanceSessionAuthorization: async ({ issuanceSession, scopes }) => { + if (scopes.includes(universityDegreeCredentialConfigurationSupported.scope)) { + const createRequestReturn = await issuer.agent.modules.openId4VcVerifier.createAuthorizationRequest({ + verifierId: issuanceSession.issuerId, + requestSigner: { + method: 'x5c', + x5c: [issuer.certificate.toString('base64')], + }, + responseMode: 'direct_post.jwt', + presentationExchange: { + definition: presentationDefinition, + }, + }) + + return { + ...createRequestReturn, + scopes: [universityDegreeCredentialConfigurationSupported.scope], + } + } + + throw new Error('Unsupported scope values') + }, + credentialRequestToCredentialMapper: async ({ + credentialRequest, + holderBindings, + credentialConfigurationIds, + verification, + }) => { + if (!verification) { + throw new Error('Expected verification in credential request mapper') + } + + const credentialConfigurationId = credentialConfigurationIds[0] + const descriptor = verification.presentationExchange.descriptors.find( + (descriptor) => descriptor.descriptor.id === presentationDefinition.input_descriptors[0].id + ) + + if (!descriptor || descriptor.format !== ClaimFormat.SdJwtVc) { + throw new Error('Expected descriptor with sd-jwt vc format') + } + + const fullName = `${descriptor.credential.prettyClaims.given_name} ${descriptor.credential.prettyClaims.family_name}` + + if (credentialRequest.format === 'vc+sd-jwt') { + return { + credentialConfigurationId, + format: credentialRequest.format, + credentials: holderBindings.map((holderBinding) => ({ + payload: { vct: credentialRequest.vct, full_name: fullName, degree: 'Software Engineer' }, + holder: holderBinding, + issuer: { + method: 'x5c', + x5c: [issuer.certificate.toString('base64')], + issuer: baseUrl, + }, + disclosureFrame: { + _sd: ['full_name'], + }, + })), + } satisfies OpenId4VciSignSdJwtCredentials + } else { + throw new Error('Invalid request') + } + }, + }), + openId4VcVerifier: new OpenId4VcVerifierModule({ + baseUrl: verifierBaseUrl, + }), + askar: new AskarModule(askarModuleConfig), + }) + + holder = await createAgentFromModules('holder', { + openId4VcHolder: new OpenId4VcHolderModule(), + askar: new AskarModule(askarModuleConfig), + }) + + await holder.agent.x509.addTrustedCertificate(issuer.certificate.toString('base64')) + await issuer.agent.x509.addTrustedCertificate(issuer.certificate.toString('base64')) + + // We let AFJ create the router, so we have a fresh one each time + expressApp.use('/oid4vci', issuer.agent.modules.openId4VcIssuer.config.router) + expressApp.use('/oid4vp', issuer.agent.modules.openId4VcVerifier.config.router) + + clearNock = setupNockToExpress(baseUrl, expressApp) + }) + + afterEach(async () => { + clearNock() + + await issuer.agent.shutdown() + await issuer.agent.wallet.delete() + + await holder.agent.shutdown() + await holder.agent.wallet.delete() + }) + + const credentialBindingResolver: OpenId4VciCredentialBindingResolver = () => ({ + method: 'jwk', + jwk: getJwkFromKey(holder.key), + }) + + it('e2e flow with requesting presentation of credentials before issuance succeeds', async () => { + const issuerRecord = await issuer.agent.modules.openId4VcIssuer.createIssuer({ + issuerId: '2f9c0385-7191-4c50-aa22-40cf5839d52b', + credentialConfigurationsSupported: { + universityDegree: universityDegreeCredentialConfigurationSupported, + }, + }) + + const x5cIssuer = { + method: 'x5c', + x5c: [issuer.certificate.toString('base64')], + issuer: baseUrl, + } satisfies SdJwtVcIssuer + + await issuer.agent.modules.openId4VcVerifier.createVerifier({ + verifierId: '2f9c0385-7191-4c50-aa22-40cf5839d52b', + }) + + // Pre-store identity credential + const holderIdentityCredential = await issuer.agent.sdJwtVc.sign({ + issuer: x5cIssuer, + payload: { + vct: 'urn:eu.europa.ec.eudi:pid:1', + given_name: 'Erika', + family_name: 'Powerstar', + }, + disclosureFrame: { + _sd: ['given_name', 'family_name'], + }, + holder: { + method: 'jwk', + jwk: holder.jwk, + }, + }) + await holder.agent.sdJwtVc.store(holderIdentityCredential.compact) + + // Create offer for university degree + const { issuanceSession, credentialOffer } = await issuer.agent.modules.openId4VcIssuer.createCredentialOffer({ + issuerId: issuerRecord.issuerId, + offeredCredentials: ['universityDegree'], + authorizationCodeFlowConfig: { + requirePresentationDuringIssuance: true, + }, + }) + + // Resolve offer + const resolvedCredentialOffer = await holder.agent.modules.openId4VcHolder.resolveCredentialOffer(credentialOffer) + const resolvedAuthorization = await holder.agent.modules.openId4VcHolder.resolveIssuanceAuthorizationRequest( + resolvedCredentialOffer, + { + clientId: 'foo', + redirectUri: 'http://localhost:1234/redirect', + scope: getScopesFromCredentialConfigurationsSupported(resolvedCredentialOffer.offeredCredentialConfigurations), + } + ) + + // Ensure presentation request + if (resolvedAuthorization.authorizationFlow !== AuthorizationFlow.PresentationDuringIssuance) { + throw new Error('Not supported') + } + const resolvedPresentationRequest = await holder.agent.modules.openId4VcHolder.resolveSiopAuthorizationRequest( + resolvedAuthorization.oid4vpRequestUrl + ) + if (!resolvedPresentationRequest.presentationExchange) { + throw new Error('Missing presentation exchange') + } + + // Submit presentation + const selectedCredentials = holder.agent.modules.openId4VcHolder.selectCredentialsForRequest( + resolvedPresentationRequest.presentationExchange.credentialsForRequest + ) + const siopResult = await holder.agent.modules.openId4VcHolder.acceptSiopAuthorizationRequest({ + authorizationRequest: resolvedPresentationRequest.authorizationRequest, + presentationExchange: { + credentials: selectedCredentials, + }, + }) + expect(siopResult.serverResponse.status).toEqual(200) + expect(siopResult.ok).toEqual(true) + if (!siopResult.ok) { + throw new Error('not ok') + } + + // Request authorization code + const { authorizationCode } = await holder.agent.modules.openId4VcHolder.retrieveAuthorizationCodeUsingPresentation( + { + authSession: resolvedAuthorization.authSession, + resolvedCredentialOffer, + presentationDuringIssuanceSession: siopResult.presentationDuringIssuanceSession, + } + ) + + // Request access token + const tokenResponse = await holder.agent.modules.openId4VcHolder.requestToken({ + resolvedCredentialOffer, + code: authorizationCode, + clientId: 'foo', + redirectUri: 'http://localhost:1234/redirect', + }) + + // Request credential + const credentialResponse = await holder.agent.modules.openId4VcHolder.requestCredentials({ + resolvedCredentialOffer, + ...tokenResponse, + clientId: 'foo', + credentialBindingResolver, + }) + + await waitForCredentialIssuanceSessionRecordSubject(issuer.replaySubject, { + state: OpenId4VcIssuanceSessionState.Completed, + issuanceSessionId: issuanceSession.id, + }) + + expect(credentialResponse.credentials).toHaveLength(1) + const compactSdJwtVc = (credentialResponse.credentials[0].credentials[0] as SdJwtVc).compact + const sdJwtVc = holder.agent.sdJwtVc.fromCompact(compactSdJwtVc) + expect(sdJwtVc.payload.vct).toEqual(universityDegreeCredentialConfigurationSupported.vct) + expect(sdJwtVc.prettyClaims.full_name).toEqual('Erika Powerstar') + }) + + it('e2e flow with requesting presentation of credentials before issuance but fails because presentation not verified', async () => { + const issuerRecord = await issuer.agent.modules.openId4VcIssuer.createIssuer({ + issuerId: '2f9c0385-7191-4c50-aa22-40cf5839d52b', + credentialConfigurationsSupported: { + universityDegree: universityDegreeCredentialConfigurationSupported, + }, + }) + + const x5cIssuer = { + method: 'x5c', + x5c: [issuer.certificate.toString('base64')], + issuer: baseUrl, + } satisfies SdJwtVcIssuer + + await issuer.agent.modules.openId4VcVerifier.createVerifier({ + verifierId: '2f9c0385-7191-4c50-aa22-40cf5839d52b', + }) + + // Pre-store identity credential + const holderIdentityCredential = await issuer.agent.sdJwtVc.sign({ + issuer: x5cIssuer, + payload: { + vct: 'urn:eu.europa.ec.eudi:pid:1', + given_name: 'Erika', + family_name: 'Powerstar', + }, + disclosureFrame: { + _sd: ['given_name', 'family_name'], + }, + holder: { + method: 'jwk', + jwk: holder.jwk, + }, + }) + await holder.agent.sdJwtVc.store(holderIdentityCredential.compact) + + // Create offer for university degree + const { credentialOffer } = await issuer.agent.modules.openId4VcIssuer.createCredentialOffer({ + issuerId: issuerRecord.issuerId, + offeredCredentials: ['universityDegree'], + authorizationCodeFlowConfig: { + requirePresentationDuringIssuance: true, + }, + }) + + // Resolve offer + const resolvedCredentialOffer = await holder.agent.modules.openId4VcHolder.resolveCredentialOffer(credentialOffer) + const resolvedAuthorization = await holder.agent.modules.openId4VcHolder.resolveIssuanceAuthorizationRequest( + resolvedCredentialOffer, + { + clientId: 'foo', + redirectUri: 'http://localhost:1234/redirect', + scope: getScopesFromCredentialConfigurationsSupported(resolvedCredentialOffer.offeredCredentialConfigurations), + } + ) + + // Ensure presentation request + if (resolvedAuthorization.authorizationFlow !== AuthorizationFlow.PresentationDuringIssuance) { + throw new Error('Not supported') + } + const resolvedPresentationRequest = await holder.agent.modules.openId4VcHolder.resolveSiopAuthorizationRequest( + resolvedAuthorization.oid4vpRequestUrl + ) + if (!resolvedPresentationRequest.presentationExchange) { + throw new Error('Missing presentation exchange') + } + + // Requesting authorization code should fail + await expect( + holder.agent.modules.openId4VcHolder.retrieveAuthorizationCodeUsingPresentation({ + authSession: resolvedAuthorization.authSession, + resolvedCredentialOffer, + }) + ).rejects.toThrow(`Invalid presentation for 'auth_session'`) + }) + + it('e2e flow with requesting presentation of credentials before issuance but fails because invalid auth session', async () => { + const issuerRecord = await issuer.agent.modules.openId4VcIssuer.createIssuer({ + issuerId: '2f9c0385-7191-4c50-aa22-40cf5839d52b', + credentialConfigurationsSupported: { + universityDegree: universityDegreeCredentialConfigurationSupported, + }, + }) + + // Create offer for university degree + const { credentialOffer } = await issuer.agent.modules.openId4VcIssuer.createCredentialOffer({ + issuerId: issuerRecord.issuerId, + offeredCredentials: ['universityDegree'], + authorizationCodeFlowConfig: { + requirePresentationDuringIssuance: true, + }, + }) + + // Resolve offer + const resolvedCredentialOffer = await holder.agent.modules.openId4VcHolder.resolveCredentialOffer(credentialOffer) + + await expect( + holder.agent.modules.openId4VcHolder.retrieveAuthorizationCodeUsingPresentation({ + authSession: 'some auth session', + resolvedCredentialOffer, + }) + ).rejects.toThrow("Invalid 'auth_session'") + }) +}) diff --git a/packages/openid4vc/tests/openid4vc.e2e.test.ts b/packages/openid4vc/tests/openid4vc.e2e.test.ts index 44b416a0db..12c7dff9fb 100644 --- a/packages/openid4vc/tests/openid4vc.e2e.test.ts +++ b/packages/openid4vc/tests/openid4vc.e2e.test.ts @@ -1,9 +1,18 @@ import type { AgentType, TenantType } from './utils' -import type { OpenId4VciSignMdocCredential } from '../src' +import type { OpenId4VciSignMdocCredentials } from '../src' import type { OpenId4VciCredentialBindingResolver } from '../src/openid4vc-holder' -import type { DifPresentationExchangeDefinitionV2, Mdoc, MdocDeviceResponse, SdJwtVc } from '@credo-ts/core' +import type { AuthorizationServerMetadata } from '@animo-id/oauth2' +import type { DifPresentationExchangeDefinitionV2, JwkJson, Mdoc, MdocDeviceResponse, SdJwtVc } from '@credo-ts/core' import type { Server } from 'http' +import { + calculateJwkThumbprint, + clientAuthenticationNone, + HashAlgorithm, + Oauth2AuthorizationServer, + preAuthorizedCodeGrantIdentifier, +} from '@animo-id/oauth2' +import { AuthorizationFlow } from '@animo-id/oid4vci' import { CredoError, ClaimFormat, @@ -24,6 +33,9 @@ import { X509ModuleConfig, parseDid, X509Service, + Hasher, + JwsService, + JwtPayload, } from '@credo-ts/core' import express, { type Express } from 'express' @@ -34,10 +46,10 @@ import { OpenId4VcHolderModule, OpenId4VcIssuanceSessionState, OpenId4VcIssuerModule, - OpenId4VcVerificationSessionRepository, OpenId4VcVerificationSessionState, OpenId4VcVerifierModule, } from '../src' +import { getOid4vciCallbacks } from '../src/shared/callbacks' import { waitForVerificationSessionRecordSubject, @@ -48,7 +60,6 @@ import { import { universityDegreeCredentialConfigurationSupported, universityDegreeCredentialConfigurationSupportedMdoc, - universityDegreeCredentialSdJwt, universityDegreeCredentialSdJwt2, } from './utilsVci' import { openBadgePresentationDefinition, universityDegreePresentationDefinition } from './utilsVp' @@ -92,57 +103,59 @@ describe('OpenId4Vc', () => { x509: new X509Module(), openId4VcIssuer: new OpenId4VcIssuerModule({ baseUrl: issuanceBaseUrl, - endpoints: { - credential: { - credentialRequestToCredentialMapper: async ({ agentContext, credentialRequest, holderBinding }) => { - // We sign the request with the first did:key did we have - const didsApi = agentContext.dependencyManager.resolve(DidsApi) - const [firstDidKeyDid] = await didsApi.getCreatedDids({ method: 'key' }) - const didDocument = await didsApi.resolveDidDocument(firstDidKeyDid.did) - const verificationMethod = didDocument.verificationMethod?.[0] - if (!verificationMethod) { - throw new Error('No verification method found') - } - - if (credentialRequest.format === 'vc+sd-jwt') { - return { - credentialSupportedId: - credentialRequest.vct === 'UniversityDegreeCredential' - ? universityDegreeCredentialSdJwt.id - : universityDegreeCredentialSdJwt2.id, - format: credentialRequest.format, - payload: { vct: credentialRequest.vct, university: 'innsbruck', degree: 'bachelor' }, - holder: holderBinding, - issuer: { - method: 'did', - didUrl: verificationMethod.id, - }, - disclosureFrame: { _sd: ['university', 'degree'] }, - } - } else if (credentialRequest.format === 'mso_mdoc') { - const trustedCertificates = - agentContext.dependencyManager.resolve(X509ModuleConfig).trustedCertificates - if (trustedCertificates?.length !== 1) { - throw new Error('Expected exactly one trusted certificate. Received 0.') - } - - return { - credentialSupportedId: '', - format: ClaimFormat.MsoMdoc, - docType: universityDegreeCredentialConfigurationSupportedMdoc.doctype, - issuerCertificate: trustedCertificates[0], - holderKey: holderBinding.key, - namespaces: { - 'Leopold-Franzens-University': { - degree: 'bachelor', - }, + credentialRequestToCredentialMapper: async ({ + agentContext, + credentialRequest, + holderBindings, + credentialConfigurationIds, + }) => { + // We sign the request with the first did:key did we have + const didsApi = agentContext.dependencyManager.resolve(DidsApi) + const [firstDidKeyDid] = await didsApi.getCreatedDids({ method: 'key' }) + const didDocument = await didsApi.resolveDidDocument(firstDidKeyDid.did) + const verificationMethod = didDocument.verificationMethod?.[0] + if (!verificationMethod) { + throw new Error('No verification method found') + } + const credentialConfigurationId = credentialConfigurationIds[0] + + if (credentialRequest.format === 'vc+sd-jwt') { + return { + credentialConfigurationId, + format: credentialRequest.format, + credentials: holderBindings.map((holderBinding) => ({ + payload: { vct: credentialRequest.vct, university: 'innsbruck', degree: 'bachelor' }, + holder: holderBinding, + issuer: { + method: 'did', + didUrl: verificationMethod.id, + }, + disclosureFrame: { _sd: ['university', 'degree'] }, + })), + } + } else if (credentialRequest.format === 'mso_mdoc') { + const trustedCertificates = agentContext.dependencyManager.resolve(X509ModuleConfig).trustedCertificates + if (trustedCertificates?.length !== 1) { + throw new Error('Expected exactly one trusted certificate. Received 0.') + } + + return { + credentialConfigurationId, + format: ClaimFormat.MsoMdoc, + credentials: holderBindings.map((holderBinding) => ({ + docType: universityDegreeCredentialConfigurationSupportedMdoc.doctype, + issuerCertificate: trustedCertificates[0], + holderKey: holderBinding.key, + namespaces: { + 'Leopold-Franzens-University': { + degree: 'bachelor', }, - } satisfies OpenId4VciSignMdocCredential - } else { - throw new Error('Invalid request') - } - }, - }, + }, + })), + } satisfies OpenId4VciSignMdocCredentials + } else { + throw new Error('Invalid request') + } }, }), askar: new AskarModule(askarModuleConfig), @@ -194,6 +207,9 @@ describe('OpenId4Vc', () => { await holder.agent.shutdown() await holder.agent.wallet.delete() + + await verifier.agent.shutdown() + await verifier.agent.wallet.delete() }) const credentialBindingResolver: OpenId4VciCredentialBindingResolver = ({ supportsJwk, supportedDidMethods }) => { @@ -229,23 +245,13 @@ describe('OpenId4Vc', () => { }) const issuer1Record = await issuerTenant1.modules.openId4VcIssuer.getIssuerByIssuerId(openIdIssuerTenant1.issuerId) expect(issuer1Record.dpopSigningAlgValuesSupported).toEqual(['EdDSA']) - - expect(issuer1Record.credentialsSupported).toEqual([ - { - id: 'universityDegree', - format: 'vc+sd-jwt', - cryptographic_binding_methods_supported: ['did:key'], - vct: universityDegreeCredentialConfigurationSupported.vct, - scope: universityDegreeCredentialConfigurationSupported.scope, - }, - ]) expect(issuer1Record.credentialConfigurationsSupported).toEqual({ universityDegree: { format: 'vc+sd-jwt', - cryptographic_binding_methods_supported: ['did:key'], + cryptographic_binding_methods_supported: ['did:key', 'jwk'], proof_types_supported: { jwt: { - proof_signing_alg_values_supported: ['EdDSA'], + proof_signing_alg_values_supported: ['EdDSA', 'ES256'], }, }, vct: universityDegreeCredentialConfigurationSupported.vct, @@ -254,14 +260,21 @@ describe('OpenId4Vc', () => { }) const openIdIssuerTenant2 = await issuerTenant2.modules.openId4VcIssuer.createIssuer({ dpopSigningAlgValuesSupported: [JwaSignatureAlgorithm.EdDSA], - credentialsSupported: [universityDegreeCredentialSdJwt2], + credentialConfigurationsSupported: { + [universityDegreeCredentialSdJwt2.id]: universityDegreeCredentialSdJwt2, + }, }) const { issuanceSession: issuanceSession1, credentialOffer: credentialOffer1 } = await issuerTenant1.modules.openId4VcIssuer.createCredentialOffer({ issuerId: openIdIssuerTenant1.issuerId, offeredCredentials: ['universityDegree'], - preAuthorizedCodeFlowConfig: {}, // { txCode: { input_mode: 'numeric', length: 4 } }, // TODO: disable due to sphereon limitations + preAuthorizedCodeFlowConfig: { + txCode: { + input_mode: 'numeric', + length: 4, + }, + }, version: 'v1.draft13', }) @@ -269,11 +282,12 @@ describe('OpenId4Vc', () => { await issuerTenant2.modules.openId4VcIssuer.createCredentialOffer({ issuerId: openIdIssuerTenant2.issuerId, offeredCredentials: [universityDegreeCredentialSdJwt2.id], - preAuthorizedCodeFlowConfig: {}, // { userPinRequired: true }, + preAuthorizedCodeFlowConfig: { + txCode: {}, + }, version: 'v1.draft11-13', }) - await issuerTenant1.endSession() await issuerTenant2.endSession() await waitForCredentialIssuanceSessionRecordSubject(issuer.replaySubject, { @@ -293,38 +307,61 @@ describe('OpenId4Vc', () => { credentialOffer1 ) - expect(resolvedCredentialOffer1.metadata.credentialIssuerMetadata?.dpop_signing_alg_values_supported).toEqual([ - 'EdDSA', - ]) - expect(resolvedCredentialOffer1.offeredCredentials).toEqual([ - { - id: 'universityDegree', + expect(resolvedCredentialOffer1.metadata.credentialIssuer?.dpop_signing_alg_values_supported).toEqual(['EdDSA']) + expect(resolvedCredentialOffer1.offeredCredentialConfigurations).toEqual({ + universityDegree: { format: 'vc+sd-jwt', - cryptographic_binding_methods_supported: ['did:key'], + cryptographic_binding_methods_supported: ['did:key', 'jwk'], + proof_types_supported: { + jwt: { + proof_signing_alg_values_supported: ['EdDSA', 'ES256'], + }, + }, vct: universityDegreeCredentialConfigurationSupported.vct, scope: universityDegreeCredentialConfigurationSupported.scope, }, - ]) + }) - expect(resolvedCredentialOffer1.credentialOfferRequestWithBaseUrl.credential_offer.credential_issuer).toEqual( + expect(resolvedCredentialOffer1.credentialOfferPayload.credential_issuer).toEqual( `${issuanceBaseUrl}/${openIdIssuerTenant1.issuerId}` ) - expect(resolvedCredentialOffer1.metadata.credentialIssuerMetadata?.token_endpoint).toEqual( + expect(resolvedCredentialOffer1.metadata.credentialIssuer?.token_endpoint).toEqual( `${issuanceBaseUrl}/${openIdIssuerTenant1.issuerId}/token` ) - expect(resolvedCredentialOffer1.metadata.credentialIssuerMetadata?.credential_endpoint).toEqual( + expect(resolvedCredentialOffer1.metadata.credentialIssuer?.credential_endpoint).toEqual( `${issuanceBaseUrl}/${openIdIssuerTenant1.issuerId}/credential` ) // Bind to JWK const tokenResponseTenant1 = await holderTenant1.modules.openId4VcHolder.requestToken({ resolvedCredentialOffer: resolvedCredentialOffer1, + txCode: issuanceSession1.userPin, }) + const expectedSubject = (await issuerTenant1.modules.openId4VcIssuer.getIssuanceSessionById(issuanceSession1.id)) + .authorization?.subject + await issuerTenant1.endSession() + expect(tokenResponseTenant1.accessToken).toBeDefined() expect(tokenResponseTenant1.dpop?.jwk).toBeInstanceOf(Jwk) const { payload } = Jwt.fromSerializedJwt(tokenResponseTenant1.accessToken) - expect(payload.additionalClaims.token_type).toEqual('DPoP') + expect(payload.toJson()).toEqual({ + cnf: { + jkt: await calculateJwkThumbprint({ + hashAlgorithm: HashAlgorithm.Sha256, + hashCallback: getOid4vciCallbacks(holderTenant1.context).hash, + jwk: tokenResponseTenant1.dpop?.jwk.toJson() as JwkJson, + }), + }, + 'pre-authorized_code': expect.any(String), + aud: `http://localhost:1234/oid4vci/${openIdIssuerTenant1.issuerId}`, + exp: expect.any(Number), + iat: expect.any(Number), + iss: `http://localhost:1234/oid4vci/${openIdIssuerTenant1.issuerId}`, + jti: expect.any(String), + nbf: undefined, + sub: expectedSubject, + }) const credentialsTenant1 = await holderTenant1.modules.openId4VcHolder.requestCredentials({ resolvedCredentialOffer: resolvedCredentialOffer1, @@ -354,8 +391,8 @@ describe('OpenId4Vc', () => { contextCorrelationId: issuer1.tenantId, }) - expect(credentialsTenant1).toHaveLength(1) - const compactSdJwtVcTenant1 = (credentialsTenant1[0].credential as SdJwtVc).compact + expect(credentialsTenant1.credentials).toHaveLength(1) + const compactSdJwtVcTenant1 = (credentialsTenant1.credentials[0].credentials[0] as SdJwtVc).compact const sdJwtVcTenant1 = holderTenant1.sdJwtVc.fromCompact(compactSdJwtVcTenant1) expect(sdJwtVcTenant1.payload.vct).toEqual('UniversityDegreeCredential') @@ -369,13 +406,13 @@ describe('OpenId4Vc', () => { contextCorrelationId: issuer2.tenantId, }) - expect(resolvedCredentialOffer2.credentialOfferRequestWithBaseUrl.credential_offer.credential_issuer).toEqual( + expect(resolvedCredentialOffer2.credentialOfferPayload.credential_issuer).toEqual( `${issuanceBaseUrl}/${openIdIssuerTenant2.issuerId}` ) - expect(resolvedCredentialOffer2.metadata.credentialIssuerMetadata?.token_endpoint).toEqual( + expect(resolvedCredentialOffer2.metadata.credentialIssuer?.token_endpoint).toEqual( `${issuanceBaseUrl}/${openIdIssuerTenant2.issuerId}/token` ) - expect(resolvedCredentialOffer2.metadata.credentialIssuerMetadata?.credential_endpoint).toEqual( + expect(resolvedCredentialOffer2.metadata.credentialIssuer?.credential_endpoint).toEqual( `${issuanceBaseUrl}/${openIdIssuerTenant2.issuerId}/credential` ) @@ -413,14 +450,150 @@ describe('OpenId4Vc', () => { contextCorrelationId: issuer2.tenantId, }) - expect(credentialsTenant2).toHaveLength(1) - const compactSdJwtVcTenant2 = (credentialsTenant2[0].credential as SdJwtVc).compact + expect(credentialsTenant2.credentials).toHaveLength(1) + const compactSdJwtVcTenant2 = (credentialsTenant2.credentials[0].credentials[0] as SdJwtVc).compact const sdJwtVcTenant2 = holderTenant1.sdJwtVc.fromCompact(compactSdJwtVcTenant2) expect(sdJwtVcTenant2.payload.vct).toEqual('UniversityDegreeCredential2') await holderTenant1.endSession() }) + it('e2e flow with tenants, issuer endpoints requesting a sd-jwt-vc using authorization code flow', async () => { + const issuerTenant = await issuer.agent.modules.tenants.getTenantAgent({ tenantId: issuer1.tenantId }) + const holderTenant = await holder.agent.modules.tenants.getTenantAgent({ tenantId: holder1.tenantId }) + + const authorizationServerKey = await issuer.agent.wallet.createKey({ + keyType: KeyType.P256, + }) + const authorizationServerJwk = getJwkFromKey(authorizationServerKey).toJson() + const authorizationServer = new Oauth2AuthorizationServer({ + callbacks: { + clientAuthentication: clientAuthenticationNone(), + generateRandom: issuer.agent.context.wallet.getRandomValues, + hash: Hasher.hash, + fetch: issuer.agent.config.agentDependencies.fetch, + verifyJwt: () => { + throw new Error('not implemented') + }, + signJwt: async (signer, { header, payload }) => { + const jwsService = issuer.agent.dependencyManager.resolve(JwsService) + return jwsService.createJwsCompact(issuer.agent.context, { + key: authorizationServerKey, + payload: JwtPayload.fromJson(payload), + protectedHeaderOptions: { + ...header, + jwk: undefined, + alg: 'ES256', + kid: 'first', + }, + }) + }, + }, + }) + const app = express() + app.get('/.well-known/oauth-authorization-server', (req, res) => + res.json({ + jwks_uri: 'http://localhost:4747/jwks.json', + issuer: 'http://localhost:4747', + token_endpoint: 'http://localhost:4747/token', + authorization_endpoint: 'http://localhost:4747/authorize', + } satisfies AuthorizationServerMetadata) + ) + app.get('/jwks.json', (req, res) => + res.setHeader('Content-Type', 'application/jwk-set+json').send( + JSON.stringify({ + keys: [{ ...authorizationServerJwk, kid: 'first' }], + }) + ) + ) + app.post('/token', async (req, res) => + res.json( + await authorizationServer.createAccessTokenResponse({ + authorizationServer: 'http://localhost:4747', + audience: 'http://localhost:1234/oid4vci/8bc91672-6a32-466c-96ec-6efca8760068', + expiresInSeconds: 5000, + subject: 'something', + scope: 'UniversityDegreeCredential', + additionalAccessTokenPayload: { + issuer_state: 'dbf99eea-0131-48b0-9022-17f7ebe25ea7', + }, + signer: { + method: 'jwk', + publicJwk: authorizationServerJwk, + alg: 'ES256', + }, + }) + ) + ) + const server = app.listen(4747) + + const openIdIssuerTenant = await issuerTenant.modules.openId4VcIssuer.createIssuer({ + issuerId: '8bc91672-6a32-466c-96ec-6efca8760068', + credentialConfigurationsSupported: { + universityDegree: universityDegreeCredentialConfigurationSupported, + }, + authorizationServerConfigs: [ + { + issuer: 'http://localhost:4747', + }, + ], + }) + + const { issuanceSession, credentialOffer } = await issuerTenant.modules.openId4VcIssuer.createCredentialOffer({ + issuerId: openIdIssuerTenant.issuerId, + offeredCredentials: ['universityDegree'], + authorizationCodeFlowConfig: { + authorizationServerUrl: 'http://localhost:4747', + issuerState: 'dbf99eea-0131-48b0-9022-17f7ebe25ea7', + }, + version: 'v1.draft13', + }) + + await issuerTenant.endSession() + + const resolvedCredentialOffer = await holderTenant.modules.openId4VcHolder.resolveCredentialOffer(credentialOffer) + const resolvedAuthorization = await holderTenant.modules.openId4VcHolder.resolveIssuanceAuthorizationRequest( + resolvedCredentialOffer, + { + clientId: 'foo', + redirectUri: 'http://localhost:1234/redirect', + scope: ['UniversityDegreeCredential'], + } + ) + if (resolvedAuthorization.authorizationFlow === AuthorizationFlow.PresentationDuringIssuance) { + throw new Error('Not supported') + } + + // Bind to JWK + const tokenResponseTenant = await holderTenant.modules.openId4VcHolder.requestToken({ + resolvedCredentialOffer, + // Mock the authorization code flow part, + code: 'some-authorization-code', + clientId: 'foo', + redirectUri: 'http://localhost:1234/redirect', + codeVerifier: resolvedAuthorization.codeVerifier, + }) + const credentialResponse = await holderTenant.modules.openId4VcHolder.requestCredentials({ + resolvedCredentialOffer, + ...tokenResponseTenant, + credentialBindingResolver, + }) + + await waitForCredentialIssuanceSessionRecordSubject(issuer.replaySubject, { + state: OpenId4VcIssuanceSessionState.Completed, + issuanceSessionId: issuanceSession.id, + contextCorrelationId: issuer1.tenantId, + }) + + expect(credentialResponse.credentials).toHaveLength(1) + const compactSdJwtVcTenant1 = (credentialResponse.credentials[0].credentials[0] as SdJwtVc).compact + const sdJwtVcTenant1 = holderTenant.sdJwtVc.fromCompact(compactSdJwtVcTenant1) + expect(sdJwtVcTenant1.payload.vct).toEqual('UniversityDegreeCredential') + + await holderTenant.endSession() + server.close() + }) + it('e2e flow with tenants only requesting an id-token', async () => { const holderTenant = await holder.agent.modules.tenants.getTenantAgent({ tenantId: holder1.tenantId }) const verifierTenant1 = await verifier.agent.modules.tenants.getTenantAgent({ tenantId: verifier1.tenantId }) @@ -755,7 +928,7 @@ describe('OpenId4Vc', () => { const certificate = await verifier.agent.x509.createSelfSignedCertificate({ key: await verifier.agent.wallet.createKey({ keyType: KeyType.Ed25519 }), - extensions: [[{ type: 'dns', value: 'localhost:1234' }]], + extensions: [[{ type: 'dns', value: `localhost:${serverPort}` }]], }) const rawCertificate = certificate.toString('base64') @@ -793,12 +966,6 @@ describe('OpenId4Vc', () => { ], } satisfies DifPresentationExchangeDefinitionV2 - // Hack to make it work with x5c check - // @ts-expect-error - verifier.agent.modules.openId4VcVerifier.config.options.baseUrl = - // @ts-expect-error - verifier.agent.modules.openId4VcVerifier.config.options.baseUrl.replace('http://', 'https://') - const { authorizationRequest, verificationSession } = await verifier.agent.modules.openId4VcVerifier.createAuthorizationRequest({ verifierId: openIdVerifier.verifierId, @@ -813,28 +980,14 @@ describe('OpenId4Vc', () => { }, }) - // Hack to make it work with x5c checks - verificationSession.authorizationRequestUri = verificationSession.authorizationRequestUri.replace('https', 'http') - const verificationSessionRepoitory = verifier.agent.dependencyManager.resolve( - OpenId4VcVerificationSessionRepository - ) - await verificationSessionRepoitory.update(verifier.agent.context, verificationSession) - - // Hack to make it work with x5c check - // @ts-expect-error - verifier.agent.modules.openId4VcVerifier.config.options.baseUrl = - // @ts-expect-error - verifier.agent.modules.openId4VcVerifier.config.options.baseUrl.replace('https://', 'http://') - - expect(authorizationRequest.replace('https', 'http')).toEqual( + expect(authorizationRequest).toEqual( `openid4vp://?client_id=localhost%3A1234&request_uri=${encodeURIComponent( verificationSession.authorizationRequestUri )}` ) const resolvedAuthorizationRequest = await holder.agent.modules.openId4VcHolder.resolveSiopAuthorizationRequest( - // hack to make it work on localhost - authorizationRequest.replace('https', 'http') + authorizationRequest ) expect(resolvedAuthorizationRequest.authorizationRequest.payload?.response_mode).toEqual('direct_post.jwt') @@ -881,16 +1034,10 @@ describe('OpenId4Vc', () => { throw new Error('Presentation exchange not defined') } - // TODO: better way to auto-select - const presentationExchangeService = holder.agent.dependencyManager.resolve(DifPresentationExchangeService) - const selectedCredentials = presentationExchangeService.selectCredentialsForRequest( + const selectedCredentials = holder.agent.modules.openId4VcHolder.selectCredentialsForRequest( resolvedAuthorizationRequest.presentationExchange.credentialsForRequest ) - // Hack to make it work with x5c - resolvedAuthorizationRequest.authorizationRequest.responseURI = - resolvedAuthorizationRequest.authorizationRequest.responseURI?.replace('https', 'http') - const { serverResponse, submittedResponse } = await holder.agent.modules.openId4VcHolder.acceptSiopAuthorizationRequest({ authorizationRequest: resolvedAuthorizationRequest.authorizationRequest, @@ -987,6 +1134,7 @@ describe('OpenId4Vc', () => { }, }, ], + descriptors: expect.any(Array), }) }) @@ -1012,7 +1160,7 @@ describe('OpenId4Vc', () => { const certificate = await verifier.agent.x509.createSelfSignedCertificate({ key: await verifier.agent.wallet.createKey({ keyType: KeyType.Ed25519 }), - extensions: [[{ type: 'dns', value: 'localhost:1234' }]], + extensions: [[{ type: 'dns', value: `localhost:${serverPort}` }]], }) const rawCertificate = certificate.toString('base64') @@ -1050,12 +1198,6 @@ describe('OpenId4Vc', () => { ], } satisfies DifPresentationExchangeDefinitionV2 - // Hack to make it work with x5c check - // @ts-expect-error - verifier.agent.modules.openId4VcVerifier.config.options.baseUrl = - // @ts-expect-error - verifier.agent.modules.openId4VcVerifier.config.options.baseUrl.replace('http://', 'https://') - const { authorizationRequest, verificationSession } = await verifier.agent.modules.openId4VcVerifier.createAuthorizationRequest({ verifierId: openIdVerifier.verifierId, @@ -1069,28 +1211,14 @@ describe('OpenId4Vc', () => { }, }) - // Hack to make it work with x5c checks - verificationSession.authorizationRequestUri = verificationSession.authorizationRequestUri.replace('https', 'http') - const verificationSessionRepoitory = verifier.agent.dependencyManager.resolve( - OpenId4VcVerificationSessionRepository - ) - await verificationSessionRepoitory.update(verifier.agent.context, verificationSession) - - // Hack to make it work with x5c check - // @ts-expect-error - verifier.agent.modules.openId4VcVerifier.config.options.baseUrl = - // @ts-expect-error - verifier.agent.modules.openId4VcVerifier.config.options.baseUrl.replace('https://', 'http://') - - expect(authorizationRequest.replace('https', 'http')).toEqual( + expect(authorizationRequest).toEqual( `openid4vp://?client_id=localhost%3A1234&request_uri=${encodeURIComponent( verificationSession.authorizationRequestUri )}` ) const resolvedAuthorizationRequest = await holder.agent.modules.openId4VcHolder.resolveSiopAuthorizationRequest( - // hack to make it work on localhost - authorizationRequest.replace('https', 'http') + authorizationRequest ) expect(resolvedAuthorizationRequest.presentationExchange?.credentialsForRequest).toEqual({ @@ -1136,16 +1264,10 @@ describe('OpenId4Vc', () => { throw new Error('Presentation exchange not defined') } - // TODO: better way to auto-select - const presentationExchangeService = holder.agent.dependencyManager.resolve(DifPresentationExchangeService) - const selectedCredentials = presentationExchangeService.selectCredentialsForRequest( + const selectedCredentials = holder.agent.modules.openId4VcHolder.selectCredentialsForRequest( resolvedAuthorizationRequest.presentationExchange.credentialsForRequest ) - // Hack to make it work with x5c - resolvedAuthorizationRequest.authorizationRequest.responseURI = - resolvedAuthorizationRequest.authorizationRequest.responseURI?.replace('https', 'http') - const { serverResponse, submittedResponse } = await holder.agent.modules.openId4VcHolder.acceptSiopAuthorizationRequest({ authorizationRequest: resolvedAuthorizationRequest.authorizationRequest, @@ -1242,6 +1364,7 @@ describe('OpenId4Vc', () => { }, }, ], + descriptors: expect.any(Array), }) }) @@ -1284,7 +1407,7 @@ describe('OpenId4Vc', () => { const certificate = await verifier.agent.x509.createSelfSignedCertificate({ key: await verifier.agent.wallet.createKey({ keyType: KeyType.Ed25519 }), - extensions: [[{ type: 'dns', value: 'localhost:1234' }]], + extensions: [[{ type: 'dns', value: `localhost:${serverPort}` }]], }) const rawCertificate = certificate.toString('base64') @@ -1346,11 +1469,6 @@ describe('OpenId4Vc', () => { ], } satisfies DifPresentationExchangeDefinitionV2 - // Hack to make it work with x5c check - // @ts-expect-error - verifier.agent.modules.openId4VcVerifier.config.options.baseUrl = - // @ts-expect-error - verifier.agent.modules.openId4VcVerifier.config.options.baseUrl.replace('http://', 'https://') const { authorizationRequest, verificationSession } = await verifier.agent.modules.openId4VcVerifier.createAuthorizationRequest({ verifierId: openIdVerifier.verifierId, @@ -1364,28 +1482,14 @@ describe('OpenId4Vc', () => { }, }) - // Hack to make it work with x5c checks - verificationSession.authorizationRequestUri = verificationSession.authorizationRequestUri.replace('https', 'http') - const verificationSessionRepoitory = verifier.agent.dependencyManager.resolve( - OpenId4VcVerificationSessionRepository - ) - await verificationSessionRepoitory.update(verifier.agent.context, verificationSession) - - // Hack to make it work with x5c check - // @ts-expect-error - verifier.agent.modules.openId4VcVerifier.config.options.baseUrl = - // @ts-expect-error - verifier.agent.modules.openId4VcVerifier.config.options.baseUrl.replace('https://', 'http://') - - expect(authorizationRequest.replace('https', 'http')).toEqual( + expect(authorizationRequest).toEqual( `openid4vp://?client_id=localhost%3A1234&request_uri=${encodeURIComponent( verificationSession.authorizationRequestUri )}` ) const resolvedAuthorizationRequest = await holder.agent.modules.openId4VcHolder.resolveSiopAuthorizationRequest( - // hack to make it work on localhost - authorizationRequest.replace('https', 'http') + authorizationRequest ) expect(resolvedAuthorizationRequest.presentationExchange?.credentialsForRequest).toEqual({ @@ -1461,16 +1565,10 @@ describe('OpenId4Vc', () => { throw new Error('Presentation exchange not defined') } - // TODO: better way to auto-select - const presentationExchangeService = holder.agent.dependencyManager.resolve(DifPresentationExchangeService) - const selectedCredentials = presentationExchangeService.selectCredentialsForRequest( + const selectedCredentials = holder.agent.modules.openId4VcHolder.selectCredentialsForRequest( resolvedAuthorizationRequest.presentationExchange.credentialsForRequest ) - // Hack to make it work with x5c - resolvedAuthorizationRequest.authorizationRequest.responseURI = - resolvedAuthorizationRequest.authorizationRequest.responseURI?.replace('https', 'http') - const { serverResponse, submittedResponse } = await holder.agent.modules.openId4VcHolder.acceptSiopAuthorizationRequest({ authorizationRequest: resolvedAuthorizationRequest.authorizationRequest, @@ -1606,6 +1704,7 @@ describe('OpenId4Vc', () => { }, }, ], + descriptors: expect.any(Array), }) }) @@ -1629,22 +1728,13 @@ describe('OpenId4Vc', () => { const issuer1Record = await issuerTenant1.modules.openId4VcIssuer.getIssuerByIssuerId(openIdIssuerTenant1.issuerId) expect(issuer1Record.dpopSigningAlgValuesSupported).toEqual(['ES256']) - expect(issuer1Record.credentialsSupported).toEqual([ - { - id: 'universityDegree', - format: 'mso_mdoc', - cryptographic_binding_methods_supported: ['did:key'], - doctype: universityDegreeCredentialConfigurationSupportedMdoc.doctype, - scope: 'UniversityDegreeCredential', - }, - ]) expect(issuer1Record.credentialConfigurationsSupported).toEqual({ universityDegree: { format: 'mso_mdoc', - cryptographic_binding_methods_supported: ['did:key'], + cryptographic_binding_methods_supported: ['did:key', 'jwk'], proof_types_supported: { jwt: { - proof_signing_alg_values_supported: ['ES256'], + proof_signing_alg_values_supported: ['ES256', 'EdDSA'], }, }, doctype: universityDegreeCredentialConfigurationSupportedMdoc.doctype, @@ -1656,7 +1746,7 @@ describe('OpenId4Vc', () => { await issuerTenant1.modules.openId4VcIssuer.createCredentialOffer({ issuerId: openIdIssuerTenant1.issuerId, offeredCredentials: ['universityDegree'], - preAuthorizedCodeFlowConfig: {}, // { txCode: { input_mode: 'numeric', length: 4 } }, // TODO: disable due to sphereon limitations + preAuthorizedCodeFlowConfig: {}, version: 'v1.draft13', }) @@ -1675,26 +1765,28 @@ describe('OpenId4Vc', () => { credentialOffer1 ) - expect(resolvedCredentialOffer1.metadata.credentialIssuerMetadata?.dpop_signing_alg_values_supported).toEqual([ - 'ES256', - ]) - expect(resolvedCredentialOffer1.offeredCredentials).toEqual([ - { - id: 'universityDegree', + expect(resolvedCredentialOffer1.metadata.credentialIssuer?.dpop_signing_alg_values_supported).toEqual(['ES256']) + expect(resolvedCredentialOffer1.offeredCredentialConfigurations).toEqual({ + universityDegree: { doctype: 'UniversityDegreeCredential', - cryptographic_binding_methods_supported: ['did:key'], + cryptographic_binding_methods_supported: ['did:key', 'jwk'], format: 'mso_mdoc', scope: universityDegreeCredentialConfigurationSupportedMdoc.scope, + proof_types_supported: { + jwt: { + proof_signing_alg_values_supported: ['ES256', 'EdDSA'], + }, + }, }, - ]) + }) - expect(resolvedCredentialOffer1.credentialOfferRequestWithBaseUrl.credential_offer.credential_issuer).toEqual( + expect(resolvedCredentialOffer1.credentialOfferPayload.credential_issuer).toEqual( `${issuanceBaseUrl}/${openIdIssuerTenant1.issuerId}` ) - expect(resolvedCredentialOffer1.metadata.credentialIssuerMetadata?.token_endpoint).toEqual( + expect(resolvedCredentialOffer1.metadata.credentialIssuer?.token_endpoint).toEqual( `${issuanceBaseUrl}/${openIdIssuerTenant1.issuerId}/token` ) - expect(resolvedCredentialOffer1.metadata.credentialIssuerMetadata?.credential_endpoint).toEqual( + expect(resolvedCredentialOffer1.metadata.credentialIssuer?.credential_endpoint).toEqual( `${issuanceBaseUrl}/${openIdIssuerTenant1.issuerId}/credential` ) @@ -1706,7 +1798,28 @@ describe('OpenId4Vc', () => { expect(tokenResponseTenant1.accessToken).toBeDefined() expect(tokenResponseTenant1.dpop?.jwk).toBeInstanceOf(Jwk) const { payload } = Jwt.fromSerializedJwt(tokenResponseTenant1.accessToken) - expect(payload.additionalClaims.token_type).toEqual('DPoP') + + expect(payload.toJson()).toEqual({ + cnf: { + jkt: await calculateJwkThumbprint({ + hashAlgorithm: HashAlgorithm.Sha256, + hashCallback: getOid4vciCallbacks(holderTenant1.context).hash, + jwk: tokenResponseTenant1.dpop?.jwk.toJson() as JwkJson, + }), + }, + 'pre-authorized_code': + resolvedCredentialOffer1.credentialOfferPayload.grants?.[preAuthorizedCodeGrantIdentifier]?.[ + 'pre-authorized_code' + ], + + aud: `http://localhost:1234/oid4vci/${openIdIssuerTenant1.issuerId}`, + exp: expect.any(Number), + iat: expect.any(Number), + iss: `http://localhost:1234/oid4vci/${openIdIssuerTenant1.issuerId}`, + jti: expect.any(String), + nbf: undefined, + sub: expect.stringContaining('credo:'), + }) const credentialsTenant1 = await holderTenant1.modules.openId4VcHolder.requestCredentials({ resolvedCredentialOffer: resolvedCredentialOffer1, @@ -1736,8 +1849,8 @@ describe('OpenId4Vc', () => { contextCorrelationId: issuer1.tenantId, }) - expect(credentialsTenant1).toHaveLength(1) - const mdocBase64Url = (credentialsTenant1[0].credential as Mdoc).base64Url + expect(credentialsTenant1.credentials).toHaveLength(1) + const mdocBase64Url = (credentialsTenant1.credentials[0].credentials[0] as Mdoc).base64Url const mdoc = holderTenant1.mdoc.fromBase64Url(mdocBase64Url) expect(mdoc.docType).toEqual('UniversityDegreeCredential') @@ -1855,12 +1968,6 @@ describe('OpenId4Vc', () => { ], } satisfies DifPresentationExchangeDefinitionV2 - // Hack to make it work with x5c check - // @ts-expect-error - verifier.agent.modules.openId4VcVerifier.config.options.baseUrl = - // @ts-expect-error - verifier.agent.modules.openId4VcVerifier.config.options.baseUrl.replace('http://', 'https://') - const { authorizationRequest, verificationSession } = await verifier.agent.modules.openId4VcVerifier.createAuthorizationRequest({ responseMode: 'direct_post.jwt', @@ -1875,28 +1982,14 @@ describe('OpenId4Vc', () => { }, }) - // Hack to make it work with x5c checks - verificationSession.authorizationRequestUri = verificationSession.authorizationRequestUri.replace('https', 'http') - const verificationSessionRepoitory = verifier.agent.dependencyManager.resolve( - OpenId4VcVerificationSessionRepository - ) - await verificationSessionRepoitory.update(verifier.agent.context, verificationSession) - - // Hack to make it work with x5c check - // @ts-expect-error - verifier.agent.modules.openId4VcVerifier.config.options.baseUrl = - // @ts-expect-error - verifier.agent.modules.openId4VcVerifier.config.options.baseUrl.replace('https://', 'http://') - - expect(authorizationRequest.replace('https', 'http')).toEqual( + expect(authorizationRequest).toEqual( `openid4vp://?client_id=localhost%3A1234&request_uri=${encodeURIComponent( verificationSession.authorizationRequestUri )}` ) const resolvedAuthorizationRequest = await holder.agent.modules.openId4VcHolder.resolveSiopAuthorizationRequest( - // hack to make it work on localhost - authorizationRequest.replace('https', 'http') + authorizationRequest ) expect(resolvedAuthorizationRequest.presentationExchange?.credentialsForRequest).toEqual({ @@ -1970,16 +2063,10 @@ describe('OpenId4Vc', () => { throw new Error('Presentation exchange not defined') } - // TODO: better way to auto-select - const presentationExchangeService = holder.agent.dependencyManager.resolve(DifPresentationExchangeService) - const selectedCredentials = presentationExchangeService.selectCredentialsForRequest( + const selectedCredentials = holder.agent.modules.openId4VcHolder.selectCredentialsForRequest( resolvedAuthorizationRequest.presentationExchange.credentialsForRequest ) - // Hack to make it work with x5c - resolvedAuthorizationRequest.authorizationRequest.responseURI = - resolvedAuthorizationRequest.authorizationRequest.responseURI?.replace('https', 'http') - const { serverResponse, submittedResponse } = await holder.agent.modules.openId4VcHolder.acceptSiopAuthorizationRequest({ authorizationRequest: resolvedAuthorizationRequest.authorizationRequest, @@ -2108,6 +2195,7 @@ describe('OpenId4Vc', () => { }, }, ], + descriptors: expect.any(Array), }) }) @@ -2212,11 +2300,6 @@ describe('OpenId4Vc', () => { ], } satisfies DifPresentationExchangeDefinitionV2 - // Hack to make it work with x5c check - // @ts-expect-error - verifier.agent.modules.openId4VcVerifier.config.options.baseUrl = - // @ts-expect-error - verifier.agent.modules.openId4VcVerifier.config.options.baseUrl.replace('http://', 'https://') const { authorizationRequest, verificationSession } = await verifier.agent.modules.openId4VcVerifier.createAuthorizationRequest({ verifierId: openIdVerifier.verifierId, @@ -2230,28 +2313,14 @@ describe('OpenId4Vc', () => { }, }) - // Hack to make it work with x5c checks - verificationSession.authorizationRequestUri = verificationSession.authorizationRequestUri.replace('https', 'http') - const verificationSessionRepoitory = verifier.agent.dependencyManager.resolve( - OpenId4VcVerificationSessionRepository - ) - await verificationSessionRepoitory.update(verifier.agent.context, verificationSession) - - // Hack to make it work with x5c check - // @ts-expect-error - verifier.agent.modules.openId4VcVerifier.config.options.baseUrl = - // @ts-expect-error - verifier.agent.modules.openId4VcVerifier.config.options.baseUrl.replace('https://', 'http://') - - expect(authorizationRequest.replace('https', 'http')).toEqual( + expect(authorizationRequest).toEqual( `openid4vp://?client_id=localhost%3A1234&request_uri=${encodeURIComponent( verificationSession.authorizationRequestUri )}` ) const resolvedAuthorizationRequest = await holder.agent.modules.openId4VcHolder.resolveSiopAuthorizationRequest( - // hack to make it work on localhost - authorizationRequest.replace('https', 'http') + authorizationRequest ) expect(resolvedAuthorizationRequest.presentationExchange?.credentialsForRequest).toEqual({ @@ -2327,16 +2396,10 @@ describe('OpenId4Vc', () => { throw new Error('Presentation exchange not defined') } - // TODO: better way to auto-select - const presentationExchangeService = holder.agent.dependencyManager.resolve(DifPresentationExchangeService) - const selectedCredentials = presentationExchangeService.selectCredentialsForRequest( + const selectedCredentials = holder.agent.modules.openId4VcHolder.selectCredentialsForRequest( resolvedAuthorizationRequest.presentationExchange.credentialsForRequest ) - // Hack to make it work with x5c - resolvedAuthorizationRequest.authorizationRequest.responseURI = - resolvedAuthorizationRequest.authorizationRequest.responseURI?.replace('https', 'http') - const { serverResponse, submittedResponse } = await holder.agent.modules.openId4VcHolder.acceptSiopAuthorizationRequest({ authorizationRequest: resolvedAuthorizationRequest.authorizationRequest, @@ -2472,6 +2535,7 @@ describe('OpenId4Vc', () => { }, }, ], + descriptors: expect.any(Array), }) }) }) diff --git a/packages/openid4vc/tests/utils.ts b/packages/openid4vc/tests/utils.ts index d076e4b65c..f7faae37c9 100644 --- a/packages/openid4vc/tests/utils.ts +++ b/packages/openid4vc/tests/utils.ts @@ -8,26 +8,48 @@ import type { BaseEvent, ModulesMap, X509Module } from '@credo-ts/core' import type { TenantsModule } from '@credo-ts/tenants' import type { Observable } from 'rxjs' -import { Agent, LogLevel, utils } from '@credo-ts/core' +import { Agent, getDomainFromUrl, getJwkFromKey, LogLevel, utils } from '@credo-ts/core' import { ReplaySubject, lastValueFrom, filter, timeout, catchError, take, map } from 'rxjs' import { TestLogger, agentDependencies, createDidKidVerificationMethod, + createX509Certificate, setupEventReplaySubjects, } from '../../core/tests' -import { OpenId4VcVerifierEvents, OpenId4VcIssuerEvents } from '../src' +import { OpenId4VcVerifierEvents, OpenId4VcIssuerEvents, OpenId4VcIssuerModule, OpenId4VcVerifierModule } from '../src' -export async function createAgentFromModules(label: string, modulesMap: MM, secretKey: string) { +export async function createAgentFromModules( + label: string, + modulesMap: MM, + secretKey?: string, + customFetch?: typeof global.fetch +) { const agent = new Agent({ - config: { label, walletConfig: { id: utils.uuid(), key: utils.uuid() }, logger: new TestLogger(LogLevel.off) }, - dependencies: agentDependencies, + config: { + label, + walletConfig: { id: utils.uuid(), key: utils.uuid() }, + allowInsecureHttpUrls: true, + logger: new TestLogger(LogLevel.off), + }, + dependencies: { + ...agentDependencies, + fetch: customFetch ?? agentDependencies.fetch, + }, modules: modulesMap, }) + let dns: string = 'localhost' + if (modulesMap.openId4VcIssuer instanceof OpenId4VcIssuerModule) { + dns = getDomainFromUrl(modulesMap.openId4VcIssuer.config.baseUrl) + } else if (modulesMap.openId4VcVerifier instanceof OpenId4VcVerifierModule) { + dns = getDomainFromUrl(modulesMap.openId4VcVerifier.config.baseUrl) + } + await agent.initialize() const data = await createDidKidVerificationMethod(agent.context, secretKey) + const certificate = await createX509Certificate(agent.context, dns, data.key) const [replaySubject] = setupEventReplaySubjects( [agent], @@ -36,6 +58,8 @@ export async function createAgentFromModules(label: strin return { ...data, + jwk: getJwkFromKey(data.key), + certificate: certificate.certificate, agent, replaySubject, } diff --git a/packages/openid4vc/tests/utilsVci.ts b/packages/openid4vc/tests/utilsVci.ts index be87dd96ad..a1ac804833 100644 --- a/packages/openid4vc/tests/utilsVci.ts +++ b/packages/openid4vc/tests/utilsVci.ts @@ -1,52 +1,58 @@ -import type { OpenId4VciCredentialConfigurationSupported, OpenId4VciCredentialSupportedWithId } from '../src' +import type { OpenId4VciCredentialConfigurationSupportedWithFormats } from '../src' import { OpenId4VciCredentialFormatProfile } from '../src' -export const openBadgeCredential: OpenId4VciCredentialSupportedWithId = { +export const openBadgeCredential = { id: `/credentials/OpenBadgeCredential`, format: OpenId4VciCredentialFormatProfile.JwtVcJson, - types: ['VerifiableCredential', 'OpenBadgeCredential'], -} + credential_definition: { + type: ['VerifiableCredential', 'OpenBadgeCredential'], + }, +} satisfies OpenId4VciCredentialConfigurationSupportedWithFormats -export const universityDegreeCredential: OpenId4VciCredentialSupportedWithId = { +export const universityDegreeCredential = { id: `/credentials/UniversityDegreeCredential`, format: OpenId4VciCredentialFormatProfile.JwtVcJson, - types: ['VerifiableCredential', 'UniversityDegreeCredential'], -} + credential_definition: { + type: ['VerifiableCredential', 'UniversityDegreeCredential'], + }, +} satisfies OpenId4VciCredentialConfigurationSupportedWithFormats -export const universityDegreeCredentialLd: OpenId4VciCredentialSupportedWithId = { +export const universityDegreeCredentialLd = { id: `/credentials/UniversityDegreeCredentialLd`, format: OpenId4VciCredentialFormatProfile.JwtVcJsonLd, - types: ['VerifiableCredential', 'UniversityDegreeCredential'], - '@context': ['context'], -} + credential_definition: { + type: ['VerifiableCredential', 'UniversityDegreeCredential'], + '@context': ['context'], + }, +} satisfies OpenId4VciCredentialConfigurationSupportedWithFormats export const universityDegreeCredentialSdJwt = { id: 'https://openid4vc-issuer.com/credentials/UniversityDegreeCredentialSdJwt', format: OpenId4VciCredentialFormatProfile.SdJwtVc, vct: 'UniversityDegreeCredential', cryptographic_binding_methods_supported: ['did:key'], -} satisfies OpenId4VciCredentialSupportedWithId +} satisfies OpenId4VciCredentialConfigurationSupportedWithFormats export const universityDegreeCredentialConfigurationSupported = { format: OpenId4VciCredentialFormatProfile.SdJwtVc, scope: 'UniversityDegreeCredential', vct: 'UniversityDegreeCredential', proof_types_supported: { - jwt: { proof_signing_alg_values_supported: ['EdDSA'] }, + jwt: { proof_signing_alg_values_supported: ['EdDSA', 'ES256'] }, }, - cryptographic_binding_methods_supported: ['did:key'], -} satisfies OpenId4VciCredentialConfigurationSupported + cryptographic_binding_methods_supported: ['did:key', 'jwk'], +} satisfies OpenId4VciCredentialConfigurationSupportedWithFormats export const universityDegreeCredentialConfigurationSupportedMdoc = { format: OpenId4VciCredentialFormatProfile.MsoMdoc, scope: 'UniversityDegreeCredential', doctype: 'UniversityDegreeCredential', proof_types_supported: { - jwt: { proof_signing_alg_values_supported: ['ES256'] }, + jwt: { proof_signing_alg_values_supported: ['ES256', 'EdDSA'] }, }, - cryptographic_binding_methods_supported: ['did:key'], -} satisfies OpenId4VciCredentialConfigurationSupported + cryptographic_binding_methods_supported: ['did:key', 'jwk'], +} satisfies OpenId4VciCredentialConfigurationSupportedWithFormats export const universityDegreeCredentialSdJwt2 = { id: 'https://openid4vc-issuer.com/credentials/UniversityDegreeCredentialSdJwt2', @@ -54,12 +60,4 @@ export const universityDegreeCredentialSdJwt2 = { vct: 'UniversityDegreeCredential2', // FIXME: should this be dynamically generated? I think static is fine for now cryptographic_binding_methods_supported: ['jwk'], -} satisfies OpenId4VciCredentialSupportedWithId - -export const allCredentialsSupported = [ - openBadgeCredential, - universityDegreeCredential, - universityDegreeCredentialLd, - universityDegreeCredentialSdJwt, - universityDegreeCredentialSdJwt2, -] +} satisfies OpenId4VciCredentialConfigurationSupportedWithFormats diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7e1bba4e21..4a16b3cc4e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,11 +6,18 @@ settings: overrides: '@types/node': 18.18.8 + undici: ^6.20.1 importers: .: devDependencies: + '@babel/core': + specifier: ^7.25.8 + version: 7.25.8 + '@babel/preset-env': + specifier: ^7.25.8 + version: 7.25.8(@babel/core@7.25.8) '@changesets/cli': specifier: ^2.27.5 version: 2.27.7 @@ -38,6 +45,9 @@ importers: '@types/node': specifier: 18.18.8 version: 18.18.8 + '@types/supertest': + specifier: ^6.0.2 + version: 6.0.2 '@types/uuid': specifier: ^9.0.1 version: 9.0.8 @@ -82,25 +92,34 @@ importers: version: 4.19.2 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@18.18.8)(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) + version: 29.7.0(@types/node@18.18.8)(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4)) + nock: + specifier: ^14.0.0-beta.15 + version: 14.0.0-beta.15 prettier: specifier: ^2.3.1 version: 2.8.8 rxjs: specifier: ^7.8.0 version: 7.8.1 + supertest: + specifier: ^7.0.0 + version: 7.0.0 ts-jest: specifier: ^29.1.2 - version: 29.2.4(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@18.18.8)(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)))(typescript@5.5.4) + version: 29.2.4(@babel/core@7.25.8)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.8))(jest@29.7.0(@types/node@18.18.8)(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4)))(typescript@5.5.4) ts-node: - specifier: ^10.0.0 - version: 10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4) + specifier: ^10.9.2 + version: 10.9.2(@types/node@18.18.8)(typescript@5.5.4) tsyringe: specifier: ^4.8.0 version: 4.8.0 typescript: specifier: ~5.5.2 version: 5.5.4 + undici: + specifier: ^6.20.1 + version: 6.20.1 ws: specifier: ^8.13.0 version: 8.18.0 @@ -151,8 +170,8 @@ importers: specifier: ^1.5.2 version: 1.7.0 ts-node: - specifier: ^10.4.0 - version: 10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4) + specifier: ^10.9.2 + version: 10.9.2(@types/node@18.18.8)(typescript@5.5.4) demo-openid: dependencies: @@ -165,12 +184,21 @@ importers: '@hyperledger/indy-vdr-nodejs': specifier: ^0.2.2 version: 0.2.2 + '@koa/bodyparser': + specifier: ^5.1.1 + version: 5.1.1(koa@2.15.3) express: specifier: ^4.18.1 version: 4.19.2 inquirer: specifier: ^8.2.5 version: 8.2.6 + jose: + specifier: ^5.3.0 + version: 5.8.0 + oidc-provider: + specifier: ^8.4.6 + version: 8.5.1 devDependencies: '@credo-ts/askar': specifier: workspace:* @@ -193,6 +221,9 @@ importers: '@types/inquirer': specifier: ^8.2.6 version: 8.2.10 + '@types/oidc-provider': + specifier: ^8.4.4 + version: 8.5.2 clear: specifier: ^0.1.0 version: 0.1.0 @@ -201,7 +232,10 @@ importers: version: 1.7.0 ts-node: specifier: ^10.9.2 - version: 10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4) + version: 10.9.2(@types/node@18.18.8)(typescript@5.5.4) + tsx: + specifier: ^4.11.0 + version: 4.19.0 packages/action-menu: dependencies: @@ -297,7 +331,7 @@ importers: devDependencies: '@animo-id/expo-secure-environment': specifier: ^0.0.1-alpha.0 - version: 0.0.1-alpha.0(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2)))(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))(react@18.3.1))(react@18.3.1) + version: 0.0.1-alpha.0(expo@51.0.29(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8)))(react-native@0.71.19(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(react@18.3.1))(react@18.3.1) '@hyperledger/aries-askar-nodejs': specifier: ^0.2.3 version: 0.2.3 @@ -327,7 +361,7 @@ importers: dependencies: '@animo-id/react-native-bbs-signatures': specifier: ^0.1.0 - version: 0.1.0(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))(react@18.3.1))(react@18.3.1) + version: 0.1.0(react-native@0.71.19(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(react@18.3.1))(react@18.3.1) '@credo-ts/core': specifier: workspace:* version: link:../core @@ -407,13 +441,13 @@ importers: version: 0.2.38 '@digitalcredentials/jsonld': specifier: ^6.0.0 - version: 6.0.0(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2)))(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))(react@18.3.1))(web-streams-polyfill@3.3.3) + version: 6.0.0(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2)))(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(react@18.3.1))(web-streams-polyfill@3.3.3) '@digitalcredentials/jsonld-signatures': specifier: ^9.4.0 - version: 9.4.0(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2)))(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))(react@18.3.1))(web-streams-polyfill@3.3.3) + version: 9.4.0(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2)))(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(react@18.3.1))(web-streams-polyfill@3.3.3) '@digitalcredentials/vc': specifier: ^6.0.1 - version: 6.0.1(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2)))(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))(react@18.3.1))(web-streams-polyfill@3.3.3) + version: 6.0.1(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2)))(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(react@18.3.1))(web-streams-polyfill@3.3.3) '@multiformats/base-x': specifier: ^4.0.1 version: 4.0.1 @@ -547,9 +581,6 @@ importers: '@types/varint': specifier: ^6.0.0 version: 6.0.3 - nock: - specifier: ^13.3.0 - version: 13.5.5 rimraf: specifier: ^4.4.0 version: 4.4.1 @@ -678,8 +709,8 @@ importers: specifier: ^8.5.4 version: 8.5.12 nock: - specifier: ^13.3.0 - version: 13.5.5 + specifier: ^14.0.0-beta.16 + version: 14.0.0-beta.16 rimraf: specifier: ^4.4.0 version: 4.4.1 @@ -689,24 +720,21 @@ importers: packages/openid4vc: dependencies: + '@animo-id/oauth2': + specifier: 0.1.3 + version: 0.1.3(typescript@5.5.4) + '@animo-id/oid4vci': + specifier: 0.1.3 + version: 0.1.3(typescript@5.5.4) '@credo-ts/core': specifier: workspace:* version: link:../core '@sphereon/did-auth-siop': specifier: 0.16.1-fix.173 - version: 0.16.1-fix.173(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4))(typescript@5.5.4) + version: 0.16.1-fix.173(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4))(typescript@5.5.4) '@sphereon/oid4vc-common': specifier: 0.16.1-fix.173 - version: 0.16.1-fix.173(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) - '@sphereon/oid4vci-client': - specifier: 0.16.1-fix.173 - version: 0.16.1-fix.173(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) - '@sphereon/oid4vci-common': - specifier: 0.16.1-fix.173 - version: 0.16.1-fix.173(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) - '@sphereon/oid4vci-issuer': - specifier: 0.16.1-fix.173 - version: 0.16.1-fix.173(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) + version: 0.16.1-fix.173(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4)) '@sphereon/ssi-types': specifier: 0.30.2-next.135 version: 0.30.2-next.135 @@ -716,6 +744,9 @@ importers: rxjs: specifier: ^7.8.0 version: 7.8.1 + zod: + specifier: ^3.23.8 + version: 3.23.8 devDependencies: '@credo-ts/tenants': specifier: workspace:* @@ -727,8 +758,8 @@ importers: specifier: ^4.18.2 version: 4.19.2 nock: - specifier: ^13.3.0 - version: 13.5.5 + specifier: ^14.0.0-beta.16 + version: 14.0.0-beta.16 rimraf: specifier: ^4.4.0 version: 4.4.1 @@ -778,13 +809,13 @@ importers: devDependencies: react-native: specifier: ^0.71.4 - version: 0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))(react@18.3.1) + version: 0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(react@18.3.1) react-native-fs: specifier: ^2.20.0 - version: 2.20.0(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))(react@18.3.1)) + version: 2.20.0(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(react@18.3.1)) react-native-get-random-values: specifier: ^1.8.0 - version: 1.11.0(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))(react@18.3.1)) + version: 1.11.0(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(react@18.3.1)) rimraf: specifier: ^4.4.0 version: 4.4.1 @@ -845,8 +876,8 @@ importers: specifier: ^8.5.4 version: 8.5.12 ts-node: - specifier: ^10.4.0 - version: 10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4) + specifier: ^10.9.2 + version: 10.9.2(@types/node@18.18.8)(typescript@5.5.4) samples/tails: dependencies: @@ -876,8 +907,8 @@ importers: version: 1.4.5-lts.1 devDependencies: ts-node: - specifier: ^10.4.0 - version: 10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4) + specifier: ^10.9.2 + version: 10.9.2(@types/node@18.18.8)(typescript@5.5.4) packages: @@ -903,6 +934,15 @@ packages: '@animo-id/mdoc@0.2.38': resolution: {integrity: sha512-98KQ0jvwTYsFOffTGvvHXBDo23b5xmhYjPiMIX6e807I6iS4fZZ9ypfBySdA5IiGUvXELKqEv27AUaayQa/9bg==} + '@animo-id/oauth2-utils@0.1.3': + resolution: {integrity: sha512-PzAx57LbDmmhI1qnF6Y/soYHLyHXxheSzlle+8rHexZmnWHXwxJ5nyOn/EQhGOqk5UEXLHYsD+27oyrMH3iR4A==} + + '@animo-id/oauth2@0.1.3': + resolution: {integrity: sha512-e4i+9nn3hyaxJ5LFTRb8Ri43VCAN5xpOvD0o2DcL6U90Y5ih3L+GVU6pzYylwk0iX/VD/HCMayDvF9qusDBh4w==} + + '@animo-id/oid4vci@0.1.3': + resolution: {integrity: sha512-01ka6sIQUVXNcrw6/fcWCbFpso60bb9Ejv4WUunA/7pGiowdzmmf4aaMRd2em/v5riRpo2tsIqv23SAbyTl41A==} + '@animo-id/react-native-bbs-signatures@0.1.0': resolution: {integrity: sha512-7qvsiWhGfUev8ngE8YzF6ON9PtCID5LiYVYM4EC5eyj80gCdhx3R46CI7K1qbqIlGsoTYQ/Xx5Ubo5Ji9eaUEA==} peerDependencies: @@ -923,14 +963,26 @@ packages: resolution: {integrity: sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==} engines: {node: '>=6.9.0'} + '@babel/code-frame@7.25.7': + resolution: {integrity: sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g==} + engines: {node: '>=6.9.0'} + '@babel/compat-data@7.25.2': resolution: {integrity: sha512-bYcppcpKBvX4znYaPEeFau03bp89ShqNMLs+rmdptMw+heSZh9+z84d2YG+K7cYLbWwzdjtDoW/uqZmPjulClQ==} engines: {node: '>=6.9.0'} + '@babel/compat-data@7.25.8': + resolution: {integrity: sha512-ZsysZyXY4Tlx+Q53XdnOFmqwfB9QDTHYxaZYajWRoBLuLEAwI2UIbtxOjWh/cFaa9IKUlcB+DDuoskLuKu56JA==} + engines: {node: '>=6.9.0'} + '@babel/core@7.25.2': resolution: {integrity: sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==} engines: {node: '>=6.9.0'} + '@babel/core@7.25.8': + resolution: {integrity: sha512-Oixnb+DzmRT30qu9d3tJSQkxuygWm32DFykT4bRoORPa9hZ/L4KhVB/XiRm6KG+roIEM7DBQlmg27kw2HZkdZg==} + engines: {node: '>=6.9.0'} + '@babel/generator@7.2.0': resolution: {integrity: sha512-BA75MVfRlFQG2EZgFYIwyT1r6xSkwfP2bdkY/kLZusEYWiJs4xCowab/alaEaT0wSvmVuXGqiefeBlP+7V1yKg==} @@ -938,30 +990,54 @@ packages: resolution: {integrity: sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw==} engines: {node: '>=6.9.0'} + '@babel/generator@7.25.7': + resolution: {integrity: sha512-5Dqpl5fyV9pIAD62yK9P7fcA768uVPUyrQmqpqstHWgMma4feF1x/oFysBCVZLY5wJ2GkMUCdsNDnGZrPoR6rA==} + engines: {node: '>=6.9.0'} + '@babel/helper-annotate-as-pure@7.24.7': resolution: {integrity: sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==} engines: {node: '>=6.9.0'} - '@babel/helper-builder-binary-assignment-operator-visitor@7.24.7': - resolution: {integrity: sha512-xZeCVVdwb4MsDBkkyZ64tReWYrLRHlMN72vP7Bdm3OUOuyFZExhsHUUnuWnm2/XOlAJzR0LfPpB56WXZn0X/lA==} + '@babel/helper-annotate-as-pure@7.25.7': + resolution: {integrity: sha512-4xwU8StnqnlIhhioZf1tqnVWeQ9pvH/ujS8hRfw/WOza+/a+1qv69BWNy+oY231maTCWgKWhfBU7kDpsds6zAA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-builder-binary-assignment-operator-visitor@7.25.7': + resolution: {integrity: sha512-12xfNeKNH7jubQNm7PAkzlLwEmCs1tfuX3UjIw6vP6QXi+leKh6+LyC/+Ed4EIQermwd58wsyh070yjDHFlNGg==} engines: {node: '>=6.9.0'} '@babel/helper-compilation-targets@7.25.2': resolution: {integrity: sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==} engines: {node: '>=6.9.0'} + '@babel/helper-compilation-targets@7.25.7': + resolution: {integrity: sha512-DniTEax0sv6isaw6qSQSfV4gVRNtw2rte8HHM45t9ZR0xILaufBRNkpMifCRiAPyvL4ACD6v0gfCwCmtOQaV4A==} + engines: {node: '>=6.9.0'} + '@babel/helper-create-class-features-plugin@7.25.0': resolution: {integrity: sha512-GYM6BxeQsETc9mnct+nIIpf63SAyzvyYN7UB/IlTyd+MBg06afFGp0mIeUqGyWgS2mxad6vqbMrHVlaL3m70sQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 + '@babel/helper-create-class-features-plugin@7.25.7': + resolution: {integrity: sha512-bD4WQhbkx80mAyj/WCm4ZHcF4rDxkoLFO6ph8/5/mQ3z4vAzltQXAmbc7GvVJx5H+lk5Mi5EmbTeox5nMGCsbw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + '@babel/helper-create-regexp-features-plugin@7.25.2': resolution: {integrity: sha512-+wqVGP+DFmqwFD3EH6TMTfUNeqDehV3E/dl+Sd54eaXqm17tEUNbEIn4sVivVowbvUpOtIGxdo3GoXyDH9N/9g==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 + '@babel/helper-create-regexp-features-plugin@7.25.7': + resolution: {integrity: sha512-byHhumTj/X47wJ6C6eLpK7wW/WBEcnUeb7D0FNc/jFQnQVw7DOso3Zz5u9x/zLrFVkHa89ZGDbkAa1D54NdrCQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + '@babel/helper-define-polyfill-provider@0.6.2': resolution: {integrity: sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ==} peerDependencies: @@ -975,99 +1051,170 @@ packages: resolution: {integrity: sha512-LABppdt+Lp/RlBxqrh4qgf1oEH/WxdzQNDJIu5gC/W1GyvPVrOBiItmmM8wan2fm4oYqFuFfkXmlGpLQhPY8CA==} engines: {node: '>=6.9.0'} + '@babel/helper-member-expression-to-functions@7.25.7': + resolution: {integrity: sha512-O31Ssjd5K6lPbTX9AAYpSKrZmLeagt9uwschJd+Ixo6QiRyfpvgtVQp8qrDR9UNFjZ8+DO34ZkdrN+BnPXemeA==} + engines: {node: '>=6.9.0'} + '@babel/helper-module-imports@7.24.7': resolution: {integrity: sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==} engines: {node: '>=6.9.0'} + '@babel/helper-module-imports@7.25.7': + resolution: {integrity: sha512-o0xCgpNmRohmnoWKQ0Ij8IdddjyBFE4T2kagL/x6M3+4zUgc+4qTOUBoNe4XxDskt1HPKO007ZPiMgLDq2s7Kw==} + engines: {node: '>=6.9.0'} + '@babel/helper-module-transforms@7.25.2': resolution: {integrity: sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 + '@babel/helper-module-transforms@7.25.7': + resolution: {integrity: sha512-k/6f8dKG3yDz/qCwSM+RKovjMix563SLxQFo0UhRNo239SP6n9u5/eLtKD6EAjwta2JHJ49CsD8pms2HdNiMMQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + '@babel/helper-optimise-call-expression@7.24.7': resolution: {integrity: sha512-jKiTsW2xmWwxT1ixIdfXUZp+P5yURx2suzLZr5Hi64rURpDYdMW0pv+Uf17EYk2Rd428Lx4tLsnjGJzYKDM/6A==} engines: {node: '>=6.9.0'} + '@babel/helper-optimise-call-expression@7.25.7': + resolution: {integrity: sha512-VAwcwuYhv/AT+Vfr28c9y6SHzTan1ryqrydSTFGjU0uDJHw3uZ+PduI8plCLkRsDnqK2DMEDmwrOQRsK/Ykjng==} + engines: {node: '>=6.9.0'} + '@babel/helper-plugin-utils@7.24.8': resolution: {integrity: sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==} engines: {node: '>=6.9.0'} + '@babel/helper-plugin-utils@7.25.7': + resolution: {integrity: sha512-eaPZai0PiqCi09pPs3pAFfl/zYgGaE6IdXtYvmf0qlcDTd3WCtO7JWCcRd64e0EQrcYgiHibEZnOGsSY4QSgaw==} + engines: {node: '>=6.9.0'} + '@babel/helper-remap-async-to-generator@7.25.0': resolution: {integrity: sha512-NhavI2eWEIz/H9dbrG0TuOicDhNexze43i5z7lEqwYm0WEZVTwnPpA0EafUTP7+6/W79HWIP2cTe3Z5NiSTVpw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 + '@babel/helper-remap-async-to-generator@7.25.7': + resolution: {integrity: sha512-kRGE89hLnPfcz6fTrlNU+uhgcwv0mBE4Gv3P9Ke9kLVJYpi4AMVVEElXvB5CabrPZW4nCM8P8UyyjrzCM0O2sw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + '@babel/helper-replace-supers@7.25.0': resolution: {integrity: sha512-q688zIvQVYtZu+i2PsdIu/uWGRpfxzr5WESsfpShfZECkO+d2o+WROWezCi/Q6kJ0tfPa5+pUGUlfx2HhrA3Bg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 + '@babel/helper-replace-supers@7.25.7': + resolution: {integrity: sha512-iy8JhqlUW9PtZkd4pHM96v6BdJ66Ba9yWSE4z0W4TvSZwLBPkyDsiIU3ENe4SmrzRBs76F7rQXTy1lYC49n6Lw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + '@babel/helper-simple-access@7.24.7': resolution: {integrity: sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==} engines: {node: '>=6.9.0'} + '@babel/helper-simple-access@7.25.7': + resolution: {integrity: sha512-FPGAkJmyoChQeM+ruBGIDyrT2tKfZJO8NcxdC+CWNJi7N8/rZpSxK7yvBJ5O/nF1gfu5KzN7VKG3YVSLFfRSxQ==} + engines: {node: '>=6.9.0'} + '@babel/helper-skip-transparent-expression-wrappers@7.24.7': resolution: {integrity: sha512-IO+DLT3LQUElMbpzlatRASEyQtfhSE0+m465v++3jyyXeBTBUjtVZg28/gHeV5mrTJqvEKhKroBGAvhW+qPHiQ==} engines: {node: '>=6.9.0'} + '@babel/helper-skip-transparent-expression-wrappers@7.25.7': + resolution: {integrity: sha512-pPbNbchZBkPMD50K0p3JGcFMNLVUCuU/ABybm/PGNj4JiHrpmNyqqCphBk4i19xXtNV0JhldQJJtbSW5aUvbyA==} + engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@7.24.8': resolution: {integrity: sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==} engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@7.25.7': + resolution: {integrity: sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.24.7': resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.25.7': + resolution: {integrity: sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-option@7.24.8': resolution: {integrity: sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-option@7.25.7': + resolution: {integrity: sha512-ytbPLsm+GjArDYXJ8Ydr1c/KJuutjF2besPNbIZnZ6MKUxi/uTA22t2ymmA4WFjZFpjiAMO0xuuJPqK2nvDVfQ==} + engines: {node: '>=6.9.0'} + '@babel/helper-wrap-function@7.25.0': resolution: {integrity: sha512-s6Q1ebqutSiZnEjaofc/UKDyC4SbzV5n5SrA2Gq8UawLycr3i04f1dX4OzoQVnexm6aOCh37SQNYlJ/8Ku+PMQ==} engines: {node: '>=6.9.0'} + '@babel/helper-wrap-function@7.25.7': + resolution: {integrity: sha512-MA0roW3JF2bD1ptAaJnvcabsVlNQShUaThyJbCDD4bCp8NEgiFvpoqRI2YS22hHlc2thjO/fTg2ShLMC3jygAg==} + engines: {node: '>=6.9.0'} + '@babel/helpers@7.25.0': resolution: {integrity: sha512-MjgLZ42aCm0oGjJj8CtSM3DB8NOOf8h2l7DCTePJs29u+v7yO/RBX9nShlKMgFnRks/Q4tBAe7Hxnov9VkGwLw==} engines: {node: '>=6.9.0'} + '@babel/helpers@7.25.7': + resolution: {integrity: sha512-Sv6pASx7Esm38KQpF/U/OXLwPPrdGHNKoeblRxgZRLXnAtnkEe4ptJPDtAZM7fBLadbc1Q07kQpSiGQ0Jg6tRA==} + engines: {node: '>=6.9.0'} + '@babel/highlight@7.24.7': resolution: {integrity: sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==} engines: {node: '>=6.9.0'} + '@babel/highlight@7.25.7': + resolution: {integrity: sha512-iYyACpW3iW8Fw+ZybQK+drQre+ns/tKpXbNESfrhNnPLIklLbXr7MYJ6gPEd0iETGLOK+SxMjVvKb/ffmk+FEw==} + engines: {node: '>=6.9.0'} + '@babel/parser@7.25.3': resolution: {integrity: sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==} engines: {node: '>=6.0.0'} hasBin: true - '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.3': - resolution: {integrity: sha512-wUrcsxZg6rqBXG05HG1FPYgsP6EvwF4WpBbxIpWIIYnH8wG0gzx3yZY3dtEHas4sTAOGkbTsc9EGPxwff8lRoA==} + '@babel/parser@7.25.8': + resolution: {integrity: sha512-HcttkxzdPucv3nNFmfOOMfFf64KgdJVqm1KaCm25dPGMLElo9nsLvXeJECQg8UzPuBGLyTSA0ZzqCtDSzKTEoQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.7': + resolution: {integrity: sha512-UV9Lg53zyebzD1DwQoT9mzkEKa922LNUp5YkTJ6Uta0RbyXaQNUgcvSt7qIu1PpPzVb6rd10OVNTzkyBGeVmxQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.25.0': - resolution: {integrity: sha512-Bm4bH2qsX880b/3ziJ8KD711LT7z4u8CFudmjqle65AZj/HNUFhEf90dqYv6O86buWvSBmeQDjv0Tn2aF/bIBA==} + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.25.7': + resolution: {integrity: sha512-GDDWeVLNxRIkQTnJn2pDOM1pkCgYdSqPeT1a9vh9yIqu2uzzgw1zcqEb+IJOhy+dTBMlNdThrDIksr2o09qrrQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.25.0': - resolution: {integrity: sha512-lXwdNZtTmeVOOFtwM/WDe7yg1PL8sYhRk/XH0FzbR2HDQ0xC+EnQ/JHeoMYSavtU115tnUk0q9CDyq8si+LMAA==} + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.25.7': + resolution: {integrity: sha512-wxyWg2RYaSUYgmd9MR0FyRGyeOMQE/Uzr1wzd/g5cf5bwi9A4v6HFdDm7y1MgDtod/fLOSTZY6jDgV0xU9d5bA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.24.7': - resolution: {integrity: sha512-+izXIbke1T33mY4MSNnrqhPXDz01WYhEf3yF5NbnUtkiNnm+XBZJl3kNfoK6NKmYlz/D07+l2GWVK/QfDkNCuQ==} + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.25.7': + resolution: {integrity: sha512-Xwg6tZpLxc4iQjorYsyGMyfJE7nP5MV8t/Ka58BgiA7Jw0fRqQNcANlLfdJ/yvBt9z9LD2We+BEkT7vLqZRWng==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.13.0 - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.25.0': - resolution: {integrity: sha512-tggFrk1AIShG/RUQbEwt2Tr/E+ObkfwrPjR6BjbRvsx24+PSjK8zrq0GWPNCjo8qpRx4DuJzlcvWJqlm+0h3kw==} + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.25.7': + resolution: {integrity: sha512-UVATLMidXrnH+GMUIuxq55nejlj02HP7F5ETyBONzP6G87fPBogG4CH6kxrSrdIuAjdwNO9VzyaYsrZPscWUrw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -1202,8 +1349,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-import-assertions@7.24.7': - resolution: {integrity: sha512-Ec3NRUMoi8gskrkBe3fNmEQfxDvY8bgfQpz6jlk/41kX9eUjvpyqWU7PBP/pLAvMaSQjbMNKJmvX57jP+M6bPg==} + '@babel/plugin-syntax-import-assertions@7.25.7': + resolution: {integrity: sha512-ZvZQRmME0zfJnDQnVBKYzHxXT7lYBB3Revz1GuS7oLXWMgqUPX4G+DDbT30ICClht9WKV34QVrZhSw6WdklwZQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1214,6 +1361,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-import-attributes@7.25.7': + resolution: {integrity: sha512-AqVo+dguCgmpi/3mYBdu9lkngOBlQ2w2vnNpa6gfiCxQZLzV4ZbhsXitJ2Yblkoe1VQwtHSaNmIaGll/26YWRw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-import-meta@7.10.4': resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} peerDependencies: @@ -1290,8 +1443,14 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-async-generator-functions@7.25.0': - resolution: {integrity: sha512-uaIi2FdqzjpAMvVqvB51S42oC2JEVgh0LDsGfZVDysWE8LrJtQC2jvKmOqEYThKyB7bDEb7BP1GYWDm7tABA0Q==} + '@babel/plugin-transform-arrow-functions@7.25.7': + resolution: {integrity: sha512-EJN2mKxDwfOUCPxMO6MUI58RN3ganiRAG/MS/S3HfB6QFNjroAMelQo/gybyYq97WerCBAZoyrAoW8Tzdq2jWg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-async-generator-functions@7.25.8': + resolution: {integrity: sha512-9ypqkozyzpG+HxlH4o4gdctalFGIjjdufzo7I2XPda0iBnZ6a+FO0rIEQcdSPXp02CkvGsII1exJhmROPQd5oA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1302,26 +1461,44 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-async-to-generator@7.25.7': + resolution: {integrity: sha512-ZUCjAavsh5CESCmi/xCpX1qcCaAglzs/7tmuvoFnJgA1dM7gQplsguljoTg+Ru8WENpX89cQyAtWoaE0I3X3Pg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-block-scoped-functions@7.24.7': resolution: {integrity: sha512-yO7RAz6EsVQDaBH18IDJcMB1HnrUn2FJ/Jslc/WtPPWcjhpUJXU/rjbwmluzp7v/ZzWcEhTMXELnnsz8djWDwQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-block-scoped-functions@7.25.7': + resolution: {integrity: sha512-xHttvIM9fvqW+0a3tZlYcZYSBpSWzGBFIt/sYG3tcdSzBB8ZeVgz2gBP7Df+sM0N1850jrviYSSeUuc+135dmQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-block-scoping@7.25.0': resolution: {integrity: sha512-yBQjYoOjXlFv9nlXb3f1casSHOZkWr29NX+zChVanLg5Nc157CrbEX9D7hxxtTpuFy7Q0YzmmWfJxzvps4kXrQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-class-properties@7.24.7': - resolution: {integrity: sha512-vKbfawVYayKcSeSR5YYzzyXvsDFWU2mD8U5TFeXtbCPLFUqe7GyCgvO6XDHzje862ODrOwy6WCPmKeWHbCFJ4w==} + '@babel/plugin-transform-block-scoping@7.25.7': + resolution: {integrity: sha512-ZEPJSkVZaeTFG/m2PARwLZQ+OG0vFIhPlKHK/JdIMy8DbRJ/htz6LRrTFtdzxi9EHmcwbNPAKDnadpNSIW+Aow==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-class-static-block@7.24.7': - resolution: {integrity: sha512-HMXK3WbBPpZQufbMG4B46A90PkuuhN9vBCb5T8+VAHqvAqvcLi+2cKoukcpmUYkszLhScU3l1iudhrks3DggRQ==} + '@babel/plugin-transform-class-properties@7.25.7': + resolution: {integrity: sha512-mhyfEW4gufjIqYFo9krXHJ3ElbFLIze5IDp+wQTxoPd+mwFb1NxatNAwmv8Q8Iuxv7Zc+q8EkiMQwc9IhyGf4g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-class-static-block@7.25.8': + resolution: {integrity: sha512-e82gl3TCorath6YLf9xUwFehVvjvfqFhdOo4+0iVIVju+6XOi5XHkqB3P2AXnSwoeTX0HBoXq5gJFtvotJzFnQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.12.0 @@ -1332,50 +1509,68 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-classes@7.25.7': + resolution: {integrity: sha512-9j9rnl+YCQY0IGoeipXvnk3niWicIB6kCsWRGLwX241qSXpbA4MKxtp/EdvFxsc4zI5vqfLxzOd0twIJ7I99zg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-computed-properties@7.24.7': resolution: {integrity: sha512-25cS7v+707Gu6Ds2oY6tCkUwsJ9YIDbggd9+cu9jzzDgiNq7hR/8dkzxWfKWnTic26vsI3EsCXNd4iEB6e8esQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-computed-properties@7.25.7': + resolution: {integrity: sha512-QIv+imtM+EtNxg/XBKL3hiWjgdLjMOmZ+XzQwSgmBfKbfxUjBzGgVPklUuE55eq5/uVoh8gg3dqlrwR/jw3ZeA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-destructuring@7.24.8': resolution: {integrity: sha512-36e87mfY8TnRxc7yc6M9g9gOB7rKgSahqkIKwLpz4Ppk2+zC2Cy1is0uwtuSG6AE4zlTOUa+7JGz9jCJGLqQFQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-dotall-regex@7.24.7': - resolution: {integrity: sha512-ZOA3W+1RRTSWvyqcMJDLqbchh7U4NRGqwRfFSVbOLS/ePIP4vHB5e8T8eXcuqyN1QkgKyj5wuW0lcS85v4CrSw==} + '@babel/plugin-transform-destructuring@7.25.7': + resolution: {integrity: sha512-xKcfLTlJYUczdaM1+epcdh1UGewJqr9zATgrNHcLBcV2QmfvPPEixo/sK/syql9cEmbr7ulu5HMFG5vbbt/sEA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-dotall-regex@7.25.7': + resolution: {integrity: sha512-kXzXMMRzAtJdDEgQBLF4oaiT6ZCU3oWHgpARnTKDAqPkDJ+bs3NrZb310YYevR5QlRo3Kn7dzzIdHbZm1VzJdQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-duplicate-keys@7.24.7': - resolution: {integrity: sha512-JdYfXyCRihAe46jUIliuL2/s0x0wObgwwiGxw/UbgJBr20gQBThrokO4nYKgWkD7uBaqM7+9x5TU7NkExZJyzw==} + '@babel/plugin-transform-duplicate-keys@7.25.7': + resolution: {integrity: sha512-by+v2CjoL3aMnWDOyCIg+yxU9KXSRa9tN6MbqggH5xvymmr9p4AMjYkNlQy4brMceBnUyHZ9G8RnpvT8wP7Cfg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.25.0': - resolution: {integrity: sha512-YLpb4LlYSc3sCUa35un84poXoraOiQucUTTu8X1j18JV+gNa8E0nyUf/CjZ171IRGr4jEguF+vzJU66QZhn29g==} + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.25.7': + resolution: {integrity: sha512-HvS6JF66xSS5rNKXLqkk7L9c/jZ/cdIVIcoPVrnl8IsVpLggTjXs8OWekbLHs/VtYDDh5WXnQyeE3PPUGm22MA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/plugin-transform-dynamic-import@7.24.7': - resolution: {integrity: sha512-sc3X26PhZQDb3JhORmakcbvkeInvxz+A8oda99lj7J60QRuPZvNAk9wQlTBS1ZynelDrDmTU4pw1tyc5d5ZMUg==} + '@babel/plugin-transform-dynamic-import@7.25.8': + resolution: {integrity: sha512-gznWY+mr4ZQL/EWPcbBQUP3BXS5FwZp8RUOw06BaRn8tQLzN4XLIxXejpHN9Qo8x8jjBmAAKp6FoS51AgkSA/A==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-exponentiation-operator@7.24.7': - resolution: {integrity: sha512-Rqe/vSc9OYgDajNIK35u7ot+KeCoetqQYFXM4Epf7M7ez3lWlOjrDjrwMei6caCVhfdw+mIKD4cgdGNy5JQotQ==} + '@babel/plugin-transform-exponentiation-operator@7.25.7': + resolution: {integrity: sha512-yjqtpstPfZ0h/y40fAXRv2snciYr0OAoMXY/0ClC7tm4C/nG5NJKmIItlaYlLbIVAWNfrYuy9dq1bE0SbX0PEg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-export-namespace-from@7.24.7': - resolution: {integrity: sha512-v0K9uNYsPL3oXZ/7F9NNIbAj2jv1whUEtyA6aujhekLs56R++JDQuzRcP2/z4WX5Vg/c5lE9uWZA0/iUoFhLTA==} + '@babel/plugin-transform-export-namespace-from@7.25.8': + resolution: {integrity: sha512-sPtYrduWINTQTW7FtOy99VCTWp4H23UX7vYcut7S4CIMEXU+54zKX9uCoGkLsWXteyaMXzVHgzWbLfQ1w4GZgw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1392,14 +1587,26 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-for-of@7.25.7': + resolution: {integrity: sha512-n/TaiBGJxYFWvpJDfsxSj9lEEE44BFM1EPGz4KEiTipTgkoFVVcCmzAL3qA7fdQU96dpo4gGf5HBx/KnDvqiHw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-function-name@7.25.1': resolution: {integrity: sha512-TVVJVdW9RKMNgJJlLtHsKDTydjZAbwIsn6ySBPQaEAUU5+gVvlJt/9nRmqVbsV/IBanRjzWoaAQKLoamWVOUuA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-json-strings@7.24.7': - resolution: {integrity: sha512-2yFnBGDvRuxAaE/f0vfBKvtnvvqU8tGpMHqMNpTN2oWMKIR3NqFkjaAgGwawhqK/pIN2T3XdjGPdaG0vDhOBGw==} + '@babel/plugin-transform-function-name@7.25.7': + resolution: {integrity: sha512-5MCTNcjCMxQ63Tdu9rxyN6cAWurqfrDZ76qvVPrGYdBxIj+EawuuxTu/+dgJlhK5eRz3v1gLwp6XwS8XaX2NiQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-json-strings@7.25.8': + resolution: {integrity: sha512-4OMNv7eHTmJ2YXs3tvxAfa/I43di+VcF+M4Wt66c88EAED1RoGaf1D64cL5FkRpNL+Vx9Hds84lksWvd/wMIdA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1410,8 +1617,14 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-logical-assignment-operators@7.24.7': - resolution: {integrity: sha512-4D2tpwlQ1odXmTEIFWy9ELJcZHqrStlzK/dAOWYyxX3zT0iXQB6banjgeOJQXzEc4S0E0a5A+hahxPaEFYftsw==} + '@babel/plugin-transform-literals@7.25.7': + resolution: {integrity: sha512-fwzkLrSu2fESR/cm4t6vqd7ebNIopz2QHGtjoU+dswQo/P6lwAG04Q98lliE3jkz/XqnbGFLnUcE0q0CVUf92w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-logical-assignment-operators@7.25.8': + resolution: {integrity: sha512-f5W0AhSbbI+yY6VakT04jmxdxz+WsID0neG7+kQZbCOjuyJNdL5Nn4WIBm4hRpKnUcO9lP0eipUhFN12JpoH8g==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1422,8 +1635,14 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-modules-amd@7.24.7': - resolution: {integrity: sha512-9+pB1qxV3vs/8Hdmz/CulFB8w2tuu6EB94JZFsjdqxQokwGa9Unap7Bo2gGBGIvPmDIVvQrom7r5m/TCDMURhg==} + '@babel/plugin-transform-member-expression-literals@7.25.7': + resolution: {integrity: sha512-Std3kXwpXfRV0QtQy5JJcRpkqP8/wG4XL7hSKZmGlxPlDqmpXtEPRmhF7ztnlTCtUN3eXRUJp+sBEZjaIBVYaw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-amd@7.25.7': + resolution: {integrity: sha512-CgselSGCGzjQvKzghCvDTxKHP3iooenLpJDO842ehn5D2G5fJB222ptnDwQho0WjEvg7zyoxb9P+wiYxiJX5yA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1434,14 +1653,20 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-modules-systemjs@7.25.0': - resolution: {integrity: sha512-YPJfjQPDXxyQWg/0+jHKj1llnY5f/R6a0p/vP4lPymxLu7Lvl4k2WMitqi08yxwQcCVUUdG9LCUj4TNEgAp3Jw==} + '@babel/plugin-transform-modules-commonjs@7.25.7': + resolution: {integrity: sha512-L9Gcahi0kKFYXvweO6n0wc3ZG1ChpSFdgG+eV1WYZ3/dGbJK7vvk91FgGgak8YwRgrCuihF8tE/Xg07EkL5COg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-modules-umd@7.24.7': - resolution: {integrity: sha512-3aytQvqJ/h9z4g8AsKPLvD4Zqi2qT+L3j7XoFFu1XBlZWEl2/1kWnhmAbxpLgPrHSY0M6UA02jyTiwUVtiKR6A==} + '@babel/plugin-transform-modules-systemjs@7.25.7': + resolution: {integrity: sha512-t9jZIvBmOXJsiuyOwhrIGs8dVcD6jDyg2icw1VL4A/g+FnWyJKwUfSSU2nwJuMV2Zqui856El9u+ElB+j9fV1g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-umd@7.25.7': + resolution: {integrity: sha512-p88Jg6QqsaPh+EB7I9GJrIqi1Zt4ZBHUQtjw3z1bzEXcLh6GfPqzZJ6G+G1HBGKUNukT58MnKG7EN7zXQBCODw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1452,26 +1677,32 @@ packages: peerDependencies: '@babel/core': ^7.0.0 - '@babel/plugin-transform-new-target@7.24.7': - resolution: {integrity: sha512-RNKwfRIXg4Ls/8mMTza5oPF5RkOW8Wy/WgMAp1/F1yZ8mMbtwXW+HDoJiOsagWrAhI5f57Vncrmr9XeT4CVapA==} + '@babel/plugin-transform-named-capturing-groups-regex@7.25.7': + resolution: {integrity: sha512-BtAT9LzCISKG3Dsdw5uso4oV1+v2NlVXIIomKJgQybotJY3OwCwJmkongjHgwGKoZXd0qG5UZ12JUlDQ07W6Ow==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-new-target@7.25.7': + resolution: {integrity: sha512-CfCS2jDsbcZaVYxRFo2qtavW8SpdzmBXC2LOI4oO0rP+JSRDxxF3inF4GcPsLgfb5FjkhXG5/yR/lxuRs2pySA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-nullish-coalescing-operator@7.24.7': - resolution: {integrity: sha512-Ts7xQVk1OEocqzm8rHMXHlxvsfZ0cEF2yomUqpKENHWMF4zKk175Y4q8H5knJes6PgYad50uuRmt3UJuhBw8pQ==} + '@babel/plugin-transform-nullish-coalescing-operator@7.25.8': + resolution: {integrity: sha512-Z7WJJWdQc8yCWgAmjI3hyC+5PXIubH9yRKzkl9ZEG647O9szl9zvmKLzpbItlijBnVhTUf1cpyWBsZ3+2wjWPQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-numeric-separator@7.24.7': - resolution: {integrity: sha512-e6q1TiVUzvH9KRvicuxdBTUj4AdKSRwzIyFFnfnezpCfP2/7Qmbb8qbU2j7GODbl4JMkblitCQjKYUaX/qkkwA==} + '@babel/plugin-transform-numeric-separator@7.25.8': + resolution: {integrity: sha512-rm9a5iEFPS4iMIy+/A/PiS0QN0UyjPIeVvbU5EMZFKJZHt8vQnasbpo3T3EFcxzCeYO0BHfc4RqooCZc51J86Q==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-object-rest-spread@7.24.7': - resolution: {integrity: sha512-4QrHAr0aXQCEFni2q4DqKLD31n2DL+RxcwnNjDFkSG0eNQ/xCavnRkfCUjsyqGC2OviNJvZOF/mQqZBw7i2C5Q==} + '@babel/plugin-transform-object-rest-spread@7.25.8': + resolution: {integrity: sha512-LkUu0O2hnUKHKE7/zYOIjByMa4VRaV2CD/cdGz0AxU9we+VA3kDDggKEzI0Oz1IroG+6gUP6UmWEHBMWZU316g==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1482,14 +1713,20 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-optional-catch-binding@7.24.7': - resolution: {integrity: sha512-uLEndKqP5BfBbC/5jTwPxLh9kqPWWgzN/f8w6UwAIirAEqiIVJWWY312X72Eub09g5KF9+Zn7+hT7sDxmhRuKA==} + '@babel/plugin-transform-object-super@7.25.7': + resolution: {integrity: sha512-pWT6UXCEW3u1t2tcAGtE15ornCBvopHj9Bps9D2DsH15APgNVOTwwczGckX+WkAvBmuoYKRCFa4DK+jM8vh5AA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-optional-chaining@7.24.8': - resolution: {integrity: sha512-5cTOLSMs9eypEy8JUVvIKOu6NgvbJMnpG62VpIHrTmROdQ+L5mDAaI40g25k5vXti55JWNX5jCkq3HZxXBQANw==} + '@babel/plugin-transform-optional-catch-binding@7.25.8': + resolution: {integrity: sha512-EbQYweoMAHOn7iJ9GgZo14ghhb9tTjgOc88xFgYngifx7Z9u580cENCV159M4xDh3q/irbhSjZVpuhpC2gKBbg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-optional-chaining@7.25.8': + resolution: {integrity: sha512-q05Bk7gXOxpTHoQ8RSzGSh/LHVB9JEIkKnk3myAWwZHnYiTGYtbdrYkIsS8Xyh4ltKf7GNUSgzs/6P2bJtBAQg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1500,14 +1737,20 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-private-methods@7.24.7': - resolution: {integrity: sha512-COTCOkG2hn4JKGEKBADkA8WNb35TGkkRbI5iT845dB+NyqgO8Hn+ajPbSnIQznneJTa3d30scb6iz/DhH8GsJQ==} + '@babel/plugin-transform-parameters@7.25.7': + resolution: {integrity: sha512-FYiTvku63me9+1Nz7TOx4YMtW3tWXzfANZtrzHhUZrz4d47EEtMQhzFoZWESfXuAMMT5mwzD4+y1N8ONAX6lMQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-private-methods@7.25.7': + resolution: {integrity: sha512-KY0hh2FluNxMLwOCHbxVOKfdB5sjWG4M183885FmaqWWiGMhRZq4DQRKH6mHdEucbJnyDyYiZNwNG424RymJjA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-private-property-in-object@7.24.7': - resolution: {integrity: sha512-9z76mxwnwFxMyxZWEgdgECQglF2Q7cFLm0kMf8pGwt+GSJsY0cONKj/UuO4bOH0w/uAel3ekS4ra5CEAyJRmDA==} + '@babel/plugin-transform-private-property-in-object@7.25.8': + resolution: {integrity: sha512-8Uh966svuB4V8RHHg0QJOB32QK287NBksJOByoKmHMp1TAobNniNalIkI2i5IPj5+S9NYCG4VIjbEuiSN8r+ow==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1518,6 +1761,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-property-literals@7.25.7': + resolution: {integrity: sha512-lQEeetGKfFi0wHbt8ClQrUSUMfEeI3MMm74Z73T9/kuz990yYVtfofjf3NuA42Jy3auFOpbjDyCSiIkTs1VIYw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-react-display-name@7.24.7': resolution: {integrity: sha512-H/Snz9PFxKsS1JLI4dJLtnJgCJRoo0AUm3chP6NYr+9En1JMKloheEiLIhlp5MDVznWo+H3AAC1Mc8lmUEpsgg==} engines: {node: '>=6.9.0'} @@ -1554,14 +1803,14 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-regenerator@7.24.7': - resolution: {integrity: sha512-lq3fvXPdimDrlg6LWBoqj+r/DEWgONuwjuOuQCSYgRroXDH/IdM1C0IZf59fL5cHLpjEH/O6opIRBbqv7ELnuA==} + '@babel/plugin-transform-regenerator@7.25.7': + resolution: {integrity: sha512-mgDoQCRjrY3XK95UuV60tZlFCQGXEtMg8H+IsW72ldw1ih1jZhzYXbJvghmAEpg5UVhhnCeia1CkGttUvCkiMQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-reserved-words@7.24.7': - resolution: {integrity: sha512-0DUq0pHcPKbjFZCfTss/pGkYMfy3vFWydkUBd9r0GHpIyfs2eCDENvqadMycRS9wZCXR41wucAfJHJmwA0UmoQ==} + '@babel/plugin-transform-reserved-words@7.25.7': + resolution: {integrity: sha512-3OfyfRRqiGeOvIWSagcwUTVk2hXBsr/ww7bLn6TRTuXnexA+Udov2icFOxFX9abaj4l96ooYkcNN1qi2Zvqwng==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1578,26 +1827,50 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-shorthand-properties@7.25.7': + resolution: {integrity: sha512-uBbxNwimHi5Bv3hUccmOFlUy3ATO6WagTApenHz9KzoIdn0XeACdB12ZJ4cjhuB2WSi80Ez2FWzJnarccriJeA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-spread@7.24.7': resolution: {integrity: sha512-x96oO0I09dgMDxJaANcRyD4ellXFLLiWhuwDxKZX5g2rWP1bTPkBSwCYv96VDXVT1bD9aPj8tppr5ITIh8hBng==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-spread@7.25.7': + resolution: {integrity: sha512-Mm6aeymI0PBh44xNIv/qvo8nmbkpZze1KvR8MkEqbIREDxoiWTi18Zr2jryfRMwDfVZF9foKh060fWgni44luw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-sticky-regex@7.24.7': resolution: {integrity: sha512-kHPSIJc9v24zEml5geKg9Mjx5ULpfncj0wRpYtxbvKyTtHCYDkVE3aHQ03FrpEo4gEe2vrJJS1Y9CJTaThA52g==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-sticky-regex@7.25.7': + resolution: {integrity: sha512-ZFAeNkpGuLnAQ/NCsXJ6xik7Id+tHuS+NT+ue/2+rn/31zcdnupCdmunOizEaP0JsUmTFSTOPoQY7PkK2pttXw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-template-literals@7.24.7': resolution: {integrity: sha512-AfDTQmClklHCOLxtGoP7HkeMw56k1/bTQjwsfhL6pppo/M4TOBSq+jjBUBLmV/4oeFg4GWMavIl44ZeCtmmZTw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-typeof-symbol@7.24.8': - resolution: {integrity: sha512-adNTUpDCVnmAE58VEqKlAA6ZBlNkMnWD0ZcW76lyNFN3MJniyGFZfNwERVk8Ap56MCnXztmDr19T4mPTztcuaw==} + '@babel/plugin-transform-template-literals@7.25.7': + resolution: {integrity: sha512-SI274k0nUsFFmyQupiO7+wKATAmMFf8iFgq2O+vVFXZ0SV9lNfT1NGzBEhjquFmD8I9sqHLguH+gZVN3vww2AA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-typeof-symbol@7.25.7': + resolution: {integrity: sha512-OmWmQtTHnO8RSUbL0NTdtpbZHeNTnm68Gj5pA4Y2blFNh+V4iZR68V1qL9cI37J21ZN7AaCnkfdHtLExQPf2uA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1608,14 +1881,14 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-unicode-escapes@7.24.7': - resolution: {integrity: sha512-U3ap1gm5+4edc2Q/P+9VrBNhGkfnf+8ZqppY71Bo/pzZmXhhLdqgaUl6cuB07O1+AQJtCLfaOmswiNbSQ9ivhw==} + '@babel/plugin-transform-unicode-escapes@7.25.7': + resolution: {integrity: sha512-BN87D7KpbdiABA+t3HbVqHzKWUDN3dymLaTnPFAMyc8lV+KN3+YzNhVRNdinaCPA4AUqx7ubXbQ9shRjYBl3SQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-unicode-property-regex@7.24.7': - resolution: {integrity: sha512-uH2O4OV5M9FZYQrwc7NdVmMxQJOCCzFeYudlZSzUAHRFeOujQefa92E74TQDVskNHCzOXoigEuoyzHDhaEaK5w==} + '@babel/plugin-transform-unicode-property-regex@7.25.7': + resolution: {integrity: sha512-IWfR89zcEPQGB/iB408uGtSPlQd3Jpq11Im86vUgcmSTcoWAiQMCTOa2K2yNNqFJEBVICKhayctee65Ka8OB0w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1626,14 +1899,20 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-unicode-sets-regex@7.24.7': - resolution: {integrity: sha512-2G8aAvF4wy1w/AGZkemprdGMRg5o6zPNhbHVImRz3lss55TYCBd6xStN19rt8XJHq20sqV0JbyWjOWwQRwV/wg==} + '@babel/plugin-transform-unicode-regex@7.25.7': + resolution: {integrity: sha512-8JKfg/hiuA3qXnlLx8qtv5HWRbgyFx2hMMtpDDuU2rTckpKkGu4ycK5yYHwuEa16/quXfoxHBIApEsNyMWnt0g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-sets-regex@7.25.7': + resolution: {integrity: sha512-YRW8o9vzImwmh4Q3Rffd09bH5/hvY0pxg+1H1i0f7APoUeg12G7+HhLj9ZFNIrYkgBXhIijPJ+IXypN0hLTIbw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/preset-env@7.25.3': - resolution: {integrity: sha512-QsYW7UeAaXvLPX9tdVliMJE7MD7M6MLYVTovRTIwhoYQVFHR1rM4wO8wqAezYi3/BpSD+NzVCZ69R6smWiIi8g==} + '@babel/preset-env@7.25.8': + resolution: {integrity: sha512-58T2yulDHMN8YMUxiLq5YmWUnlDCyY1FsHM+v12VMx+1/FlrUj5tY50iDCpofFQEM8fMYOaY9YRvym2jcjn1Dg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1678,14 +1957,26 @@ packages: resolution: {integrity: sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==} engines: {node: '>=6.9.0'} + '@babel/template@7.25.7': + resolution: {integrity: sha512-wRwtAgI3bAS+JGU2upWNL9lSlDcRCqD05BZ1n3X2ONLH1WilFP6O1otQjeMK/1g0pvYcXC7b/qVUB1keofjtZA==} + engines: {node: '>=6.9.0'} + '@babel/traverse@7.25.3': resolution: {integrity: sha512-HefgyP1x754oGCsKmV5reSmtV7IXj/kpaE1XYY+D9G5PvKKoFfSbiS4M77MdjuwlZKDIKFCffq9rPU+H/s3ZdQ==} engines: {node: '>=6.9.0'} + '@babel/traverse@7.25.7': + resolution: {integrity: sha512-jatJPT1Zjqvh/1FyJs6qAHL+Dzb7sTb+xr7Q+gM1b+1oBsMsQQ4FkVKb6dFlJvLlVssqkRzV05Jzervt9yhnzg==} + engines: {node: '>=6.9.0'} + '@babel/types@7.25.2': resolution: {integrity: sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==} engines: {node: '>=6.9.0'} + '@babel/types@7.25.8': + resolution: {integrity: sha512-JWtuCu8VQsMladxVz/P4HzHUGCAwpuqacmowgXFs5XjxIgKuNjnLokQzuVjlTvIzODaDmpjT3oxcC48vyk9EWg==} + engines: {node: '>=6.9.0'} + '@bcoe/v8-coverage@0.2.3': resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} @@ -1869,6 +2160,150 @@ packages: resolution: {integrity: sha512-TZgLoi00Jc9uv3b6jStH+G8+bCqpHIqFw9DYODz+fVjNh197ksvcYqSndUDHa2oi0HCcK+soI8j4ba3Sa4Pl4w==} engines: {node: '>=12'} + '@esbuild/aix-ppc64@0.23.1': + resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.23.1': + resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.23.1': + resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.23.1': + resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.23.1': + resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.23.1': + resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.23.1': + resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.23.1': + resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.23.1': + resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.23.1': + resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.23.1': + resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.23.1': + resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.23.1': + resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.23.1': + resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.23.1': + resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.23.1': + resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.23.1': + resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-x64@0.23.1': + resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.23.1': + resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.23.1': + resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.23.1': + resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.23.1': + resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.23.1': + resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.23.1': + resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@eslint-community/eslint-utils@4.4.0': resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -1994,15 +2429,14 @@ packages: resolution: {integrity: sha512-sqXgo1SCv+j4VtYEwl/bukuOIBrVgx6euIoCat3Iyx5oeoXwEA2USCoeL0IPubflMxncA2INkqJ/Wr3NGrSgzw==} hasBin: true - '@fastify/busboy@2.1.1': - resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} - engines: {node: '>=14'} - '@graphql-typed-document-node/core@3.2.0': resolution: {integrity: sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==} peerDependencies: graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + '@hapi/bourne@3.0.0': + resolution: {integrity: sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w==} + '@hapi/hoek@9.3.0': resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==} @@ -2172,6 +2606,20 @@ packages: peerDependencies: '@js-joda/core': '>=1.11.0' + '@koa/bodyparser@5.1.1': + resolution: {integrity: sha512-ZBF49xqNVxnmJ+8iXegq+fXPQm9RSX8giNl/aXS5rW1VpNct92wnFbGR/47vfoRJVLARGQ4HVL4WaQ0u8IJVoA==} + engines: {node: '>= 16'} + peerDependencies: + koa: ^2.14.1 + + '@koa/cors@5.0.0': + resolution: {integrity: sha512-x/iUDjcS90W69PryLDIMgFyV21YLTnG9zOpPXS7Bkt2b8AsY3zZsIpOLBkYr9fBcF3HbkKaER5hOBZLfpLgYNw==} + engines: {node: '>= 14.0.0'} + + '@koa/router@12.0.1': + resolution: {integrity: sha512-ribfPYfHb+Uw3b27Eiw6NPqjhIhTpVFzEWLwyc/1Xp+DCdwRRyIlAUODX+9bPARF6aQtUu1+/PHzdNvRzcs/+Q==} + engines: {node: '>= 12'} + '@manypkg/find-root@1.1.0': resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==} @@ -2194,6 +2642,10 @@ packages: resolution: {integrity: sha512-s9ccL/1TTvCP1N//4QR84j/d5D/stx/AI1kPcRgiE4O3KrxyF7ZdL9ca8fmFuN6yh9LAbn/OiGRnOXgvn38Dgg==} engines: {node: '>=14', yarn: 1.x} + '@mswjs/interceptors@0.36.9': + resolution: {integrity: sha512-mMRDUBwSNeCgjSMEWfjoh4Rm9fbyZ7xQ9SBq8eGHiiyRn1ieTip3pNEt0wxWVPPxR4i1Rv9bTkeEbkX7M4c15A==} + engines: {node: '>=18'} + '@multiformats/base-x@4.0.1': resolution: {integrity: sha512-eMk0b9ReBbV23xXU693TAIrLyeO5iTgBZGSJfpqriG8UkYvr/hC9u9pyMlAakDNHWmbhMZCDs6KQO0jzKD8OTw==} @@ -2207,6 +2659,10 @@ packages: resolution: {integrity: sha512-TlaHRXDehJuRNR9TfZDNQ45mMEd5dwUwmicsafcIX4SsNiqnCHKjE/1alYPd/lDRVhxdhUAlv8uEhMCI5zjIJQ==} engines: {node: ^14.21.3 || >=16} + '@noble/hashes@1.4.0': + resolution: {integrity: sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==} + engines: {node: '>= 16'} + '@noble/hashes@1.5.0': resolution: {integrity: sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==} engines: {node: ^14.21.3 || >=16} @@ -2227,6 +2683,15 @@ packages: resolution: {integrity: sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + '@open-draft/deferred-promise@2.2.0': + resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==} + + '@open-draft/logger@0.3.0': + resolution: {integrity: sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==} + + '@open-draft/until@2.1.0': + resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} + '@peculiar/asn1-cms@2.3.13': resolution: {integrity: sha512-joqu8A7KR2G85oLPq+vB+NFr2ro7Ls4ol13Zcse/giPSzUNN0n2k3v8kMpf6QdGUhI13e5SzQYN8AKP8sJ8v4w==} @@ -2438,6 +2903,10 @@ packages: '@sinclair/typebox@0.27.8': resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + '@sindresorhus/is@5.6.0': + resolution: {integrity: sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==} + engines: {node: '>=14.16'} + '@sinonjs/commons@3.0.1': resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} @@ -2467,23 +2936,6 @@ packages: resolution: {integrity: sha512-+AAUvEEFs0vzz1mrgjSgvDkcBtr18d2XEVgJex7QlAqxCKVGfjzZlqL2Q2vOLKYVaXsazhD5LnYiY6B5WMTC3Q==} engines: {node: '>=18'} - '@sphereon/oid4vci-client@0.16.1-fix.173': - resolution: {integrity: sha512-t7i+iZwDHTQKeTq+u9NufXoE5Qsrt9bvspSD6uWYjwidQBAtD4LZHuGyHbygOyVAIX+LUMmZA3A2/ciL29Ra9w==} - engines: {node: '>=18'} - - '@sphereon/oid4vci-common@0.16.1-fix.173': - resolution: {integrity: sha512-nSjOoR1SxF5+S10qjHHLG21bWx8MVbDjERag1zWb7ADlZDO8GXgPqzzEvm1BodW7yci5ugiKOyjIdbPMpg9aFQ==} - engines: {node: '>=18'} - - '@sphereon/oid4vci-issuer@0.16.1-fix.173': - resolution: {integrity: sha512-pP1yH5O2Wplypb/lRHU3hKVzj5cbH4XTdC9CqvWYeWpfvrXfEGtI0amoaNC3NyWjR3FO904lwuSpxh28Xc+MLw==} - engines: {node: '>=18'} - peerDependencies: - awesome-qr: ^2.1.5-rc.0 - peerDependenciesMeta: - awesome-qr: - optional: true - '@sphereon/pex-models@2.3.1': resolution: {integrity: sha512-SByU4cJ0XYA6VZQ/L6lsSiRcFtBPHbFioCeQ4GP7/W/jQ+PSBD7uK2oTnKQ9/0iEiMK/6JYqhKgLs4a9UX3UTQ==} @@ -2591,80 +3043,9 @@ packages: '@stablelib/xchacha20poly1305@1.0.1': resolution: {integrity: sha512-B1Abj0sMJ8h3HNmGnJ7vHBrAvxuNka6cJJoZ1ILN7iuacXp7sUYcgOVEOTLWj+rtQMpspY9tXSCRLPmN1mQNWg==} - '@swc/core-darwin-arm64@1.7.40': - resolution: {integrity: sha512-LRRrCiRJLb1kpQtxMNNsr5W82Inr0dy5Imho+4HQzVx/Ismi0qX4hQBgzJAnyOBNLK1+OBVb/912UVhKXppdfQ==} - engines: {node: '>=10'} - cpu: [arm64] - os: [darwin] - - '@swc/core-darwin-x64@1.7.40': - resolution: {integrity: sha512-Lpl0XK/4fLzS5jsK48opUuGXrqJXwqJckYYPwyGbCfCXm4MsBe+7dX2hq/Kc4YMY25+NeTmzAXhla8TT4WYD/g==} - engines: {node: '>=10'} - cpu: [x64] - os: [darwin] - - '@swc/core-linux-arm-gnueabihf@1.7.40': - resolution: {integrity: sha512-4bEvvjptpoc5BRPr/R419h6fXTEuub+frpxxlxBOEKxgXjAF/S3xdxyPijUAakmW/xXBF0u7OC4KYI+38yQp6g==} - engines: {node: '>=10'} - cpu: [arm] - os: [linux] - - '@swc/core-linux-arm64-gnu@1.7.40': - resolution: {integrity: sha512-v2fBlHJ/6Ovz0L2xFAI9TRiKyl9DTdx139PuAHD9gyzp16Utl/W0MPd4t2cYdkI6hPXE9PsJCSzMOrduh+YoDg==} - engines: {node: '>=10'} - cpu: [arm64] - os: [linux] - - '@swc/core-linux-arm64-musl@1.7.40': - resolution: {integrity: sha512-uMkduQuU4LFVkW6txv8AVArT8GjJVJ5IHoWloXaUBMT447iE8NALmpePdZWhMyj6KV7j0y23CM5rzV/I2eNGLg==} - engines: {node: '>=10'} - cpu: [arm64] - os: [linux] - - '@swc/core-linux-x64-gnu@1.7.40': - resolution: {integrity: sha512-4LZdY1MBSnXyTpW5fpBU/+JGAhkuHT+VnFTDNegRboN5nSPh7y0Yvn4LmIioESV+sWzjKkEXujJPGjrp+oSp5w==} - engines: {node: '>=10'} - cpu: [x64] - os: [linux] - - '@swc/core-linux-x64-musl@1.7.40': - resolution: {integrity: sha512-FPjOwT3SgI6PAwH1O8bhOGBPzuvzOlzKeCtxLaCjruHJu9V8KKBrMTWOZT/FJyYC9mX5Ip1+l9j30UqUZdQxtA==} - engines: {node: '>=10'} - cpu: [x64] - os: [linux] - - '@swc/core-win32-arm64-msvc@1.7.40': - resolution: {integrity: sha512-//ovXdD9GsTmhPmXJlXnIbRQkeuL6PSrYSr7uCMNcclrUdJG0YkO0GMM2afUKYbdJcunylDDWsSS8PFWn0QxmA==} - engines: {node: '>=10'} - cpu: [arm64] - os: [win32] - - '@swc/core-win32-ia32-msvc@1.7.40': - resolution: {integrity: sha512-iD/1auVhHGlhWAPrWmfRWL3w4AvXIWGVXZiSA109/xnRIPiHKb/HqqTp/qB94E/ZHMPRgLKkLTNwamlkueUs8g==} - engines: {node: '>=10'} - cpu: [ia32] - os: [win32] - - '@swc/core-win32-x64-msvc@1.7.40': - resolution: {integrity: sha512-ZlFAV1WFPhhWQ/8esiygmetkb905XIcMMtHRRG0FBGCllO+HVL5nikUaLDgTClz1onmEY9sMXUFQeoPtvliV+w==} - engines: {node: '>=10'} - cpu: [x64] - os: [win32] - - '@swc/core@1.7.40': - resolution: {integrity: sha512-0HIzM5vigVT5IvNum+pPuST9p8xFhN6mhdIKju7qYYeNuZG78lwms/2d8WgjTJJlzp6JlPguXGrMMNzjQw0qNg==} - engines: {node: '>=10'} - peerDependencies: - '@swc/helpers': '*' - peerDependenciesMeta: - '@swc/helpers': - optional: true - - '@swc/counter@0.1.3': - resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} - - '@swc/types@0.1.13': - resolution: {integrity: sha512-JL7eeCk6zWCbiYQg2xQSdLXQJl8Qoc9rXmG2cEKvHe3CKwMHwHGpfOb8frzNLmbycOo6I51qxnLnn9ESf4I20Q==} + '@szmarczak/http-timer@5.0.1': + resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==} + engines: {node: '>=14.16'} '@tokenizer/token@0.3.0': resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} @@ -2684,6 +3065,9 @@ packages: '@tsconfig/node16@1.0.4': resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + '@types/accepts@1.3.7': + resolution: {integrity: sha512-Pay9fq2lM2wXPWbteBsRAGiWH2hig4ZE2asK+mm7kUzlxRTfL961rj89I6zV/E3PcIkDqyuBEcMxFT7rccugeQ==} + '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -2705,6 +3089,15 @@ packages: '@types/connect@3.4.38': resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + '@types/content-disposition@0.5.8': + resolution: {integrity: sha512-QVSSvno3dE0MgO76pJhmv4Qyi/j0Yk9pBp0Y7TJ2Tlj+KCgJWY6qX7nnxCOLkZ3VYRSIk1WTxCvwUSdx6CCLdg==} + + '@types/cookiejar@2.1.5': + resolution: {integrity: sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==} + + '@types/cookies@0.9.0': + resolution: {integrity: sha512-40Zk8qR147RABiQ7NQnBzWzDcjKzNrntB5BAmeGCb2p/MIyOE+4BVvc17wumsUqUw00bJYqoXFHYygQnEFh4/Q==} + '@types/cors@2.8.17': resolution: {integrity: sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==} @@ -2729,6 +3122,12 @@ packages: '@types/graceful-fs@4.1.9': resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} + '@types/http-assert@1.5.5': + resolution: {integrity: sha512-4+tE/lwdAahgZT1g30Jkdm9PzFRde0xwxBNUyRsCitRvCQB90iuA2uJYdUnhnANRcqGXaWOGY4FEoxeElNAK2g==} + + '@types/http-cache-semantics@4.0.4': + resolution: {integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==} + '@types/http-errors@2.0.4': resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==} @@ -2759,12 +3158,24 @@ packages: '@types/jsonpath@0.2.4': resolution: {integrity: sha512-K3hxB8Blw0qgW6ExKgMbXQv2UPZBoE2GqLpVY+yr7nMD2Pq86lsuIzyAaiQ7eMqFL5B6di6pxSkogLJEyEHoGA==} + '@types/keygrip@1.0.6': + resolution: {integrity: sha512-lZuNAY9xeJt7Bx4t4dx0rYCDqGPW8RXhQZK1td7d4H6E9zYbLoOtjBvfwdTKpsyxQI/2jv+armjX/RW+ZNpXOQ==} + + '@types/koa-compose@3.2.8': + resolution: {integrity: sha512-4Olc63RY+MKvxMwVknCUDhRQX1pFQoBZ/lXcRLP69PQkEpze/0cr8LNqJQe5NFb/b19DWi2a5bTi2VAlQzhJuA==} + + '@types/koa@2.15.0': + resolution: {integrity: sha512-7QFsywoE5URbuVnG3loe03QXuGajrnotr3gQkXcEBShORai23MePfFYdhz90FEtBBpkyIYQbVD+evKtloCgX3g==} + '@types/long@4.0.2': resolution: {integrity: sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==} '@types/luxon@3.4.2': resolution: {integrity: sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==} + '@types/methods@1.1.4': + resolution: {integrity: sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==} + '@types/mime@1.3.5': resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} @@ -2780,6 +3191,9 @@ packages: '@types/object-inspect@1.13.0': resolution: {integrity: sha512-lwGTVESDDV+XsQ1pH4UifpJ1f7OtXzQ6QBOX2Afq2bM/T3oOt8hF6exJMjjIjtEWeAN2YAo25J7HxWh97CCz9w==} + '@types/oidc-provider@8.5.2': + resolution: {integrity: sha512-NiD3VG49+cRCAAe8+uZLM4onOcX8y9+cwaml8JG1qlgc98rWoCRgsnOB4Ypx+ysays5jiwzfUgT0nWyXPB/9uQ==} + '@types/qs@6.9.15': resolution: {integrity: sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==} @@ -2810,6 +3224,12 @@ packages: '@types/stack-utils@2.0.3': resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} + '@types/superagent@8.1.9': + resolution: {integrity: sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ==} + + '@types/supertest@6.0.2': + resolution: {integrity: sha512-137ypx2lk/wTQbW6An6safu9hXmajAifU/s7szAHLN/FeIm5w7yR0Wkl9fdJMRSHwOn4HLAI0DaB2TOORuhPDg==} + '@types/through@0.0.33': resolution: {integrity: sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ==} @@ -3330,6 +3750,11 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + browserslist@4.24.0: + resolution: {integrity: sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + bs-logger@0.2.6: resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==} engines: {node: '>= 6'} @@ -3381,6 +3806,18 @@ packages: resolution: {integrity: sha512-B+L5iIa9mgcjLbliir2th36yEwPftrzteHYujzsx3dFP/31GCHcIeS8f5MGd80odLOjaOvSpU3EEAmRQptkxLQ==} engines: {node: ^16.14.0 || >=18.0.0} + cache-content-type@1.0.1: + resolution: {integrity: sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==} + engines: {node: '>= 6.0.0'} + + cacheable-lookup@7.0.0: + resolution: {integrity: sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==} + engines: {node: '>=14.16'} + + cacheable-request@10.2.14: + resolution: {integrity: sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==} + engines: {node: '>=14.16'} + call-bind@1.0.7: resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} engines: {node: '>= 0.4'} @@ -3412,6 +3849,9 @@ packages: caniuse-lite@1.0.30001651: resolution: {integrity: sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==} + caniuse-lite@1.0.30001668: + resolution: {integrity: sha512-nWLrdxqCdblixUO+27JtGJJE/txpJlyUy5YN1u53wLZkP0emYCo5zgS6QYft7VUYR42LGgi/S5hdLZTrnyIddw==} + canonicalize@1.0.8: resolution: {integrity: sha512-0CNTVCLZggSh7bc5VkX5WWPWO+cyZbNd07IHIsSXLia/eAq+r836hgk+8BKoEh7949Mda87VUOitx5OddVj64A==} @@ -3511,6 +3951,10 @@ packages: resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==} engines: {node: '>=0.8'} + co-body@6.2.0: + resolution: {integrity: sha512-Kbpv2Yd1NdL1V/V4cwLVxraHDV6K8ayohr2rmH0J87Er8+zJjcTa6dAn9QMPC9CRgU8+aNajKbSf1TzDB1yKPA==} + engines: {node: '>=8.0.0'} + co@4.6.0: resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} @@ -3584,6 +4028,9 @@ packages: compare-versions@6.1.1: resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==} + component-emitter@1.3.1: + resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==} + component-type@1.2.2: resolution: {integrity: sha512-99VUHREHiN5cLeHm3YLq312p6v+HUEcwtLCAtelvUDI6+SH5g5Cr85oNR2S1o6ywzL0ykMbuwLzM2ANocjEOIA==} @@ -3627,6 +4074,13 @@ packages: resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} engines: {node: '>= 0.6'} + cookiejar@2.1.4: + resolution: {integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==} + + cookies@0.9.1: + resolution: {integrity: sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==} + engines: {node: '>= 0.8'} + core-js-compat@3.38.1: resolution: {integrity: sha512-JRH6gfXxGmrzF3tZ57lFx97YARxCXPaMzPo6jELZhv88pBH5VXpQ+y0znKGlFnzuaihqhLbefxSJxWJMPtfDzw==} @@ -3753,6 +4207,10 @@ packages: resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==} engines: {node: '>=0.10'} + decompress-response@6.0.0: + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} + engines: {node: '>=10'} + dedent@1.5.3: resolution: {integrity: sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==} peerDependencies: @@ -3761,6 +4219,9 @@ packages: babel-plugin-macros: optional: true + deep-equal@1.0.1: + resolution: {integrity: sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw==} + deep-extend@0.6.0: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} engines: {node: '>=4.0.0'} @@ -3783,6 +4244,10 @@ packages: defaults@1.0.4: resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} + defer-to-connect@2.0.1: + resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==} + engines: {node: '>=10'} + define-data-property@1.1.4: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} @@ -3809,6 +4274,10 @@ packages: denodeify@1.2.1: resolution: {integrity: sha512-KNTihKNmQENUZeKu5fzfpzRqR5S2VMp4gl9RFHiWzj9DfvYQPMJ6XHKNaQxaGCXwPk6y9yme3aUoaiAe+KX+vg==} + depd@1.1.2: + resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==} + engines: {node: '>= 0.6'} + depd@2.0.0: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} @@ -3840,6 +4309,9 @@ packages: resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} engines: {node: '>=8'} + dezalgo@1.0.4: + resolution: {integrity: sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==} + did-jwt-vc@3.2.15: resolution: {integrity: sha512-M/WPiL34CQUiN4bvWnZ0OFHJ3usPtstfQfbNbHAWHvwjeCGi7nAdv62VXHgy2xIhJMc790hH7PsMN3i6SCGEyg==} engines: {node: '>=18'} @@ -3904,6 +4376,9 @@ packages: electron-to-chromium@1.5.13: resolution: {integrity: sha512-lbBcvtIJ4J6sS4tb5TLp1b4LyfCdMkwStzXPyAgVgTRAsep4bvrAGaBOP7ZJtQMNJpSQ9SqG4brWOroNaQtm7Q==} + electron-to-chromium@1.5.38: + resolution: {integrity: sha512-VbeVexmZ1IFh+5EfrYz1I0HTzHVIlJa112UEWhciPyeOcKJGeTv6N8WnG4wsQB81DGCaVEGhpSb6o6a8WYFXXg==} + elliptic@6.5.4: resolution: {integrity: sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==} @@ -3995,6 +4470,11 @@ packages: resolution: {integrity: sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==} engines: {node: '>=0.12'} + esbuild@0.23.1: + resolution: {integrity: sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==} + engines: {node: '>=18'} + hasBin: true + escalade@3.1.2: resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} engines: {node: '>=6'} @@ -4091,7 +4571,6 @@ packages: eslint@8.57.0: resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. hasBin: true esniff@2.0.1: @@ -4132,6 +4611,10 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + eta@3.5.0: + resolution: {integrity: sha512-e3x3FBvGzeCIHhF+zhK8FZA2vC5uFn6b4HJjegUbIWrDb4mJ7JjTGMJY9VGIbRVpmSwHopNiaJibhjIr+HfLug==} + engines: {node: '>=6.0.0'} + etag@1.8.1: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} @@ -4245,6 +4728,9 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fast-safe-stringify@2.1.1: + resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + fast-text-encoding@1.0.6: resolution: {integrity: sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w==} @@ -4384,6 +4870,10 @@ packages: resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} engines: {node: '>=14'} + form-data-encoder@2.1.4: + resolution: {integrity: sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==} + engines: {node: '>= 14.17'} + form-data@3.0.1: resolution: {integrity: sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==} engines: {node: '>= 6'} @@ -4399,6 +4889,9 @@ packages: resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} engines: {node: '>=12.20.0'} + formidable@3.5.2: + resolution: {integrity: sha512-Jqc1btCy3QzRbJaICGwKcBfGWuLADRerLzDqi2NwSt/UkXLsHJw2TVResiaoBufHVHy9aSgClOHCeJsSsFLTbg==} + forwarded@0.2.0: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} @@ -4549,6 +5042,10 @@ packages: gopd@1.0.1: resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} + got@13.0.0: + resolution: {integrity: sha512-XfBk1CxOOScDcMr9O1yKkNaQyy865NbYs+F7dr4H0LZMVgCj2Le59k6PqbNHoL5ToeaEQUYh6c6yMfVcc6SJxA==} + engines: {node: '>=16'} + graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} @@ -4622,6 +5119,10 @@ packages: resolution: {integrity: sha512-cnN7bQUm65UWOy6cbGcCcZ3rpwW8Q/j4OP5aWRhEry4Z2t2aR1cjrbp0BS+KiBN0smvP1caBgAuxutvyvJILzQ==} engines: {node: '>=8'} + hexoid@2.0.0: + resolution: {integrity: sha512-qlspKUK7IlSQv2o+5I7yhUd7TxlOG2Vr5LTa3ve2XSNVKAL/n/u/7KLvKmFNimomDIKvZFXWHv0T12mv7rT8Aw==} + engines: {node: '>=8'} + highlight.js@10.7.3: resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} @@ -4635,10 +5136,25 @@ packages: html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + http-assert@1.5.0: + resolution: {integrity: sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w==} + engines: {node: '>= 0.8'} + + http-cache-semantics@4.1.1: + resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} + + http-errors@1.8.1: + resolution: {integrity: sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==} + engines: {node: '>= 0.6'} + http-errors@2.0.0: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} engines: {node: '>= 0.8'} + http2-wrapper@2.2.1: + resolution: {integrity: sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==} + engines: {node: '>=10.19.0'} + https-proxy-agent@5.0.1: resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} engines: {node: '>= 6'} @@ -4692,6 +5208,10 @@ packages: resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} engines: {node: '>=8'} + inflation@2.1.0: + resolution: {integrity: sha512-t54PPJHG1Pp7VQvxyVCJ9mBbjG3Hqryges9bXoOO6GExCPa+//i/d5GSuFtpx3ALLd7lgIAur6zrIlBQyJuMlQ==} + engines: {node: '>= 0.8.0'} + inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. @@ -4826,6 +5346,9 @@ packages: resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} engines: {node: '>= 0.4'} + is-node-process@1.2.0: + resolution: {integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==} + is-number-object@1.0.7: resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} engines: {node: '>= 0.4'} @@ -5120,6 +5643,9 @@ packages: join-component@1.1.0: resolution: {integrity: sha512-bF7vcQxbODoGK1imE2P9GS9aw4zD0Sd+Hni68IMZLj7zRnquH7dXUmMw9hDI5S/Jzt7q+IyTXN0rSg2GI0IKhQ==} + jose@5.8.0: + resolution: {integrity: sha512-E7CqYpL/t7MMnfGnK/eg416OsFCVUrU/Y3Vwe7QjKhu/BkS1Ms455+2xsqZQVN57/U2MHMBvEb5SrmAZWAIntA==} + js-base64@3.7.7: resolution: {integrity: sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==} @@ -5191,6 +5717,11 @@ packages: engines: {node: '>=4'} hasBin: true + jsesc@3.0.2: + resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==} + engines: {node: '>=6'} + hasBin: true + json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} @@ -5257,6 +5788,10 @@ packages: resolution: {integrity: sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==} engines: {node: '>=18'} + keygrip@1.1.0: + resolution: {integrity: sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==} + engines: {node: '>= 0.6'} + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -5268,6 +5803,17 @@ packages: resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} engines: {node: '>=6'} + koa-compose@4.1.0: + resolution: {integrity: sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==} + + koa-convert@2.0.0: + resolution: {integrity: sha512-asOvN6bFlSnxewce2e/DK3p4tltyfC4VM7ZwuTuepI7dEQVcvpyFuBcEARu1+Hxg8DIwytce2n7jrZtRlPrARA==} + engines: {node: '>= 10'} + + koa@2.15.3: + resolution: {integrity: sha512-j/8tY9j5t+GVMLeioLaxweJiKUayFhlGqNTzf2ZGwL0ZCQijd2RLHK0SLW5Tsko8YyyqCZC2cojIb0/s62qTAg==} + engines: {node: ^4.8.4 || ^6.10.1 || ^7.10.1 || >= 8.1.4} + ky-universal@0.11.0: resolution: {integrity: sha512-65KyweaWvk+uKKkCrfAf+xqN2/epw1IJDtlyCPxYffFCMR8u1sp2U65NtWpnozYfZxQ6IUzIlvUcw+hQ82U2Xw==} engines: {node: '>=14.16'} @@ -5453,6 +5999,10 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true + lowercase-keys@3.0.0: + resolution: {integrity: sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} @@ -5604,6 +6154,10 @@ packages: resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==} engines: {node: '>=8.6'} + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + mime-db@1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} @@ -5634,6 +6188,14 @@ packages: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} + mimic-response@3.1.0: + resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} + engines: {node: '>=10'} + + mimic-response@4.0.0: + resolution: {integrity: sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + minimalistic-assert@1.0.1: resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} @@ -5750,6 +6312,11 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + nanoid@5.0.7: + resolution: {integrity: sha512-oLxFY2gd2IqnjcYyOXD8XGCftpGtZP2AbHbOkthDkvRywH5ayNtPVy9YlOPcHckXzbLTCHpkb7FB+yuxKV13pQ==} + engines: {node: ^18 || >=20} + hasBin: true + natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} @@ -5778,9 +6345,13 @@ packages: resolution: {integrity: sha512-WDD0bdg9mbq6F4mRxEYcPWwfA1vxd0mrvKOyxI7Xj/atfRHVeutzuWByG//jfm4uPzp0y4Kj051EORCBSQMycw==} engines: {node: '>=12.0.0'} - nock@13.5.5: - resolution: {integrity: sha512-XKYnqUrCwXC8DGG1xX4YH5yNIrlh9c065uaMZZHUoeUUINTOyt+x/G+ezYk0Ft6ExSREVIs+qBJDK503viTfFA==} - engines: {node: '>= 10.13'} + nock@14.0.0-beta.15: + resolution: {integrity: sha512-rp72chatxoZbR/2cYHwtb+IX6n6kkanYKGN2PKn4c12JBrj9n4xGUKFykuQHB+Gkz3fynlikFbMH2LI6VoebuQ==} + engines: {node: '>= 18'} + + nock@14.0.0-beta.16: + resolution: {integrity: sha512-H6ZyT+Naz9wfy0gNrhD0m+VIkCq9li/eaNQPEUEjXg06gsLR3/jDctROt44Z+iT3gFnkTQ0wXtwKJPdvbueBbg==} + engines: {node: '>= 18'} node-addon-api@3.2.1: resolution: {integrity: sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==} @@ -5840,6 +6411,10 @@ packages: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} + normalize-url@8.0.1: + resolution: {integrity: sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==} + engines: {node: '>=14.16'} + npm-package-arg@7.0.0: resolution: {integrity: sha512-xXxr8y5U0kl8dVkz2oK7yZjPBvqM2fwaO5l3Yg13p03v8+E3qQcD0JNhHzjL1vyGgxcKkD0cco+NLR72iuPk3g==} @@ -5865,6 +6440,10 @@ packages: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} + object-hash@3.0.0: + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} + engines: {node: '>= 6'} + object-inspect@1.13.2: resolution: {integrity: sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==} engines: {node: '>= 0.4'} @@ -5889,6 +6468,13 @@ packages: resolution: {integrity: sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==} engines: {node: '>= 0.4'} + oidc-provider@8.5.1: + resolution: {integrity: sha512-Bm3EyxN68/KS76IlciJ3+4pnVtfdRWL+NghWpIF0XQbiRT1gzc6Qf/cyFmpL9yieko/jXYZ/uLHUv77jD00qww==} + + oidc-token-hash@5.0.3: + resolution: {integrity: sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw==} + engines: {node: ^10.13.0 || >=12.0.0} + on-finished@2.3.0: resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==} engines: {node: '>= 0.8'} @@ -5912,6 +6498,9 @@ packages: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} + only@0.0.2: + resolution: {integrity: sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ==} + open@6.4.0: resolution: {integrity: sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg==} engines: {node: '>=8'} @@ -5955,6 +6544,13 @@ packages: outdent@0.5.0: resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} + outvariant@1.4.3: + resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} + + p-cancelable@3.0.0: + resolution: {integrity: sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==} + engines: {node: '>=12.20'} + p-filter@2.1.0: resolution: {integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==} engines: {node: '>=8'} @@ -6063,6 +6659,9 @@ packages: path-to-regexp@0.1.7: resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} + path-to-regexp@6.2.2: + resolution: {integrity: sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==} + path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -6233,6 +6832,14 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + quick-lru@5.1.1: + resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} + engines: {node: '>=10'} + + quick-lru@7.0.0: + resolution: {integrity: sha512-MX8gB7cVYTrYcFfAnfLlhRd0+Toyl8yX8uBx1MrX7K0jegiz9TumwOK27ldXrgDlHRdVi+MqU9Ssw6dr4BNreg==} + engines: {node: '>=18'} + range-parser@1.2.1: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} @@ -6359,6 +6966,10 @@ packages: resolution: {integrity: sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==} engines: {node: '>=4'} + regenerate-unicode-properties@10.2.0: + resolution: {integrity: sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==} + engines: {node: '>=4'} + regenerate@1.4.2: resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==} @@ -6379,6 +6990,17 @@ packages: resolution: {integrity: sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==} engines: {node: '>=4'} + regexpu-core@6.1.1: + resolution: {integrity: sha512-k67Nb9jvwJcJmVpw0jPttR1/zVfnKf8Km0IPatrU/zJ5XeG3+Slx0xLXs9HByJSzXzrlz5EDvN6yLNMDc2qdnw==} + engines: {node: '>=4'} + + regjsgen@0.8.0: + resolution: {integrity: sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==} + + regjsparser@0.11.1: + resolution: {integrity: sha512-1DHODs4B8p/mQHU9kr+jv8+wIC9mtG4eBHxWxIq5mhjE3D5oORhCc6deRKzTjs9DcfRFmj9BHSDguZklqCGFWQ==} + hasBin: true + regjsparser@0.9.1: resolution: {integrity: sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==} hasBin: true @@ -6404,6 +7026,9 @@ packages: requires-port@1.0.0: resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + resolve-alpn@1.2.1: + resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==} + resolve-cwd@3.0.0: resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} engines: {node: '>=8'} @@ -6434,6 +7059,10 @@ packages: resolve@1.7.1: resolution: {integrity: sha512-c7rwLofp8g1U+h1KNyHL/jicrKg1Ek4q+Lr33AL65uZTinUZHe30D5HlyN5V9NW0JX1D5dXQ4jqW5l7Sy/kGfw==} + responselike@3.0.0: + resolution: {integrity: sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==} + engines: {node: '>=14.16'} + restore-cursor@2.0.0: resolution: {integrity: sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==} engines: {node: '>=4'} @@ -6703,6 +7332,9 @@ packages: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} + strict-event-emitter@0.5.1: + resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} + strict-uri-encode@2.0.0: resolution: {integrity: sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==} engines: {node: '>=4'} @@ -6796,6 +7428,14 @@ packages: sudo-prompt@9.2.1: resolution: {integrity: sha512-Mu7R0g4ig9TUuGSxJavny5Rv0egCEtpZRNMrZaYS1vxkiIxGiGUwoezU3LazIQ+KE04hTrTfNPgxU5gzi7F5Pw==} + superagent@9.0.2: + resolution: {integrity: sha512-xuW7dzkUpcJq7QnhOsnNUgtYp3xRwpt2F7abdRYIpCsAt0hhUqia0EdxyXZQQpNmGtsCzYHryaKSV3q3GJnq7w==} + engines: {node: '>=14.18.0'} + + supertest@7.0.0: + resolution: {integrity: sha512-qlsr7fIC0lSddmA3tzojvzubYxvlGtzumcdHgPwbFWMISQwL22MhM2Y3LNt+6w9Yyx7559VW5ab70dgphm8qQA==} + engines: {node: '>=14.18.0'} + supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} @@ -6992,6 +7632,15 @@ packages: resolution: {integrity: sha512-oDWuGVONxhVEBtschLf2cs/Jy8i7h1T+CpdkTNWQgdAF7DhRo2G8vMCgILKe7ojdEkLhICWgI1LYSSKaJsRgcw==} engines: {node: '>=16'} + tsscmp@1.0.6: + resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==} + engines: {node: '>=0.6.x'} + + tsx@4.19.0: + resolution: {integrity: sha512-bV30kM7bsLZKZIOCHeMNVMJ32/LuJzLVajkQI/qf92J2Qr08ueLQvW00PUZGiuLPP760UINwupgUj8qrSCPUKg==} + engines: {node: '>=18.0.0'} + hasBin: true + tsyringe@4.8.0: resolution: {integrity: sha512-YB1FG+axdxADa3ncEtRnQCFq/M0lALGLxSZeVNbTU8NqhOVc51nnv2CISTcvc1kyv6EGPtXVr0v6lWeDxiijOA==} engines: {node: '>= 6.0.0'} @@ -7159,9 +7808,9 @@ packages: undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} - undici@5.28.4: - resolution: {integrity: sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==} - engines: {node: '>=14.0'} + undici@6.20.1: + resolution: {integrity: sha512-AjQF1QsmqfJys+LXfGTNum+qw4S88CojRInG/6t31W/1fk6G59s92bnAvGz5Cmur+kQv2SURXEvvudLmbrE8QA==} + engines: {node: '>=18.17'} unicode-canonical-property-names-ecmascript@2.0.0: resolution: {integrity: sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==} @@ -7506,6 +8155,10 @@ packages: resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} engines: {node: '>=12'} + ylru@1.4.0: + resolution: {integrity: sha512-2OQsPNEmBCvXuFlIni/a+Rn+R2pHW9INm0BxXJ4hVDA8TirqMj+J/Rp9ItLatT/5pZqWwefVrTQcHpixsxnVlA==} + engines: {node: '>= 4.0.0'} + yn@3.1.1: resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} engines: {node: '>=6'} @@ -7555,23 +8208,45 @@ snapshots: '@jridgewell/gen-mapping': 0.3.5 '@jridgewell/trace-mapping': 0.3.25 - '@animo-id/expo-secure-environment@0.0.1-alpha.0(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2)))(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))(react@18.3.1))(react@18.3.1)': + '@animo-id/expo-secure-environment@0.0.1-alpha.0(expo@51.0.29(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8)))(react-native@0.71.19(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(react@18.3.1))(react@18.3.1)': dependencies: '@peculiar/asn1-ecc': 2.3.13 '@peculiar/asn1-schema': 2.3.13 '@peculiar/asn1-x509': 2.3.13 - expo: 51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2)) + expo: 51.0.29(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8)) react: 18.3.1 - react-native: 0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))(react@18.3.1) + react-native: 0.71.19(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(react@18.3.1) '@animo-id/mdoc@0.2.38': dependencies: compare-versions: 6.1.1 - '@animo-id/react-native-bbs-signatures@0.1.0(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))(react@18.3.1))(react@18.3.1)': + '@animo-id/oauth2-utils@0.1.3(typescript@5.5.4)': + dependencies: + buffer: 6.0.3 + valibot: 0.42.1(typescript@5.5.4) + transitivePeerDependencies: + - typescript + + '@animo-id/oauth2@0.1.3(typescript@5.5.4)': + dependencies: + '@animo-id/oauth2-utils': 0.1.3(typescript@5.5.4) + valibot: 0.42.1(typescript@5.5.4) + transitivePeerDependencies: + - typescript + + '@animo-id/oid4vci@0.1.3(typescript@5.5.4)': + dependencies: + '@animo-id/oauth2': 0.1.3(typescript@5.5.4) + '@animo-id/oauth2-utils': 0.1.3(typescript@5.5.4) + valibot: 0.42.1(typescript@5.5.4) + transitivePeerDependencies: + - typescript + + '@animo-id/react-native-bbs-signatures@0.1.0(react-native@0.71.19(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(react@18.3.1))(react@18.3.1)': dependencies: react: 18.3.1 - react-native: 0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))(react@18.3.1) + react-native: 0.71.19(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(react@18.3.1) '@astronautlabs/jsonpath@1.1.2': dependencies: @@ -7581,15 +8256,22 @@ snapshots: '@babel/code-frame@7.10.4': dependencies: - '@babel/highlight': 7.24.7 + '@babel/highlight': 7.25.7 '@babel/code-frame@7.24.7': dependencies: '@babel/highlight': 7.24.7 picocolors: 1.0.1 + '@babel/code-frame@7.25.7': + dependencies: + '@babel/highlight': 7.25.7 + picocolors: 1.0.1 + '@babel/compat-data@7.25.2': {} + '@babel/compat-data@7.25.8': {} + '@babel/core@7.25.2': dependencies: '@ampproject/remapping': 2.3.0 @@ -7610,9 +8292,29 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/core@7.25.8': + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.25.7 + '@babel/generator': 7.25.7 + '@babel/helper-compilation-targets': 7.25.7 + '@babel/helper-module-transforms': 7.25.7(@babel/core@7.25.8) + '@babel/helpers': 7.25.7 + '@babel/parser': 7.25.8 + '@babel/template': 7.25.7 + '@babel/traverse': 7.25.7 + '@babel/types': 7.25.8 + convert-source-map: 2.0.0 + debug: 4.3.6 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + '@babel/generator@7.2.0': dependencies: - '@babel/types': 7.25.2 + '@babel/types': 7.25.8 jsesc: 2.5.2 lodash: 4.17.21 source-map: 0.5.7 @@ -7625,14 +8327,25 @@ snapshots: '@jridgewell/trace-mapping': 0.3.25 jsesc: 2.5.2 + '@babel/generator@7.25.7': + dependencies: + '@babel/types': 7.25.8 + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + jsesc: 3.0.2 + '@babel/helper-annotate-as-pure@7.24.7': dependencies: '@babel/types': 7.25.2 - '@babel/helper-builder-binary-assignment-operator-visitor@7.24.7': + '@babel/helper-annotate-as-pure@7.25.7': dependencies: - '@babel/traverse': 7.25.3 - '@babel/types': 7.25.2 + '@babel/types': 7.25.8 + + '@babel/helper-builder-binary-assignment-operator-visitor@7.25.7': + dependencies: + '@babel/traverse': 7.25.7 + '@babel/types': 7.25.8 transitivePeerDependencies: - supports-color @@ -7644,6 +8357,14 @@ snapshots: lru-cache: 5.1.1 semver: 6.3.1 + '@babel/helper-compilation-targets@7.25.7': + dependencies: + '@babel/compat-data': 7.25.8 + '@babel/helper-validator-option': 7.25.7 + browserslist: 4.24.0 + lru-cache: 5.1.1 + semver: 6.3.1 + '@babel/helper-create-class-features-plugin@7.25.0(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 @@ -7657,6 +8378,45 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-create-class-features-plugin@7.25.0(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-annotate-as-pure': 7.24.7 + '@babel/helper-member-expression-to-functions': 7.24.8 + '@babel/helper-optimise-call-expression': 7.24.7 + '@babel/helper-replace-supers': 7.25.0(@babel/core@7.25.8) + '@babel/helper-skip-transparent-expression-wrappers': 7.24.7 + '@babel/traverse': 7.25.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/helper-create-class-features-plugin@7.25.7(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-annotate-as-pure': 7.25.7 + '@babel/helper-member-expression-to-functions': 7.25.7 + '@babel/helper-optimise-call-expression': 7.25.7 + '@babel/helper-replace-supers': 7.25.7(@babel/core@7.25.2) + '@babel/helper-skip-transparent-expression-wrappers': 7.25.7 + '@babel/traverse': 7.25.7 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/helper-create-class-features-plugin@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-annotate-as-pure': 7.25.7 + '@babel/helper-member-expression-to-functions': 7.25.7 + '@babel/helper-optimise-call-expression': 7.25.7 + '@babel/helper-replace-supers': 7.25.7(@babel/core@7.25.8) + '@babel/helper-skip-transparent-expression-wrappers': 7.25.7 + '@babel/traverse': 7.25.7 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + '@babel/helper-create-regexp-features-plugin@7.25.2(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 @@ -7664,6 +8424,27 @@ snapshots: regexpu-core: 5.3.2 semver: 6.3.1 + '@babel/helper-create-regexp-features-plugin@7.25.2(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-annotate-as-pure': 7.24.7 + regexpu-core: 5.3.2 + semver: 6.3.1 + + '@babel/helper-create-regexp-features-plugin@7.25.7(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-annotate-as-pure': 7.25.7 + regexpu-core: 6.1.1 + semver: 6.3.1 + + '@babel/helper-create-regexp-features-plugin@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-annotate-as-pure': 7.25.7 + regexpu-core: 6.1.1 + semver: 6.3.1 + '@babel/helper-define-polyfill-provider@0.6.2(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 @@ -7675,7 +8456,18 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-environment-visitor@7.24.7': + '@babel/helper-define-polyfill-provider@0.6.2(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-compilation-targets': 7.25.2 + '@babel/helper-plugin-utils': 7.24.8 + debug: 4.3.6 + lodash.debounce: 4.0.8 + resolve: 1.22.8 + transitivePeerDependencies: + - supports-color + + '@babel/helper-environment-visitor@7.24.7': dependencies: '@babel/types': 7.25.2 @@ -7686,6 +8478,13 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-member-expression-to-functions@7.25.7': + dependencies: + '@babel/traverse': 7.25.7 + '@babel/types': 7.25.8 + transitivePeerDependencies: + - supports-color + '@babel/helper-module-imports@7.24.7': dependencies: '@babel/traverse': 7.25.3 @@ -7693,6 +8492,13 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-module-imports@7.25.7': + dependencies: + '@babel/traverse': 7.25.7 + '@babel/types': 7.25.8 + transitivePeerDependencies: + - supports-color + '@babel/helper-module-transforms@7.25.2(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 @@ -7703,12 +8509,48 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-module-transforms@7.25.2(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-module-imports': 7.24.7 + '@babel/helper-simple-access': 7.24.7 + '@babel/helper-validator-identifier': 7.24.7 + '@babel/traverse': 7.25.3 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.25.7(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-module-imports': 7.25.7 + '@babel/helper-simple-access': 7.25.7 + '@babel/helper-validator-identifier': 7.25.7 + '@babel/traverse': 7.25.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-module-imports': 7.25.7 + '@babel/helper-simple-access': 7.25.7 + '@babel/helper-validator-identifier': 7.25.7 + '@babel/traverse': 7.25.7 + transitivePeerDependencies: + - supports-color + '@babel/helper-optimise-call-expression@7.24.7': dependencies: '@babel/types': 7.25.2 + '@babel/helper-optimise-call-expression@7.25.7': + dependencies: + '@babel/types': 7.25.8 + '@babel/helper-plugin-utils@7.24.8': {} + '@babel/helper-plugin-utils@7.25.7': {} + '@babel/helper-remap-async-to-generator@7.25.0(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 @@ -7718,6 +8560,33 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-remap-async-to-generator@7.25.0(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-annotate-as-pure': 7.24.7 + '@babel/helper-wrap-function': 7.25.0 + '@babel/traverse': 7.25.3 + transitivePeerDependencies: + - supports-color + + '@babel/helper-remap-async-to-generator@7.25.7(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-annotate-as-pure': 7.25.7 + '@babel/helper-wrap-function': 7.25.7 + '@babel/traverse': 7.25.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-remap-async-to-generator@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-annotate-as-pure': 7.25.7 + '@babel/helper-wrap-function': 7.25.7 + '@babel/traverse': 7.25.7 + transitivePeerDependencies: + - supports-color + '@babel/helper-replace-supers@7.25.0(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 @@ -7727,6 +8596,33 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-replace-supers@7.25.0(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-member-expression-to-functions': 7.24.8 + '@babel/helper-optimise-call-expression': 7.24.7 + '@babel/traverse': 7.25.3 + transitivePeerDependencies: + - supports-color + + '@babel/helper-replace-supers@7.25.7(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-member-expression-to-functions': 7.25.7 + '@babel/helper-optimise-call-expression': 7.25.7 + '@babel/traverse': 7.25.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-replace-supers@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-member-expression-to-functions': 7.25.7 + '@babel/helper-optimise-call-expression': 7.25.7 + '@babel/traverse': 7.25.7 + transitivePeerDependencies: + - supports-color + '@babel/helper-simple-access@7.24.7': dependencies: '@babel/traverse': 7.25.3 @@ -7734,6 +8630,13 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-simple-access@7.25.7': + dependencies: + '@babel/traverse': 7.25.7 + '@babel/types': 7.25.8 + transitivePeerDependencies: + - supports-color + '@babel/helper-skip-transparent-expression-wrappers@7.24.7': dependencies: '@babel/traverse': 7.25.3 @@ -7741,12 +8644,25 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-skip-transparent-expression-wrappers@7.25.7': + dependencies: + '@babel/traverse': 7.25.7 + '@babel/types': 7.25.8 + transitivePeerDependencies: + - supports-color + '@babel/helper-string-parser@7.24.8': {} + '@babel/helper-string-parser@7.25.7': {} + '@babel/helper-validator-identifier@7.24.7': {} + '@babel/helper-validator-identifier@7.25.7': {} + '@babel/helper-validator-option@7.24.8': {} + '@babel/helper-validator-option@7.25.7': {} + '@babel/helper-wrap-function@7.25.0': dependencies: '@babel/template': 7.25.0 @@ -7755,11 +8671,24 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-wrap-function@7.25.7': + dependencies: + '@babel/template': 7.25.7 + '@babel/traverse': 7.25.7 + '@babel/types': 7.25.8 + transitivePeerDependencies: + - supports-color + '@babel/helpers@7.25.0': dependencies: '@babel/template': 7.25.0 '@babel/types': 7.25.2 + '@babel/helpers@7.25.7': + dependencies: + '@babel/template': 7.25.7 + '@babel/types': 7.25.8 + '@babel/highlight@7.24.7': dependencies: '@babel/helper-validator-identifier': 7.24.7 @@ -7767,42 +8696,88 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.0.1 + '@babel/highlight@7.25.7': + dependencies: + '@babel/helper-validator-identifier': 7.25.7 + chalk: 2.4.2 + js-tokens: 4.0.0 + picocolors: 1.0.1 + '@babel/parser@7.25.3': dependencies: '@babel/types': 7.25.2 - '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.3(@babel/core@7.25.2)': + '@babel/parser@7.25.8': + dependencies: + '@babel/types': 7.25.8 + + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.7(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.24.8 - '@babel/traverse': 7.25.3 + '@babel/helper-plugin-utils': 7.25.7 + '@babel/traverse': 7.25.7 transitivePeerDependencies: - supports-color - '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.25.0(@babel/core@7.25.2)': + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + '@babel/traverse': 7.25.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.25.7(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.24.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.25.0(@babel/core@7.25.2)': + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.25.7(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.24.8 + '@babel/helper-plugin-utils': 7.25.7 - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.24.7(@babel/core@7.25.2)': + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.25.7(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.24.8 - '@babel/helper-skip-transparent-expression-wrappers': 7.24.7 - '@babel/plugin-transform-optional-chaining': 7.24.8(@babel/core@7.25.2) + '@babel/helper-plugin-utils': 7.25.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.7 + '@babel/plugin-transform-optional-chaining': 7.25.8(@babel/core@7.25.2) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.7 + '@babel/plugin-transform-optional-chaining': 7.25.8(@babel/core@7.25.8) transitivePeerDependencies: - supports-color - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.25.0(@babel/core@7.25.2)': + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.25.7(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.24.8 - '@babel/traverse': 7.25.3 + '@babel/helper-plugin-utils': 7.25.7 + '@babel/traverse': 7.25.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + '@babel/traverse': 7.25.7 transitivePeerDependencies: - supports-color @@ -7816,6 +8791,16 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/plugin-proposal-async-generator-functions@7.20.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-environment-visitor': 7.24.7 + '@babel/helper-plugin-utils': 7.24.8 + '@babel/helper-remap-async-to-generator': 7.25.0(@babel/core@7.25.8) + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.25.8) + transitivePeerDependencies: + - supports-color + '@babel/plugin-proposal-class-properties@7.18.6(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 @@ -7824,14 +8809,32 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/plugin-proposal-class-properties@7.18.6(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-create-class-features-plugin': 7.25.0(@babel/core@7.25.8) + '@babel/helper-plugin-utils': 7.24.8 + transitivePeerDependencies: + - supports-color + '@babel/plugin-proposal-decorators@7.24.7(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 - '@babel/helper-create-class-features-plugin': 7.25.0(@babel/core@7.25.2) - '@babel/helper-plugin-utils': 7.24.8 + '@babel/helper-create-class-features-plugin': 7.25.7(@babel/core@7.25.2) + '@babel/helper-plugin-utils': 7.25.7 '@babel/plugin-syntax-decorators': 7.24.7(@babel/core@7.25.2) transitivePeerDependencies: - supports-color + optional: true + + '@babel/plugin-proposal-decorators@7.24.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-create-class-features-plugin': 7.25.7(@babel/core@7.25.8) + '@babel/helper-plugin-utils': 7.25.7 + '@babel/plugin-syntax-decorators': 7.24.7(@babel/core@7.25.8) + transitivePeerDependencies: + - supports-color '@babel/plugin-proposal-export-default-from@7.24.7(@babel/core@7.25.2)': dependencies: @@ -7839,6 +8842,12 @@ snapshots: '@babel/helper-plugin-utils': 7.24.8 '@babel/plugin-syntax-export-default-from': 7.24.7(@babel/core@7.25.2) + '@babel/plugin-proposal-export-default-from@7.24.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.24.8 + '@babel/plugin-syntax-export-default-from': 7.24.7(@babel/core@7.25.8) + '@babel/plugin-proposal-export-namespace-from@7.18.9(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 @@ -7848,8 +8857,15 @@ snapshots: '@babel/plugin-proposal-logical-assignment-operators@7.20.7(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.24.8 + '@babel/helper-plugin-utils': 7.25.7 '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.25.2) + optional: true + + '@babel/plugin-proposal-logical-assignment-operators@7.20.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.25.8) '@babel/plugin-proposal-nullish-coalescing-operator@7.18.6(@babel/core@7.25.2)': dependencies: @@ -7857,11 +8873,24 @@ snapshots: '@babel/helper-plugin-utils': 7.24.8 '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.25.2) + '@babel/plugin-proposal-nullish-coalescing-operator@7.18.6(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.24.8 + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.25.8) + '@babel/plugin-proposal-numeric-separator@7.18.6(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.24.8 + '@babel/helper-plugin-utils': 7.25.7 '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.25.2) + optional: true + + '@babel/plugin-proposal-numeric-separator@7.18.6(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.25.8) '@babel/plugin-proposal-object-rest-spread@7.20.7(@babel/core@7.25.2)': dependencies: @@ -7872,12 +8901,27 @@ snapshots: '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.25.2) '@babel/plugin-transform-parameters': 7.24.7(@babel/core@7.25.2) + '@babel/plugin-proposal-object-rest-spread@7.20.7(@babel/core@7.25.8)': + dependencies: + '@babel/compat-data': 7.25.2 + '@babel/core': 7.25.8 + '@babel/helper-compilation-targets': 7.25.2 + '@babel/helper-plugin-utils': 7.24.8 + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.25.8) + '@babel/plugin-transform-parameters': 7.24.7(@babel/core@7.25.8) + '@babel/plugin-proposal-optional-catch-binding@7.18.6(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.25.2) + '@babel/plugin-proposal-optional-catch-binding@7.18.6(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.24.8 + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.25.8) + '@babel/plugin-proposal-optional-chaining@7.21.0(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 @@ -7887,18 +8931,36 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/plugin-proposal-optional-chaining@7.21.0(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.24.8 + '@babel/helper-skip-transparent-expression-wrappers': 7.24.7 + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.25.8) + transitivePeerDependencies: + - supports-color + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.25.2)': + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.25.8)': dependencies: - '@babel/core': 7.25.2 + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.24.8 + + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 '@babel/helper-plugin-utils': 7.24.8 '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.25.2)': @@ -7906,26 +8968,47 @@ snapshots: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.25.2)': + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.25.8)': dependencies: - '@babel/core': 7.25.2 + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.24.8 + + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 '@babel/helper-plugin-utils': 7.24.8 '@babel/plugin-syntax-decorators@7.24.7(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.24.8 + '@babel/helper-plugin-utils': 7.25.7 + optional: true + + '@babel/plugin-syntax-decorators@7.24.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 '@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 + '@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.24.8 + '@babel/plugin-syntax-export-default-from@7.24.7(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 + '@babel/plugin-syntax-export-default-from@7.24.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.24.8 + '@babel/plugin-syntax-export-namespace-from@7.8.3(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 @@ -7936,24 +9019,44 @@ snapshots: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-syntax-import-assertions@7.24.7(@babel/core@7.25.2)': + '@babel/plugin-syntax-flow@7.24.7(@babel/core@7.25.8)': dependencies: - '@babel/core': 7.25.2 + '@babel/core': 7.25.8 '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-syntax-import-attributes@7.24.7(@babel/core@7.25.2)': + '@babel/plugin-syntax-import-assertions@7.25.7(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-syntax-import-assertions@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-syntax-import-attributes@7.24.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.25.2)': + '@babel/plugin-syntax-import-attributes@7.25.7(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-syntax-import-attributes@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.25.2)': + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.25.8)': dependencies: - '@babel/core': 7.25.2 + '@babel/core': 7.25.8 '@babel/helper-plugin-utils': 7.24.8 '@babel/plugin-syntax-jsx@7.24.7(@babel/core@7.25.2)': @@ -7961,44 +9064,81 @@ snapshots: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 + '@babel/plugin-syntax-jsx@7.24.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.24.8 + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 + optional: true + + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.24.8 '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.24.8 + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 + optional: true + + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.24.8 '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.24.8 + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.24.8 + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.25.2)': + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.25.8)': dependencies: - '@babel/core': 7.25.2 + '@babel/core': 7.25.8 '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.25.2)': + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.25.8)': dependencies: - '@babel/core': 7.25.2 + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.24.8 + + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 '@babel/helper-plugin-utils': 7.24.8 '@babel/plugin-syntax-typescript@7.24.7(@babel/core@7.25.2)': @@ -8006,24 +9146,58 @@ snapshots: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 + '@babel/plugin-syntax-typescript@7.24.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.24.8 + '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 '@babel/helper-create-regexp-features-plugin': 7.25.2(@babel/core@7.25.2) - '@babel/helper-plugin-utils': 7.24.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-create-regexp-features-plugin': 7.25.2(@babel/core@7.25.8) + '@babel/helper-plugin-utils': 7.25.7 '@babel/plugin-transform-arrow-functions@7.24.7(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-transform-async-generator-functions@7.25.0(@babel/core@7.25.2)': + '@babel/plugin-transform-arrow-functions@7.24.7(@babel/core@7.25.8)': dependencies: - '@babel/core': 7.25.2 + '@babel/core': 7.25.8 '@babel/helper-plugin-utils': 7.24.8 - '@babel/helper-remap-async-to-generator': 7.25.0(@babel/core@7.25.2) - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.25.2) - '@babel/traverse': 7.25.3 + + '@babel/plugin-transform-arrow-functions@7.25.7(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-arrow-functions@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-async-generator-functions@7.25.8(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-plugin-utils': 7.25.7 + '@babel/helper-remap-async-to-generator': 7.25.7(@babel/core@7.25.2) + '@babel/traverse': 7.25.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-async-generator-functions@7.25.8(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + '@babel/helper-remap-async-to-generator': 7.25.7(@babel/core@7.25.8) + '@babel/traverse': 7.25.7 transitivePeerDependencies: - supports-color @@ -8036,31 +9210,103 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-transform-block-scoped-functions@7.24.7(@babel/core@7.25.2)': + '@babel/plugin-transform-async-to-generator@7.24.7(@babel/core@7.25.8)': dependencies: - '@babel/core': 7.25.2 + '@babel/core': 7.25.8 + '@babel/helper-module-imports': 7.24.7 '@babel/helper-plugin-utils': 7.24.8 + '@babel/helper-remap-async-to-generator': 7.25.0(@babel/core@7.25.8) + transitivePeerDependencies: + - supports-color - '@babel/plugin-transform-block-scoping@7.25.0(@babel/core@7.25.2)': + '@babel/plugin-transform-async-to-generator@7.25.7(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.24.8 + '@babel/helper-module-imports': 7.25.7 + '@babel/helper-plugin-utils': 7.25.7 + '@babel/helper-remap-async-to-generator': 7.25.7(@babel/core@7.25.2) + transitivePeerDependencies: + - supports-color - '@babel/plugin-transform-class-properties@7.24.7(@babel/core@7.25.2)': + '@babel/plugin-transform-async-to-generator@7.25.7(@babel/core@7.25.8)': dependencies: - '@babel/core': 7.25.2 - '@babel/helper-create-class-features-plugin': 7.25.0(@babel/core@7.25.2) - '@babel/helper-plugin-utils': 7.24.8 + '@babel/core': 7.25.8 + '@babel/helper-module-imports': 7.25.7 + '@babel/helper-plugin-utils': 7.25.7 + '@babel/helper-remap-async-to-generator': 7.25.7(@babel/core@7.25.8) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-class-static-block@7.24.7(@babel/core@7.25.2)': + '@babel/plugin-transform-block-scoped-functions@7.24.7(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 - '@babel/helper-create-class-features-plugin': 7.25.0(@babel/core@7.25.2) '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.25.2) - transitivePeerDependencies: + + '@babel/plugin-transform-block-scoped-functions@7.24.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.24.8 + + '@babel/plugin-transform-block-scoped-functions@7.25.7(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-block-scoped-functions@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-block-scoping@7.25.0(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-plugin-utils': 7.24.8 + + '@babel/plugin-transform-block-scoping@7.25.0(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.24.8 + + '@babel/plugin-transform-block-scoping@7.25.7(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-block-scoping@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-class-properties@7.25.7(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-create-class-features-plugin': 7.25.7(@babel/core@7.25.2) + '@babel/helper-plugin-utils': 7.25.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-class-properties@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-create-class-features-plugin': 7.25.7(@babel/core@7.25.8) + '@babel/helper-plugin-utils': 7.25.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-class-static-block@7.25.8(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-create-class-features-plugin': 7.25.7(@babel/core@7.25.2) + '@babel/helper-plugin-utils': 7.25.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-class-static-block@7.25.8(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-create-class-features-plugin': 7.25.7(@babel/core@7.25.8) + '@babel/helper-plugin-utils': 7.25.7 + transitivePeerDependencies: - supports-color '@babel/plugin-transform-classes@7.25.0(@babel/core@7.25.2)': @@ -8075,53 +9321,155 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/plugin-transform-classes@7.25.0(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-annotate-as-pure': 7.24.7 + '@babel/helper-compilation-targets': 7.25.2 + '@babel/helper-plugin-utils': 7.24.8 + '@babel/helper-replace-supers': 7.25.0(@babel/core@7.25.8) + '@babel/traverse': 7.25.3 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-classes@7.25.7(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-annotate-as-pure': 7.25.7 + '@babel/helper-compilation-targets': 7.25.7 + '@babel/helper-plugin-utils': 7.25.7 + '@babel/helper-replace-supers': 7.25.7(@babel/core@7.25.2) + '@babel/traverse': 7.25.7 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-classes@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-annotate-as-pure': 7.25.7 + '@babel/helper-compilation-targets': 7.25.7 + '@babel/helper-plugin-utils': 7.25.7 + '@babel/helper-replace-supers': 7.25.7(@babel/core@7.25.8) + '@babel/traverse': 7.25.7 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + '@babel/plugin-transform-computed-properties@7.24.7(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 '@babel/template': 7.25.0 + '@babel/plugin-transform-computed-properties@7.24.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.24.8 + '@babel/template': 7.25.0 + + '@babel/plugin-transform-computed-properties@7.25.7(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-plugin-utils': 7.25.7 + '@babel/template': 7.25.7 + + '@babel/plugin-transform-computed-properties@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + '@babel/template': 7.25.7 + '@babel/plugin-transform-destructuring@7.24.8(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-transform-dotall-regex@7.24.7(@babel/core@7.25.2)': + '@babel/plugin-transform-destructuring@7.24.8(@babel/core@7.25.8)': dependencies: - '@babel/core': 7.25.2 - '@babel/helper-create-regexp-features-plugin': 7.25.2(@babel/core@7.25.2) + '@babel/core': 7.25.8 '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-transform-duplicate-keys@7.24.7(@babel/core@7.25.2)': + '@babel/plugin-transform-destructuring@7.25.7(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.24.8 + '@babel/helper-plugin-utils': 7.25.7 - '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.25.0(@babel/core@7.25.2)': + '@babel/plugin-transform-destructuring@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-dotall-regex@7.25.7(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 - '@babel/helper-create-regexp-features-plugin': 7.25.2(@babel/core@7.25.2) - '@babel/helper-plugin-utils': 7.24.8 + '@babel/helper-create-regexp-features-plugin': 7.25.7(@babel/core@7.25.2) + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-dotall-regex@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-create-regexp-features-plugin': 7.25.7(@babel/core@7.25.8) + '@babel/helper-plugin-utils': 7.25.7 - '@babel/plugin-transform-dynamic-import@7.24.7(@babel/core@7.25.2)': + '@babel/plugin-transform-duplicate-keys@7.25.7(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.25.2) + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-duplicate-keys@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 - '@babel/plugin-transform-exponentiation-operator@7.24.7(@babel/core@7.25.2)': + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.25.7(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 - '@babel/helper-builder-binary-assignment-operator-visitor': 7.24.7 - '@babel/helper-plugin-utils': 7.24.8 + '@babel/helper-create-regexp-features-plugin': 7.25.7(@babel/core@7.25.2) + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-create-regexp-features-plugin': 7.25.7(@babel/core@7.25.8) + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-dynamic-import@7.25.8(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-dynamic-import@7.25.8(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-exponentiation-operator@7.25.7(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-builder-binary-assignment-operator-visitor': 7.25.7 + '@babel/helper-plugin-utils': 7.25.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-exponentiation-operator@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-builder-binary-assignment-operator-visitor': 7.25.7 + '@babel/helper-plugin-utils': 7.25.7 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-export-namespace-from@7.24.7(@babel/core@7.25.2)': + '@babel/plugin-transform-export-namespace-from@7.25.8(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.25.2) + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-export-namespace-from@7.25.8(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 '@babel/plugin-transform-flow-strip-types@7.25.2(@babel/core@7.25.2)': dependencies: @@ -8129,6 +9477,12 @@ snapshots: '@babel/helper-plugin-utils': 7.24.8 '@babel/plugin-syntax-flow': 7.24.7(@babel/core@7.25.2) + '@babel/plugin-transform-flow-strip-types@7.25.2(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.24.8 + '@babel/plugin-syntax-flow': 7.24.7(@babel/core@7.25.8) + '@babel/plugin-transform-for-of@7.24.7(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 @@ -8137,6 +9491,30 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/plugin-transform-for-of@7.24.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.24.8 + '@babel/helper-skip-transparent-expression-wrappers': 7.24.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-for-of@7.25.7(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-plugin-utils': 7.25.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-for-of@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.7 + transitivePeerDependencies: + - supports-color + '@babel/plugin-transform-function-name@7.25.1(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 @@ -8146,33 +9524,106 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-transform-json-strings@7.24.7(@babel/core@7.25.2)': + '@babel/plugin-transform-function-name@7.25.1(@babel/core@7.25.8)': dependencies: - '@babel/core': 7.25.2 + '@babel/core': 7.25.8 + '@babel/helper-compilation-targets': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.25.2) + '@babel/traverse': 7.25.3 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-function-name@7.25.7(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-compilation-targets': 7.25.7 + '@babel/helper-plugin-utils': 7.25.7 + '@babel/traverse': 7.25.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-function-name@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-compilation-targets': 7.25.7 + '@babel/helper-plugin-utils': 7.25.7 + '@babel/traverse': 7.25.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-json-strings@7.25.8(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-json-strings@7.25.8(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 '@babel/plugin-transform-literals@7.25.2(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-transform-logical-assignment-operators@7.24.7(@babel/core@7.25.2)': + '@babel/plugin-transform-literals@7.25.2(@babel/core@7.25.8)': dependencies: - '@babel/core': 7.25.2 + '@babel/core': 7.25.8 '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.25.2) + + '@babel/plugin-transform-literals@7.25.7(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-literals@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-logical-assignment-operators@7.25.8(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-logical-assignment-operators@7.25.8(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 '@babel/plugin-transform-member-expression-literals@7.24.7(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-transform-modules-amd@7.24.7(@babel/core@7.25.2)': + '@babel/plugin-transform-member-expression-literals@7.24.7(@babel/core@7.25.8)': dependencies: - '@babel/core': 7.25.2 - '@babel/helper-module-transforms': 7.25.2(@babel/core@7.25.2) + '@babel/core': 7.25.8 '@babel/helper-plugin-utils': 7.24.8 + + '@babel/plugin-transform-member-expression-literals@7.25.7(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-member-expression-literals@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-modules-amd@7.25.7(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-module-transforms': 7.25.7(@babel/core@7.25.2) + '@babel/helper-plugin-utils': 7.25.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-amd@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-module-transforms': 7.25.7(@babel/core@7.25.8) + '@babel/helper-plugin-utils': 7.25.7 transitivePeerDependencies: - supports-color @@ -8185,21 +9636,66 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-systemjs@7.25.0(@babel/core@7.25.2)': + '@babel/plugin-transform-modules-commonjs@7.24.8(@babel/core@7.25.8)': dependencies: - '@babel/core': 7.25.2 - '@babel/helper-module-transforms': 7.25.2(@babel/core@7.25.2) + '@babel/core': 7.25.8 + '@babel/helper-module-transforms': 7.25.2(@babel/core@7.25.8) '@babel/helper-plugin-utils': 7.24.8 - '@babel/helper-validator-identifier': 7.24.7 - '@babel/traverse': 7.25.3 + '@babel/helper-simple-access': 7.24.7 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-umd@7.24.7(@babel/core@7.25.2)': + '@babel/plugin-transform-modules-commonjs@7.25.7(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 - '@babel/helper-module-transforms': 7.25.2(@babel/core@7.25.2) - '@babel/helper-plugin-utils': 7.24.8 + '@babel/helper-module-transforms': 7.25.7(@babel/core@7.25.2) + '@babel/helper-plugin-utils': 7.25.7 + '@babel/helper-simple-access': 7.25.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-commonjs@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-module-transforms': 7.25.7(@babel/core@7.25.8) + '@babel/helper-plugin-utils': 7.25.7 + '@babel/helper-simple-access': 7.25.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-systemjs@7.25.7(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-module-transforms': 7.25.7(@babel/core@7.25.2) + '@babel/helper-plugin-utils': 7.25.7 + '@babel/helper-validator-identifier': 7.25.7 + '@babel/traverse': 7.25.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-systemjs@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-module-transforms': 7.25.7(@babel/core@7.25.8) + '@babel/helper-plugin-utils': 7.25.7 + '@babel/helper-validator-identifier': 7.25.7 + '@babel/traverse': 7.25.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-umd@7.25.7(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-module-transforms': 7.25.7(@babel/core@7.25.2) + '@babel/helper-plugin-utils': 7.25.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-umd@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-module-transforms': 7.25.7(@babel/core@7.25.8) + '@babel/helper-plugin-utils': 7.25.7 transitivePeerDependencies: - supports-color @@ -8209,30 +9705,67 @@ snapshots: '@babel/helper-create-regexp-features-plugin': 7.25.2(@babel/core@7.25.2) '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-transform-new-target@7.24.7(@babel/core@7.25.2)': + '@babel/plugin-transform-named-capturing-groups-regex@7.24.7(@babel/core@7.25.8)': dependencies: - '@babel/core': 7.25.2 + '@babel/core': 7.25.8 + '@babel/helper-create-regexp-features-plugin': 7.25.2(@babel/core@7.25.8) '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-transform-nullish-coalescing-operator@7.24.7(@babel/core@7.25.2)': + '@babel/plugin-transform-named-capturing-groups-regex@7.25.7(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.25.2) + '@babel/helper-create-regexp-features-plugin': 7.25.7(@babel/core@7.25.2) + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-named-capturing-groups-regex@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-create-regexp-features-plugin': 7.25.7(@babel/core@7.25.8) + '@babel/helper-plugin-utils': 7.25.7 - '@babel/plugin-transform-numeric-separator@7.24.7(@babel/core@7.25.2)': + '@babel/plugin-transform-new-target@7.25.7(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.25.2) + '@babel/helper-plugin-utils': 7.25.7 - '@babel/plugin-transform-object-rest-spread@7.24.7(@babel/core@7.25.2)': + '@babel/plugin-transform-new-target@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-nullish-coalescing-operator@7.25.8(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 - '@babel/helper-compilation-targets': 7.25.2 - '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.25.2) - '@babel/plugin-transform-parameters': 7.24.7(@babel/core@7.25.2) + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-nullish-coalescing-operator@7.25.8(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-numeric-separator@7.25.8(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-numeric-separator@7.25.8(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-object-rest-spread@7.25.8(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-compilation-targets': 7.25.7 + '@babel/helper-plugin-utils': 7.25.7 + '@babel/plugin-transform-parameters': 7.25.7(@babel/core@7.25.2) + + '@babel/plugin-transform-object-rest-spread@7.25.8(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-compilation-targets': 7.25.7 + '@babel/helper-plugin-utils': 7.25.7 + '@babel/plugin-transform-parameters': 7.25.7(@babel/core@7.25.8) '@babel/plugin-transform-object-super@7.24.7(@babel/core@7.25.2)': dependencies: @@ -8242,18 +9775,53 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-transform-optional-catch-binding@7.24.7(@babel/core@7.25.2)': + '@babel/plugin-transform-object-super@7.24.7(@babel/core@7.25.8)': dependencies: - '@babel/core': 7.25.2 + '@babel/core': 7.25.8 '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.25.2) + '@babel/helper-replace-supers': 7.25.0(@babel/core@7.25.8) + transitivePeerDependencies: + - supports-color - '@babel/plugin-transform-optional-chaining@7.24.8(@babel/core@7.25.2)': + '@babel/plugin-transform-object-super@7.25.7(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.24.8 - '@babel/helper-skip-transparent-expression-wrappers': 7.24.7 - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.25.2) + '@babel/helper-plugin-utils': 7.25.7 + '@babel/helper-replace-supers': 7.25.7(@babel/core@7.25.2) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-object-super@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + '@babel/helper-replace-supers': 7.25.7(@babel/core@7.25.8) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-optional-catch-binding@7.25.8(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-optional-catch-binding@7.25.8(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-optional-chaining@7.25.8(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-plugin-utils': 7.25.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-optional-chaining@7.25.8(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.7 transitivePeerDependencies: - supports-color @@ -8262,21 +9830,52 @@ snapshots: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-transform-private-methods@7.24.7(@babel/core@7.25.2)': + '@babel/plugin-transform-parameters@7.24.7(@babel/core@7.25.8)': dependencies: - '@babel/core': 7.25.2 - '@babel/helper-create-class-features-plugin': 7.25.0(@babel/core@7.25.2) + '@babel/core': 7.25.8 '@babel/helper-plugin-utils': 7.24.8 + + '@babel/plugin-transform-parameters@7.25.7(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-parameters@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-private-methods@7.25.7(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-create-class-features-plugin': 7.25.7(@babel/core@7.25.2) + '@babel/helper-plugin-utils': 7.25.7 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-private-property-in-object@7.24.7(@babel/core@7.25.2)': + '@babel/plugin-transform-private-methods@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-create-class-features-plugin': 7.25.7(@babel/core@7.25.8) + '@babel/helper-plugin-utils': 7.25.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-private-property-in-object@7.25.8(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 - '@babel/helper-annotate-as-pure': 7.24.7 - '@babel/helper-create-class-features-plugin': 7.25.0(@babel/core@7.25.2) - '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.25.2) + '@babel/helper-annotate-as-pure': 7.25.7 + '@babel/helper-create-class-features-plugin': 7.25.7(@babel/core@7.25.2) + '@babel/helper-plugin-utils': 7.25.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-private-property-in-object@7.25.8(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-annotate-as-pure': 7.25.7 + '@babel/helper-create-class-features-plugin': 7.25.7(@babel/core@7.25.8) + '@babel/helper-plugin-utils': 7.25.7 transitivePeerDependencies: - supports-color @@ -8285,95 +9884,248 @@ snapshots: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 + '@babel/plugin-transform-property-literals@7.24.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.24.8 + + '@babel/plugin-transform-property-literals@7.25.7(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-property-literals@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + '@babel/plugin-transform-react-display-name@7.24.7(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 + '@babel/plugin-transform-react-display-name@7.24.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.24.8 + '@babel/plugin-transform-react-jsx-development@7.24.7(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 '@babel/plugin-transform-react-jsx': 7.25.2(@babel/core@7.25.2) transitivePeerDependencies: - supports-color + optional: true + + '@babel/plugin-transform-react-jsx-development@7.24.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/plugin-transform-react-jsx': 7.25.2(@babel/core@7.25.8) + transitivePeerDependencies: + - supports-color '@babel/plugin-transform-react-jsx-self@7.24.7(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 + '@babel/plugin-transform-react-jsx-self@7.24.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.24.8 + '@babel/plugin-transform-react-jsx-source@7.24.7(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.24.8 + '@babel/helper-plugin-utils': 7.24.8 + + '@babel/plugin-transform-react-jsx-source@7.24.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.24.8 + + '@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-annotate-as-pure': 7.24.7 + '@babel/helper-module-imports': 7.24.7 + '@babel/helper-plugin-utils': 7.24.8 + '@babel/plugin-syntax-jsx': 7.24.7(@babel/core@7.25.2) + '@babel/types': 7.25.2 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-annotate-as-pure': 7.24.7 + '@babel/helper-module-imports': 7.24.7 + '@babel/helper-plugin-utils': 7.24.8 + '@babel/plugin-syntax-jsx': 7.24.7(@babel/core@7.25.8) + '@babel/types': 7.25.2 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-react-pure-annotations@7.24.7(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-annotate-as-pure': 7.25.7 + '@babel/helper-plugin-utils': 7.25.7 + optional: true + + '@babel/plugin-transform-react-pure-annotations@7.24.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-annotate-as-pure': 7.25.7 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-regenerator@7.25.7(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-plugin-utils': 7.25.7 + regenerator-transform: 0.15.2 + + '@babel/plugin-transform-regenerator@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + regenerator-transform: 0.15.2 + + '@babel/plugin-transform-reserved-words@7.25.7(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-reserved-words@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-runtime@7.24.7(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-module-imports': 7.24.7 + '@babel/helper-plugin-utils': 7.24.8 + babel-plugin-polyfill-corejs2: 0.4.11(@babel/core@7.25.2) + babel-plugin-polyfill-corejs3: 0.10.6(@babel/core@7.25.2) + babel-plugin-polyfill-regenerator: 0.6.2(@babel/core@7.25.2) + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-runtime@7.24.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-module-imports': 7.24.7 + '@babel/helper-plugin-utils': 7.24.8 + babel-plugin-polyfill-corejs2: 0.4.11(@babel/core@7.25.8) + babel-plugin-polyfill-corejs3: 0.10.6(@babel/core@7.25.8) + babel-plugin-polyfill-regenerator: 0.6.2(@babel/core@7.25.8) + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-shorthand-properties@7.24.7(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-plugin-utils': 7.24.8 + + '@babel/plugin-transform-shorthand-properties@7.24.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.24.8 + + '@babel/plugin-transform-shorthand-properties@7.25.7(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-shorthand-properties@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-spread@7.24.7(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-plugin-utils': 7.24.8 + '@babel/helper-skip-transparent-expression-wrappers': 7.24.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-spread@7.24.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.24.8 + '@babel/helper-skip-transparent-expression-wrappers': 7.24.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-spread@7.25.7(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-plugin-utils': 7.25.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.7 + transitivePeerDependencies: + - supports-color - '@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.25.2)': + '@babel/plugin-transform-spread@7.25.7(@babel/core@7.25.8)': dependencies: - '@babel/core': 7.25.2 - '@babel/helper-annotate-as-pure': 7.24.7 - '@babel/helper-module-imports': 7.24.7 - '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-syntax-jsx': 7.24.7(@babel/core@7.25.2) - '@babel/types': 7.25.2 + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.7 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-react-pure-annotations@7.24.7(@babel/core@7.25.2)': + '@babel/plugin-transform-sticky-regex@7.24.7(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 - '@babel/helper-annotate-as-pure': 7.24.7 '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-transform-regenerator@7.24.7(@babel/core@7.25.2)': + '@babel/plugin-transform-sticky-regex@7.24.7(@babel/core@7.25.8)': dependencies: - '@babel/core': 7.25.2 + '@babel/core': 7.25.8 '@babel/helper-plugin-utils': 7.24.8 - regenerator-transform: 0.15.2 - '@babel/plugin-transform-reserved-words@7.24.7(@babel/core@7.25.2)': + '@babel/plugin-transform-sticky-regex@7.25.7(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.24.8 + '@babel/helper-plugin-utils': 7.25.7 - '@babel/plugin-transform-runtime@7.24.7(@babel/core@7.25.2)': + '@babel/plugin-transform-sticky-regex@7.25.7(@babel/core@7.25.8)': dependencies: - '@babel/core': 7.25.2 - '@babel/helper-module-imports': 7.24.7 - '@babel/helper-plugin-utils': 7.24.8 - babel-plugin-polyfill-corejs2: 0.4.11(@babel/core@7.25.2) - babel-plugin-polyfill-corejs3: 0.10.6(@babel/core@7.25.2) - babel-plugin-polyfill-regenerator: 0.6.2(@babel/core@7.25.2) - semver: 6.3.1 - transitivePeerDependencies: - - supports-color + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 - '@babel/plugin-transform-shorthand-properties@7.24.7(@babel/core@7.25.2)': + '@babel/plugin-transform-template-literals@7.24.7(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-transform-spread@7.24.7(@babel/core@7.25.2)': + '@babel/plugin-transform-template-literals@7.24.7(@babel/core@7.25.8)': dependencies: - '@babel/core': 7.25.2 + '@babel/core': 7.25.8 '@babel/helper-plugin-utils': 7.24.8 - '@babel/helper-skip-transparent-expression-wrappers': 7.24.7 - transitivePeerDependencies: - - supports-color - '@babel/plugin-transform-sticky-regex@7.24.7(@babel/core@7.25.2)': + '@babel/plugin-transform-template-literals@7.25.7(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.24.8 + '@babel/helper-plugin-utils': 7.25.7 - '@babel/plugin-transform-template-literals@7.24.7(@babel/core@7.25.2)': + '@babel/plugin-transform-template-literals@7.25.7(@babel/core@7.25.8)': dependencies: - '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.24.8 + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 - '@babel/plugin-transform-typeof-symbol@7.24.8(@babel/core@7.25.2)': + '@babel/plugin-transform-typeof-symbol@7.25.7(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.24.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-typeof-symbol@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 '@babel/plugin-transform-typescript@7.25.2(@babel/core@7.25.2)': dependencies: @@ -8386,16 +10138,38 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-transform-unicode-escapes@7.24.7(@babel/core@7.25.2)': + '@babel/plugin-transform-typescript@7.25.2(@babel/core@7.25.8)': dependencies: - '@babel/core': 7.25.2 + '@babel/core': 7.25.8 + '@babel/helper-annotate-as-pure': 7.24.7 + '@babel/helper-create-class-features-plugin': 7.25.0(@babel/core@7.25.8) '@babel/helper-plugin-utils': 7.24.8 + '@babel/helper-skip-transparent-expression-wrappers': 7.24.7 + '@babel/plugin-syntax-typescript': 7.24.7(@babel/core@7.25.8) + transitivePeerDependencies: + - supports-color - '@babel/plugin-transform-unicode-property-regex@7.24.7(@babel/core@7.25.2)': + '@babel/plugin-transform-unicode-escapes@7.25.7(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 - '@babel/helper-create-regexp-features-plugin': 7.25.2(@babel/core@7.25.2) - '@babel/helper-plugin-utils': 7.24.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-unicode-escapes@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-unicode-property-regex@7.25.7(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-create-regexp-features-plugin': 7.25.7(@babel/core@7.25.2) + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-unicode-property-regex@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-create-regexp-features-plugin': 7.25.7(@babel/core@7.25.8) + '@babel/helper-plugin-utils': 7.25.7 '@babel/plugin-transform-unicode-regex@7.24.7(@babel/core@7.25.2)': dependencies: @@ -8403,92 +10177,101 @@ snapshots: '@babel/helper-create-regexp-features-plugin': 7.25.2(@babel/core@7.25.2) '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-transform-unicode-sets-regex@7.24.7(@babel/core@7.25.2)': + '@babel/plugin-transform-unicode-regex@7.24.7(@babel/core@7.25.8)': dependencies: - '@babel/core': 7.25.2 - '@babel/helper-create-regexp-features-plugin': 7.25.2(@babel/core@7.25.2) + '@babel/core': 7.25.8 + '@babel/helper-create-regexp-features-plugin': 7.25.2(@babel/core@7.25.8) '@babel/helper-plugin-utils': 7.24.8 - '@babel/preset-env@7.25.3(@babel/core@7.25.2)': + '@babel/plugin-transform-unicode-regex@7.25.7(@babel/core@7.25.2)': dependencies: - '@babel/compat-data': 7.25.2 '@babel/core': 7.25.2 - '@babel/helper-compilation-targets': 7.25.2 - '@babel/helper-plugin-utils': 7.24.8 - '@babel/helper-validator-option': 7.24.8 - '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.25.3(@babel/core@7.25.2) - '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.25.0(@babel/core@7.25.2) - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.25.0(@babel/core@7.25.2) - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.25.0(@babel/core@7.25.2) + '@babel/helper-create-regexp-features-plugin': 7.25.7(@babel/core@7.25.2) + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-unicode-regex@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-create-regexp-features-plugin': 7.25.7(@babel/core@7.25.8) + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-unicode-sets-regex@7.25.7(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-create-regexp-features-plugin': 7.25.7(@babel/core@7.25.2) + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-unicode-sets-regex@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-create-regexp-features-plugin': 7.25.7(@babel/core@7.25.8) + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/preset-env@7.25.8(@babel/core@7.25.2)': + dependencies: + '@babel/compat-data': 7.25.8 + '@babel/core': 7.25.2 + '@babel/helper-compilation-targets': 7.25.7 + '@babel/helper-plugin-utils': 7.25.7 + '@babel/helper-validator-option': 7.25.7 + '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.25.7(@babel/core@7.25.2) + '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.25.7(@babel/core@7.25.2) + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.25.7(@babel/core@7.25.2) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.25.7(@babel/core@7.25.2) + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.25.7(@babel/core@7.25.2) '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.25.2) - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.25.2) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.25.2) - '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.25.2) - '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.25.2) - '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.25.2) - '@babel/plugin-syntax-import-assertions': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-syntax-import-attributes': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.25.2) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.25.2) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.25.2) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.25.2) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.25.2) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.25.2) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.25.2) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.25.2) - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.25.2) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.25.2) + '@babel/plugin-syntax-import-assertions': 7.25.7(@babel/core@7.25.2) + '@babel/plugin-syntax-import-attributes': 7.25.7(@babel/core@7.25.2) '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.25.2) - '@babel/plugin-transform-arrow-functions': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-transform-async-generator-functions': 7.25.0(@babel/core@7.25.2) - '@babel/plugin-transform-async-to-generator': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-transform-block-scoped-functions': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-transform-block-scoping': 7.25.0(@babel/core@7.25.2) - '@babel/plugin-transform-class-properties': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-transform-class-static-block': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-transform-classes': 7.25.0(@babel/core@7.25.2) - '@babel/plugin-transform-computed-properties': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-transform-destructuring': 7.24.8(@babel/core@7.25.2) - '@babel/plugin-transform-dotall-regex': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-transform-duplicate-keys': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.25.0(@babel/core@7.25.2) - '@babel/plugin-transform-dynamic-import': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-transform-exponentiation-operator': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-transform-export-namespace-from': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-transform-for-of': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-transform-function-name': 7.25.1(@babel/core@7.25.2) - '@babel/plugin-transform-json-strings': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-transform-literals': 7.25.2(@babel/core@7.25.2) - '@babel/plugin-transform-logical-assignment-operators': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-transform-member-expression-literals': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-transform-modules-amd': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-transform-modules-commonjs': 7.24.8(@babel/core@7.25.2) - '@babel/plugin-transform-modules-systemjs': 7.25.0(@babel/core@7.25.2) - '@babel/plugin-transform-modules-umd': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-transform-named-capturing-groups-regex': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-transform-new-target': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-transform-nullish-coalescing-operator': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-transform-numeric-separator': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-transform-object-rest-spread': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-transform-object-super': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-transform-optional-catch-binding': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-transform-optional-chaining': 7.24.8(@babel/core@7.25.2) - '@babel/plugin-transform-parameters': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-transform-private-methods': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-transform-private-property-in-object': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-transform-property-literals': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-transform-regenerator': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-transform-reserved-words': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-transform-shorthand-properties': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-transform-spread': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-transform-sticky-regex': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-transform-template-literals': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-transform-typeof-symbol': 7.24.8(@babel/core@7.25.2) - '@babel/plugin-transform-unicode-escapes': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-transform-unicode-property-regex': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-transform-unicode-regex': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-transform-unicode-sets-regex': 7.24.7(@babel/core@7.25.2) + '@babel/plugin-transform-arrow-functions': 7.25.7(@babel/core@7.25.2) + '@babel/plugin-transform-async-generator-functions': 7.25.8(@babel/core@7.25.2) + '@babel/plugin-transform-async-to-generator': 7.25.7(@babel/core@7.25.2) + '@babel/plugin-transform-block-scoped-functions': 7.25.7(@babel/core@7.25.2) + '@babel/plugin-transform-block-scoping': 7.25.7(@babel/core@7.25.2) + '@babel/plugin-transform-class-properties': 7.25.7(@babel/core@7.25.2) + '@babel/plugin-transform-class-static-block': 7.25.8(@babel/core@7.25.2) + '@babel/plugin-transform-classes': 7.25.7(@babel/core@7.25.2) + '@babel/plugin-transform-computed-properties': 7.25.7(@babel/core@7.25.2) + '@babel/plugin-transform-destructuring': 7.25.7(@babel/core@7.25.2) + '@babel/plugin-transform-dotall-regex': 7.25.7(@babel/core@7.25.2) + '@babel/plugin-transform-duplicate-keys': 7.25.7(@babel/core@7.25.2) + '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.25.7(@babel/core@7.25.2) + '@babel/plugin-transform-dynamic-import': 7.25.8(@babel/core@7.25.2) + '@babel/plugin-transform-exponentiation-operator': 7.25.7(@babel/core@7.25.2) + '@babel/plugin-transform-export-namespace-from': 7.25.8(@babel/core@7.25.2) + '@babel/plugin-transform-for-of': 7.25.7(@babel/core@7.25.2) + '@babel/plugin-transform-function-name': 7.25.7(@babel/core@7.25.2) + '@babel/plugin-transform-json-strings': 7.25.8(@babel/core@7.25.2) + '@babel/plugin-transform-literals': 7.25.7(@babel/core@7.25.2) + '@babel/plugin-transform-logical-assignment-operators': 7.25.8(@babel/core@7.25.2) + '@babel/plugin-transform-member-expression-literals': 7.25.7(@babel/core@7.25.2) + '@babel/plugin-transform-modules-amd': 7.25.7(@babel/core@7.25.2) + '@babel/plugin-transform-modules-commonjs': 7.25.7(@babel/core@7.25.2) + '@babel/plugin-transform-modules-systemjs': 7.25.7(@babel/core@7.25.2) + '@babel/plugin-transform-modules-umd': 7.25.7(@babel/core@7.25.2) + '@babel/plugin-transform-named-capturing-groups-regex': 7.25.7(@babel/core@7.25.2) + '@babel/plugin-transform-new-target': 7.25.7(@babel/core@7.25.2) + '@babel/plugin-transform-nullish-coalescing-operator': 7.25.8(@babel/core@7.25.2) + '@babel/plugin-transform-numeric-separator': 7.25.8(@babel/core@7.25.2) + '@babel/plugin-transform-object-rest-spread': 7.25.8(@babel/core@7.25.2) + '@babel/plugin-transform-object-super': 7.25.7(@babel/core@7.25.2) + '@babel/plugin-transform-optional-catch-binding': 7.25.8(@babel/core@7.25.2) + '@babel/plugin-transform-optional-chaining': 7.25.8(@babel/core@7.25.2) + '@babel/plugin-transform-parameters': 7.25.7(@babel/core@7.25.2) + '@babel/plugin-transform-private-methods': 7.25.7(@babel/core@7.25.2) + '@babel/plugin-transform-private-property-in-object': 7.25.8(@babel/core@7.25.2) + '@babel/plugin-transform-property-literals': 7.25.7(@babel/core@7.25.2) + '@babel/plugin-transform-regenerator': 7.25.7(@babel/core@7.25.2) + '@babel/plugin-transform-reserved-words': 7.25.7(@babel/core@7.25.2) + '@babel/plugin-transform-shorthand-properties': 7.25.7(@babel/core@7.25.2) + '@babel/plugin-transform-spread': 7.25.7(@babel/core@7.25.2) + '@babel/plugin-transform-sticky-regex': 7.25.7(@babel/core@7.25.2) + '@babel/plugin-transform-template-literals': 7.25.7(@babel/core@7.25.2) + '@babel/plugin-transform-typeof-symbol': 7.25.7(@babel/core@7.25.2) + '@babel/plugin-transform-unicode-escapes': 7.25.7(@babel/core@7.25.2) + '@babel/plugin-transform-unicode-property-regex': 7.25.7(@babel/core@7.25.2) + '@babel/plugin-transform-unicode-regex': 7.25.7(@babel/core@7.25.2) + '@babel/plugin-transform-unicode-sets-regex': 7.25.7(@babel/core@7.25.2) '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.25.2) babel-plugin-polyfill-corejs2: 0.4.11(@babel/core@7.25.2) babel-plugin-polyfill-corejs3: 0.10.6(@babel/core@7.25.2) @@ -8498,6 +10281,80 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/preset-env@7.25.8(@babel/core@7.25.8)': + dependencies: + '@babel/compat-data': 7.25.8 + '@babel/core': 7.25.8 + '@babel/helper-compilation-targets': 7.25.7 + '@babel/helper-plugin-utils': 7.25.7 + '@babel/helper-validator-option': 7.25.7 + '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.25.8) + '@babel/plugin-syntax-import-assertions': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-syntax-import-attributes': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.25.8) + '@babel/plugin-transform-arrow-functions': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-async-generator-functions': 7.25.8(@babel/core@7.25.8) + '@babel/plugin-transform-async-to-generator': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-block-scoped-functions': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-block-scoping': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-class-properties': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-class-static-block': 7.25.8(@babel/core@7.25.8) + '@babel/plugin-transform-classes': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-computed-properties': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-destructuring': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-dotall-regex': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-duplicate-keys': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-dynamic-import': 7.25.8(@babel/core@7.25.8) + '@babel/plugin-transform-exponentiation-operator': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-export-namespace-from': 7.25.8(@babel/core@7.25.8) + '@babel/plugin-transform-for-of': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-function-name': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-json-strings': 7.25.8(@babel/core@7.25.8) + '@babel/plugin-transform-literals': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-logical-assignment-operators': 7.25.8(@babel/core@7.25.8) + '@babel/plugin-transform-member-expression-literals': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-modules-amd': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-modules-commonjs': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-modules-systemjs': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-modules-umd': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-named-capturing-groups-regex': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-new-target': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-nullish-coalescing-operator': 7.25.8(@babel/core@7.25.8) + '@babel/plugin-transform-numeric-separator': 7.25.8(@babel/core@7.25.8) + '@babel/plugin-transform-object-rest-spread': 7.25.8(@babel/core@7.25.8) + '@babel/plugin-transform-object-super': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-optional-catch-binding': 7.25.8(@babel/core@7.25.8) + '@babel/plugin-transform-optional-chaining': 7.25.8(@babel/core@7.25.8) + '@babel/plugin-transform-parameters': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-private-methods': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-private-property-in-object': 7.25.8(@babel/core@7.25.8) + '@babel/plugin-transform-property-literals': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-regenerator': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-reserved-words': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-shorthand-properties': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-spread': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-sticky-regex': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-template-literals': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-typeof-symbol': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-unicode-escapes': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-unicode-property-regex': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-unicode-regex': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-unicode-sets-regex': 7.25.7(@babel/core@7.25.8) + '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.25.8) + babel-plugin-polyfill-corejs2: 0.4.11(@babel/core@7.25.8) + babel-plugin-polyfill-corejs3: 0.10.6(@babel/core@7.25.8) + babel-plugin-polyfill-regenerator: 0.6.2(@babel/core@7.25.8) + core-js-compat: 3.38.1 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + '@babel/preset-flow@7.24.7(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 @@ -8508,21 +10365,41 @@ snapshots: '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.24.8 + '@babel/helper-plugin-utils': 7.25.7 + '@babel/types': 7.25.2 + esutils: 2.0.3 + + '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 '@babel/types': 7.25.2 esutils: 2.0.3 '@babel/preset-react@7.24.7(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.24.8 - '@babel/helper-validator-option': 7.24.8 + '@babel/helper-plugin-utils': 7.25.7 + '@babel/helper-validator-option': 7.25.7 '@babel/plugin-transform-react-display-name': 7.24.7(@babel/core@7.25.2) '@babel/plugin-transform-react-jsx': 7.25.2(@babel/core@7.25.2) '@babel/plugin-transform-react-jsx-development': 7.24.7(@babel/core@7.25.2) '@babel/plugin-transform-react-pure-annotations': 7.24.7(@babel/core@7.25.2) transitivePeerDependencies: - supports-color + optional: true + + '@babel/preset-react@7.24.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + '@babel/helper-validator-option': 7.25.7 + '@babel/plugin-transform-react-display-name': 7.24.7(@babel/core@7.25.8) + '@babel/plugin-transform-react-jsx': 7.25.2(@babel/core@7.25.8) + '@babel/plugin-transform-react-jsx-development': 7.24.7(@babel/core@7.25.8) + '@babel/plugin-transform-react-pure-annotations': 7.24.7(@babel/core@7.25.8) + transitivePeerDependencies: + - supports-color '@babel/preset-typescript@7.24.7(@babel/core@7.25.2)': dependencies: @@ -8535,6 +10412,17 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/preset-typescript@7.24.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.24.8 + '@babel/helper-validator-option': 7.24.8 + '@babel/plugin-syntax-jsx': 7.24.7(@babel/core@7.25.8) + '@babel/plugin-transform-modules-commonjs': 7.24.8(@babel/core@7.25.8) + '@babel/plugin-transform-typescript': 7.25.2(@babel/core@7.25.8) + transitivePeerDependencies: + - supports-color + '@babel/register@7.24.6(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 @@ -8556,6 +10444,12 @@ snapshots: '@babel/parser': 7.25.3 '@babel/types': 7.25.2 + '@babel/template@7.25.7': + dependencies: + '@babel/code-frame': 7.25.7 + '@babel/parser': 7.25.8 + '@babel/types': 7.25.8 + '@babel/traverse@7.25.3': dependencies: '@babel/code-frame': 7.24.7 @@ -8568,12 +10462,30 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/traverse@7.25.7': + dependencies: + '@babel/code-frame': 7.25.7 + '@babel/generator': 7.25.7 + '@babel/parser': 7.25.8 + '@babel/template': 7.25.7 + '@babel/types': 7.25.8 + debug: 4.3.6 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + '@babel/types@7.25.2': dependencies: '@babel/helper-string-parser': 7.24.8 '@babel/helper-validator-identifier': 7.24.7 to-fast-properties: 2.0.0 + '@babel/types@7.25.8': + dependencies: + '@babel/helper-string-parser': 7.25.7 + '@babel/helper-validator-identifier': 7.25.7 + to-fast-properties: 2.0.0 + '@bcoe/v8-coverage@0.2.3': {} '@changesets/apply-release-plan@7.0.4': @@ -8780,7 +10692,7 @@ snapshots: '@cosmjs/encoding': 0.30.1 '@cosmjs/math': 0.30.1 '@cosmjs/utils': 0.30.1 - '@noble/hashes': 1.5.0 + '@noble/hashes': 1.4.0 bn.js: 5.2.1 elliptic: 6.5.7 libsodium-wrappers: 0.7.15 @@ -8875,7 +10787,7 @@ snapshots: dependencies: ky: 0.33.3 ky-universal: 0.11.0(ky@0.33.3)(web-streams-polyfill@3.3.3) - undici: 5.28.4 + undici: 6.20.1 transitivePeerDependencies: - web-streams-polyfill @@ -8911,11 +10823,11 @@ snapshots: '@digitalcredentials/base64url-universal': 2.0.6 pako: 2.1.0 - '@digitalcredentials/ed25519-signature-2020@3.0.2(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2)))(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))(react@18.3.1))(web-streams-polyfill@3.3.3)': + '@digitalcredentials/ed25519-signature-2020@3.0.2(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2)))(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(react@18.3.1))(web-streams-polyfill@3.3.3)': dependencies: '@digitalcredentials/base58-universal': 1.0.1 '@digitalcredentials/ed25519-verification-key-2020': 3.2.2 - '@digitalcredentials/jsonld-signatures': 9.4.0(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2)))(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))(react@18.3.1))(web-streams-polyfill@3.3.3) + '@digitalcredentials/jsonld-signatures': 9.4.0(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2)))(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(react@18.3.1))(web-streams-polyfill@3.3.3) ed25519-signature-2018-context: 1.1.0 ed25519-signature-2020-context: 1.1.0 transitivePeerDependencies: @@ -8939,12 +10851,12 @@ snapshots: - domexception - web-streams-polyfill - '@digitalcredentials/jsonld-signatures@9.4.0(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2)))(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))(react@18.3.1))(web-streams-polyfill@3.3.3)': + '@digitalcredentials/jsonld-signatures@9.4.0(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2)))(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(react@18.3.1))(web-streams-polyfill@3.3.3)': dependencies: '@digitalbazaar/security-context': 1.0.1 - '@digitalcredentials/jsonld': 6.0.0(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2)))(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))(react@18.3.1))(web-streams-polyfill@3.3.3) + '@digitalcredentials/jsonld': 6.0.0(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2)))(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(react@18.3.1))(web-streams-polyfill@3.3.3) fast-text-encoding: 1.0.6 - isomorphic-webcrypto: 2.3.8(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2)))(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))(react@18.3.1)) + isomorphic-webcrypto: 2.3.8(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2)))(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(react@18.3.1)) serialize-error: 8.1.0 transitivePeerDependencies: - domexception @@ -8952,10 +10864,10 @@ snapshots: - react-native - web-streams-polyfill - '@digitalcredentials/jsonld@5.2.2(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2)))(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))(react@18.3.1))(web-streams-polyfill@3.3.3)': + '@digitalcredentials/jsonld@5.2.2(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2)))(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(react@18.3.1))(web-streams-polyfill@3.3.3)': dependencies: '@digitalcredentials/http-client': 1.2.2(web-streams-polyfill@3.3.3) - '@digitalcredentials/rdf-canonize': 1.0.0(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2)))(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))(react@18.3.1)) + '@digitalcredentials/rdf-canonize': 1.0.0(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2)))(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(react@18.3.1)) canonicalize: 1.0.8 lru-cache: 6.0.0 transitivePeerDependencies: @@ -8964,10 +10876,10 @@ snapshots: - react-native - web-streams-polyfill - '@digitalcredentials/jsonld@6.0.0(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2)))(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))(react@18.3.1))(web-streams-polyfill@3.3.3)': + '@digitalcredentials/jsonld@6.0.0(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2)))(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(react@18.3.1))(web-streams-polyfill@3.3.3)': dependencies: '@digitalcredentials/http-client': 1.2.2(web-streams-polyfill@3.3.3) - '@digitalcredentials/rdf-canonize': 1.0.0(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2)))(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))(react@18.3.1)) + '@digitalcredentials/rdf-canonize': 1.0.0(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2)))(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(react@18.3.1)) canonicalize: 1.0.8 lru-cache: 6.0.0 transitivePeerDependencies: @@ -8978,19 +10890,19 @@ snapshots: '@digitalcredentials/open-badges-context@2.1.0': {} - '@digitalcredentials/rdf-canonize@1.0.0(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2)))(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))(react@18.3.1))': + '@digitalcredentials/rdf-canonize@1.0.0(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2)))(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(react@18.3.1))': dependencies: fast-text-encoding: 1.0.6 - isomorphic-webcrypto: 2.3.8(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2)))(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))(react@18.3.1)) + isomorphic-webcrypto: 2.3.8(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2)))(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(react@18.3.1)) transitivePeerDependencies: - expo - react-native - '@digitalcredentials/vc-status-list@5.0.2(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2)))(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))(react@18.3.1))(web-streams-polyfill@3.3.3)': + '@digitalcredentials/vc-status-list@5.0.2(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2)))(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(react@18.3.1))(web-streams-polyfill@3.3.3)': dependencies: '@digitalbazaar/vc-status-list-context': 3.1.1 '@digitalcredentials/bitstring': 2.0.1 - '@digitalcredentials/vc': 4.2.0(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2)))(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))(react@18.3.1))(web-streams-polyfill@3.3.3) + '@digitalcredentials/vc': 4.2.0(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2)))(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(react@18.3.1))(web-streams-polyfill@3.3.3) credentials-context: 2.0.0 transitivePeerDependencies: - domexception @@ -8998,10 +10910,10 @@ snapshots: - react-native - web-streams-polyfill - '@digitalcredentials/vc@4.2.0(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2)))(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))(react@18.3.1))(web-streams-polyfill@3.3.3)': + '@digitalcredentials/vc@4.2.0(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2)))(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(react@18.3.1))(web-streams-polyfill@3.3.3)': dependencies: - '@digitalcredentials/jsonld': 5.2.2(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2)))(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))(react@18.3.1))(web-streams-polyfill@3.3.3) - '@digitalcredentials/jsonld-signatures': 9.4.0(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2)))(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))(react@18.3.1))(web-streams-polyfill@3.3.3) + '@digitalcredentials/jsonld': 5.2.2(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2)))(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(react@18.3.1))(web-streams-polyfill@3.3.3) + '@digitalcredentials/jsonld-signatures': 9.4.0(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2)))(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(react@18.3.1))(web-streams-polyfill@3.3.3) credentials-context: 2.0.0 transitivePeerDependencies: - domexception @@ -9009,14 +10921,14 @@ snapshots: - react-native - web-streams-polyfill - '@digitalcredentials/vc@6.0.1(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2)))(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))(react@18.3.1))(web-streams-polyfill@3.3.3)': + '@digitalcredentials/vc@6.0.1(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2)))(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(react@18.3.1))(web-streams-polyfill@3.3.3)': dependencies: '@digitalbazaar/vc-status-list': 7.1.0(web-streams-polyfill@3.3.3) - '@digitalcredentials/ed25519-signature-2020': 3.0.2(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2)))(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))(react@18.3.1))(web-streams-polyfill@3.3.3) - '@digitalcredentials/jsonld': 6.0.0(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2)))(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))(react@18.3.1))(web-streams-polyfill@3.3.3) - '@digitalcredentials/jsonld-signatures': 9.4.0(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2)))(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))(react@18.3.1))(web-streams-polyfill@3.3.3) + '@digitalcredentials/ed25519-signature-2020': 3.0.2(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2)))(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(react@18.3.1))(web-streams-polyfill@3.3.3) + '@digitalcredentials/jsonld': 6.0.0(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2)))(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(react@18.3.1))(web-streams-polyfill@3.3.3) + '@digitalcredentials/jsonld-signatures': 9.4.0(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2)))(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(react@18.3.1))(web-streams-polyfill@3.3.3) '@digitalcredentials/open-badges-context': 2.1.0 - '@digitalcredentials/vc-status-list': 5.0.2(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2)))(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))(react@18.3.1))(web-streams-polyfill@3.3.3) + '@digitalcredentials/vc-status-list': 5.0.2(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2)))(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(react@18.3.1))(web-streams-polyfill@3.3.3) credentials-context: 2.0.0 fix-esm: 1.0.1 transitivePeerDependencies: @@ -9026,6 +10938,78 @@ snapshots: - supports-color - web-streams-polyfill + '@esbuild/aix-ppc64@0.23.1': + optional: true + + '@esbuild/android-arm64@0.23.1': + optional: true + + '@esbuild/android-arm@0.23.1': + optional: true + + '@esbuild/android-x64@0.23.1': + optional: true + + '@esbuild/darwin-arm64@0.23.1': + optional: true + + '@esbuild/darwin-x64@0.23.1': + optional: true + + '@esbuild/freebsd-arm64@0.23.1': + optional: true + + '@esbuild/freebsd-x64@0.23.1': + optional: true + + '@esbuild/linux-arm64@0.23.1': + optional: true + + '@esbuild/linux-arm@0.23.1': + optional: true + + '@esbuild/linux-ia32@0.23.1': + optional: true + + '@esbuild/linux-loong64@0.23.1': + optional: true + + '@esbuild/linux-mips64el@0.23.1': + optional: true + + '@esbuild/linux-ppc64@0.23.1': + optional: true + + '@esbuild/linux-riscv64@0.23.1': + optional: true + + '@esbuild/linux-s390x@0.23.1': + optional: true + + '@esbuild/linux-x64@0.23.1': + optional: true + + '@esbuild/netbsd-x64@0.23.1': + optional: true + + '@esbuild/openbsd-arm64@0.23.1': + optional: true + + '@esbuild/openbsd-x64@0.23.1': + optional: true + + '@esbuild/sunos-x64@0.23.1': + optional: true + + '@esbuild/win32-arm64@0.23.1': + optional: true + + '@esbuild/win32-ia32@0.23.1': + optional: true + + '@esbuild/win32-x64@0.23.1': + optional: true + '@eslint-community/eslint-utils@4.4.0(eslint@8.57.0)': dependencies: eslint: 8.57.0 @@ -9306,10 +11290,10 @@ snapshots: '@expo/metro-config@0.18.11': dependencies: - '@babel/core': 7.25.2 - '@babel/generator': 7.25.0 - '@babel/parser': 7.25.3 - '@babel/types': 7.25.2 + '@babel/core': 7.25.8 + '@babel/generator': 7.25.7 + '@babel/parser': 7.25.8 + '@babel/types': 7.25.8 '@expo/config': 9.0.3 '@expo/env': 0.3.0 '@expo/json-file': 8.3.3 @@ -9341,7 +11325,7 @@ snapshots: find-up: 5.0.0 find-yarn-workspace-root: 2.0.0 js-yaml: 3.14.1 - micromatch: 4.0.7 + micromatch: 4.0.8 npm-package-arg: 7.0.0 ora: 3.4.0 split: 1.0.1 @@ -9400,12 +11384,12 @@ snapshots: find-up: 5.0.0 js-yaml: 4.1.0 - '@fastify/busboy@2.1.1': {} - '@graphql-typed-document-node/core@3.2.0(graphql@15.8.0)': dependencies: graphql: 15.8.0 + '@hapi/bourne@3.0.0': {} + '@hapi/hoek@9.3.0': {} '@hapi/topo@5.1.0': @@ -9498,7 +11482,7 @@ snapshots: jest-util: 29.7.0 slash: 3.0.0 - '@jest/core@29.7.0(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4))': + '@jest/core@29.7.0(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4))': dependencies: '@jest/console': 29.7.0 '@jest/reporters': 29.7.0 @@ -9512,7 +11496,7 @@ snapshots: exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@18.18.8)(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) + jest-config: 29.7.0(@types/node@18.18.8)(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4)) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -9628,7 +11612,7 @@ snapshots: '@jest/transform@29.7.0': dependencies: - '@babel/core': 7.25.2 + '@babel/core': 7.25.8 '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.25 babel-plugin-istanbul: 6.1.1 @@ -9710,6 +11694,27 @@ snapshots: dependencies: '@js-joda/core': 5.6.3 + '@koa/bodyparser@5.1.1(koa@2.15.3)': + dependencies: + co-body: 6.2.0 + koa: 2.15.3 + lodash.merge: 4.6.2 + type-is: 1.6.18 + + '@koa/cors@5.0.0': + dependencies: + vary: 1.1.2 + + '@koa/router@12.0.1': + dependencies: + debug: 4.3.6 + http-errors: 2.0.0 + koa-compose: 4.1.0 + methods: 1.1.2 + path-to-regexp: 6.2.2 + transitivePeerDependencies: + - supports-color + '@manypkg/find-root@1.1.0': dependencies: '@babel/runtime': 7.25.0 @@ -9768,6 +11773,15 @@ snapshots: - supports-color optional: true + '@mswjs/interceptors@0.36.9': + dependencies: + '@open-draft/deferred-promise': 2.2.0 + '@open-draft/logger': 0.3.0 + '@open-draft/until': 2.1.0 + is-node-process: 1.2.0 + outvariant: 1.4.3 + strict-event-emitter: 0.5.1 + '@multiformats/base-x@4.0.1': {} '@noble/ciphers@0.4.1': {} @@ -9778,6 +11792,8 @@ snapshots: dependencies: '@noble/hashes': 1.5.0 + '@noble/hashes@1.4.0': {} + '@noble/hashes@1.5.0': {} '@nodelib/fs.scandir@2.1.5': @@ -9796,6 +11812,15 @@ snapshots: dependencies: semver: 7.6.3 + '@open-draft/deferred-promise@2.2.0': {} + + '@open-draft/logger@0.3.0': + dependencies: + is-node-process: 1.2.0 + outvariant: 1.4.3 + + '@open-draft/until@2.1.0': {} + '@peculiar/asn1-cms@2.3.13': dependencies: '@peculiar/asn1-schema': 2.3.13 @@ -10021,6 +12046,26 @@ snapshots: - supports-color - utf-8-validate + '@react-native-community/cli-plugin-metro@10.2.3(@babel/core@7.25.8)': + dependencies: + '@react-native-community/cli-server-api': 10.1.1 + '@react-native-community/cli-tools': 10.1.1 + chalk: 4.1.2 + execa: 1.0.0 + metro: 0.73.10 + metro-config: 0.73.10 + metro-core: 0.73.10 + metro-react-native-babel-transformer: 0.73.10(@babel/core@7.25.8) + metro-resolver: 0.73.10 + metro-runtime: 0.73.10 + readline: 1.3.0 + transitivePeerDependencies: + - '@babel/core' + - bufferutil + - encoding + - supports-color + - utf-8-validate + '@react-native-community/cli-server-api@10.1.1': dependencies: '@react-native-community/cli-debugger-ui': 10.0.0 @@ -10056,14 +12101,40 @@ snapshots: dependencies: joi: 17.13.3 - '@react-native-community/cli@10.2.7(@babel/core@7.25.2)': + '@react-native-community/cli@10.2.7(@babel/core@7.25.2)': + dependencies: + '@react-native-community/cli-clean': 10.1.1 + '@react-native-community/cli-config': 10.1.1 + '@react-native-community/cli-debugger-ui': 10.0.0 + '@react-native-community/cli-doctor': 10.2.7 + '@react-native-community/cli-hermes': 10.2.7 + '@react-native-community/cli-plugin-metro': 10.2.3(@babel/core@7.25.2) + '@react-native-community/cli-server-api': 10.1.1 + '@react-native-community/cli-tools': 10.1.1 + '@react-native-community/cli-types': 10.0.0 + chalk: 4.1.2 + commander: 9.5.0 + execa: 1.0.0 + find-up: 4.1.0 + fs-extra: 8.1.0 + graceful-fs: 4.2.11 + prompts: 2.4.2 + semver: 6.3.1 + transitivePeerDependencies: + - '@babel/core' + - bufferutil + - encoding + - supports-color + - utf-8-validate + + '@react-native-community/cli@10.2.7(@babel/core@7.25.8)': dependencies: '@react-native-community/cli-clean': 10.1.1 '@react-native-community/cli-config': 10.1.1 '@react-native-community/cli-debugger-ui': 10.0.0 '@react-native-community/cli-doctor': 10.2.7 '@react-native-community/cli-hermes': 10.2.7 - '@react-native-community/cli-plugin-metro': 10.2.3(@babel/core@7.25.2) + '@react-native-community/cli-plugin-metro': 10.2.3(@babel/core@7.25.8) '@react-native-community/cli-server-api': 10.1.1 '@react-native-community/cli-tools': 10.1.1 '@react-native-community/cli-types': 10.0.0 @@ -10084,14 +12155,22 @@ snapshots: '@react-native/assets@1.0.0': {} - '@react-native/babel-plugin-codegen@0.74.87(@babel/preset-env@7.25.3(@babel/core@7.25.2))': + '@react-native/babel-plugin-codegen@0.74.87(@babel/preset-env@7.25.8(@babel/core@7.25.2))': + dependencies: + '@react-native/codegen': 0.74.87(@babel/preset-env@7.25.8(@babel/core@7.25.2)) + transitivePeerDependencies: + - '@babel/preset-env' + - supports-color + optional: true + + '@react-native/babel-plugin-codegen@0.74.87(@babel/preset-env@7.25.8(@babel/core@7.25.8))': dependencies: - '@react-native/codegen': 0.74.87(@babel/preset-env@7.25.3(@babel/core@7.25.2)) + '@react-native/codegen': 0.74.87(@babel/preset-env@7.25.8(@babel/core@7.25.8)) transitivePeerDependencies: - '@babel/preset-env' - supports-color - '@react-native/babel-preset@0.74.87(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))': + '@react-native/babel-preset@0.74.87(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))': dependencies: '@babel/core': 7.25.2 '@babel/plugin-proposal-async-generator-functions': 7.20.7(@babel/core@7.25.2) @@ -10108,46 +12187,110 @@ snapshots: '@babel/plugin-syntax-flow': 7.24.7(@babel/core@7.25.2) '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.25.2) '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.25.2) - '@babel/plugin-transform-arrow-functions': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-transform-async-to-generator': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-transform-block-scoping': 7.25.0(@babel/core@7.25.2) - '@babel/plugin-transform-classes': 7.25.0(@babel/core@7.25.2) - '@babel/plugin-transform-computed-properties': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-transform-destructuring': 7.24.8(@babel/core@7.25.2) + '@babel/plugin-transform-arrow-functions': 7.25.7(@babel/core@7.25.2) + '@babel/plugin-transform-async-to-generator': 7.25.7(@babel/core@7.25.2) + '@babel/plugin-transform-block-scoping': 7.25.7(@babel/core@7.25.2) + '@babel/plugin-transform-classes': 7.25.7(@babel/core@7.25.2) + '@babel/plugin-transform-computed-properties': 7.25.7(@babel/core@7.25.2) + '@babel/plugin-transform-destructuring': 7.25.7(@babel/core@7.25.2) '@babel/plugin-transform-flow-strip-types': 7.25.2(@babel/core@7.25.2) - '@babel/plugin-transform-function-name': 7.25.1(@babel/core@7.25.2) - '@babel/plugin-transform-literals': 7.25.2(@babel/core@7.25.2) - '@babel/plugin-transform-modules-commonjs': 7.24.8(@babel/core@7.25.2) - '@babel/plugin-transform-named-capturing-groups-regex': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-transform-parameters': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-transform-private-methods': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-transform-private-property-in-object': 7.24.7(@babel/core@7.25.2) + '@babel/plugin-transform-function-name': 7.25.7(@babel/core@7.25.2) + '@babel/plugin-transform-literals': 7.25.7(@babel/core@7.25.2) + '@babel/plugin-transform-modules-commonjs': 7.25.7(@babel/core@7.25.2) + '@babel/plugin-transform-named-capturing-groups-regex': 7.25.7(@babel/core@7.25.2) + '@babel/plugin-transform-parameters': 7.25.7(@babel/core@7.25.2) + '@babel/plugin-transform-private-methods': 7.25.7(@babel/core@7.25.2) + '@babel/plugin-transform-private-property-in-object': 7.25.8(@babel/core@7.25.2) '@babel/plugin-transform-react-display-name': 7.24.7(@babel/core@7.25.2) '@babel/plugin-transform-react-jsx': 7.25.2(@babel/core@7.25.2) '@babel/plugin-transform-react-jsx-self': 7.24.7(@babel/core@7.25.2) '@babel/plugin-transform-react-jsx-source': 7.24.7(@babel/core@7.25.2) '@babel/plugin-transform-runtime': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-transform-shorthand-properties': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-transform-spread': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-transform-sticky-regex': 7.24.7(@babel/core@7.25.2) + '@babel/plugin-transform-shorthand-properties': 7.25.7(@babel/core@7.25.2) + '@babel/plugin-transform-spread': 7.25.7(@babel/core@7.25.2) + '@babel/plugin-transform-sticky-regex': 7.25.7(@babel/core@7.25.2) '@babel/plugin-transform-typescript': 7.25.2(@babel/core@7.25.2) - '@babel/plugin-transform-unicode-regex': 7.24.7(@babel/core@7.25.2) - '@babel/template': 7.25.0 - '@react-native/babel-plugin-codegen': 0.74.87(@babel/preset-env@7.25.3(@babel/core@7.25.2)) + '@babel/plugin-transform-unicode-regex': 7.25.7(@babel/core@7.25.2) + '@babel/template': 7.25.7 + '@react-native/babel-plugin-codegen': 0.74.87(@babel/preset-env@7.25.8(@babel/core@7.25.2)) babel-plugin-transform-flow-enums: 0.0.2(@babel/core@7.25.2) react-refresh: 0.14.2 transitivePeerDependencies: - '@babel/preset-env' - supports-color + optional: true + + '@react-native/babel-preset@0.74.87(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))': + dependencies: + '@babel/core': 7.25.8 + '@babel/plugin-proposal-async-generator-functions': 7.20.7(@babel/core@7.25.8) + '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.25.8) + '@babel/plugin-proposal-export-default-from': 7.24.7(@babel/core@7.25.8) + '@babel/plugin-proposal-logical-assignment-operators': 7.20.7(@babel/core@7.25.8) + '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6(@babel/core@7.25.8) + '@babel/plugin-proposal-numeric-separator': 7.18.6(@babel/core@7.25.8) + '@babel/plugin-proposal-object-rest-spread': 7.20.7(@babel/core@7.25.8) + '@babel/plugin-proposal-optional-catch-binding': 7.18.6(@babel/core@7.25.8) + '@babel/plugin-proposal-optional-chaining': 7.21.0(@babel/core@7.25.8) + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.25.8) + '@babel/plugin-syntax-export-default-from': 7.24.7(@babel/core@7.25.8) + '@babel/plugin-syntax-flow': 7.24.7(@babel/core@7.25.8) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.25.8) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.25.8) + '@babel/plugin-transform-arrow-functions': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-async-to-generator': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-block-scoping': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-classes': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-computed-properties': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-destructuring': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-flow-strip-types': 7.25.2(@babel/core@7.25.8) + '@babel/plugin-transform-function-name': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-literals': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-modules-commonjs': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-named-capturing-groups-regex': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-parameters': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-private-methods': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-private-property-in-object': 7.25.8(@babel/core@7.25.8) + '@babel/plugin-transform-react-display-name': 7.24.7(@babel/core@7.25.8) + '@babel/plugin-transform-react-jsx': 7.25.2(@babel/core@7.25.8) + '@babel/plugin-transform-react-jsx-self': 7.24.7(@babel/core@7.25.8) + '@babel/plugin-transform-react-jsx-source': 7.24.7(@babel/core@7.25.8) + '@babel/plugin-transform-runtime': 7.24.7(@babel/core@7.25.8) + '@babel/plugin-transform-shorthand-properties': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-spread': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-sticky-regex': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-typescript': 7.25.2(@babel/core@7.25.8) + '@babel/plugin-transform-unicode-regex': 7.25.7(@babel/core@7.25.8) + '@babel/template': 7.25.7 + '@react-native/babel-plugin-codegen': 0.74.87(@babel/preset-env@7.25.8(@babel/core@7.25.8)) + babel-plugin-transform-flow-enums: 0.0.2(@babel/core@7.25.8) + react-refresh: 0.14.2 + transitivePeerDependencies: + - '@babel/preset-env' + - supports-color - '@react-native/codegen@0.74.87(@babel/preset-env@7.25.3(@babel/core@7.25.2))': + '@react-native/codegen@0.74.87(@babel/preset-env@7.25.8(@babel/core@7.25.2))': dependencies: - '@babel/parser': 7.25.3 - '@babel/preset-env': 7.25.3(@babel/core@7.25.2) + '@babel/parser': 7.25.8 + '@babel/preset-env': 7.25.8(@babel/core@7.25.2) + glob: 7.2.3 + hermes-parser: 0.19.1 + invariant: 2.2.4 + jscodeshift: 0.14.0(@babel/preset-env@7.25.8(@babel/core@7.25.2)) + mkdirp: 0.5.6 + nullthrows: 1.1.1 + transitivePeerDependencies: + - supports-color + optional: true + + '@react-native/codegen@0.74.87(@babel/preset-env@7.25.8(@babel/core@7.25.8))': + dependencies: + '@babel/parser': 7.25.8 + '@babel/preset-env': 7.25.8(@babel/core@7.25.8) glob: 7.2.3 hermes-parser: 0.19.1 invariant: 2.2.4 - jscodeshift: 0.14.0(@babel/preset-env@7.25.3(@babel/core@7.25.2)) + jscodeshift: 0.14.0(@babel/preset-env@7.25.8(@babel/core@7.25.8)) mkdirp: 0.5.6 nullthrows: 1.1.1 transitivePeerDependencies: @@ -10259,6 +12402,8 @@ snapshots: '@sinclair/typebox@0.27.8': {} + '@sindresorhus/is@5.6.0': {} + '@sinonjs/commons@3.0.1': dependencies: type-detect: 4.0.8 @@ -10269,11 +12414,11 @@ snapshots: '@sovpro/delimited-stream@1.1.0': {} - '@sphereon/did-auth-siop@0.16.1-fix.173(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4))(typescript@5.5.4)': + '@sphereon/did-auth-siop@0.16.1-fix.173(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4))(typescript@5.5.4)': dependencies: '@astronautlabs/jsonpath': 1.1.2 - '@sphereon/jarm': 0.16.1-fix.173(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4))(typescript@5.5.4) - '@sphereon/oid4vc-common': 0.16.1-fix.173(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) + '@sphereon/jarm': 0.16.1-fix.173(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4))(typescript@5.5.4) + '@sphereon/oid4vc-common': 0.16.1-fix.173(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4)) '@sphereon/pex': 5.0.0-unstable.24 '@sphereon/pex-models': 2.3.1 '@sphereon/ssi-types': 0.30.2-next.129 @@ -10314,9 +12459,9 @@ snapshots: transitivePeerDependencies: - encoding - '@sphereon/jarm@0.16.1-fix.173(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4))(typescript@5.5.4)': + '@sphereon/jarm@0.16.1-fix.173(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4))(typescript@5.5.4)': dependencies: - '@sphereon/oid4vc-common': 0.16.1-fix.173(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) + '@sphereon/oid4vc-common': 0.16.1-fix.173(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4)) valibot: 0.42.1(typescript@5.5.4) transitivePeerDependencies: - '@google-cloud/spanner' @@ -10346,9 +12491,9 @@ snapshots: '@js-joda/timezone': 2.3.0(@js-joda/core@5.6.3) format-util: 1.0.5 - '@sphereon/oid4vc-common@0.16.1-fix.173(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4))': + '@sphereon/oid4vc-common@0.16.1-fix.173(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4))': dependencies: - '@sphereon/ssi-types': 0.30.1(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) + '@sphereon/ssi-types': 0.30.1(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4)) jwt-decode: 4.0.0 sha.js: 2.4.11 uint8arrays: 3.1.1 @@ -10374,91 +12519,6 @@ snapshots: - ts-node - typeorm-aurora-data-api-driver - '@sphereon/oid4vci-client@0.16.1-fix.173(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4))': - dependencies: - '@sphereon/oid4vc-common': 0.16.1-fix.173(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) - '@sphereon/oid4vci-common': 0.16.1-fix.173(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) - '@sphereon/ssi-types': 0.30.1(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) - cross-fetch: 3.1.8 - debug: 4.3.6 - transitivePeerDependencies: - - '@google-cloud/spanner' - - '@sap/hana-client' - - better-sqlite3 - - encoding - - hdb-pool - - ioredis - - mongodb - - mssql - - mysql2 - - oracledb - - pg - - pg-native - - pg-query-stream - - redis - - sql.js - - sqlite3 - - supports-color - - ts-node - - typeorm-aurora-data-api-driver - - '@sphereon/oid4vci-common@0.16.1-fix.173(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4))': - dependencies: - '@sphereon/oid4vc-common': 0.16.1-fix.173(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) - '@sphereon/ssi-types': 0.30.1(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) - cross-fetch: 3.1.8 - debug: 4.3.6 - jwt-decode: 4.0.0 - uint8arrays: 3.1.1 - uuid: 9.0.1 - transitivePeerDependencies: - - '@google-cloud/spanner' - - '@sap/hana-client' - - better-sqlite3 - - encoding - - hdb-pool - - ioredis - - mongodb - - mssql - - mysql2 - - oracledb - - pg - - pg-native - - pg-query-stream - - redis - - sql.js - - sqlite3 - - supports-color - - ts-node - - typeorm-aurora-data-api-driver - - '@sphereon/oid4vci-issuer@0.16.1-fix.173(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4))': - dependencies: - '@sphereon/oid4vc-common': 0.16.1-fix.173(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) - '@sphereon/oid4vci-common': 0.16.1-fix.173(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) - '@sphereon/ssi-types': 0.30.1(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) - uuid: 9.0.1 - transitivePeerDependencies: - - '@google-cloud/spanner' - - '@sap/hana-client' - - better-sqlite3 - - encoding - - hdb-pool - - ioredis - - mongodb - - mssql - - mysql2 - - oracledb - - pg - - pg-native - - pg-query-stream - - redis - - sql.js - - sqlite3 - - supports-color - - ts-node - - typeorm-aurora-data-api-driver - '@sphereon/pex-models@2.3.1': {} '@sphereon/pex@5.0.0-unstable.24': @@ -10493,14 +12553,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@sphereon/ssi-sdk-ext.did-utils@0.24.1-unstable.130(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4))': + '@sphereon/ssi-sdk-ext.did-utils@0.24.1-unstable.130(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4))': dependencies: '@ethersproject/networks': 5.7.1 '@ethersproject/transactions': 5.7.0 '@sphereon/did-uni-client': 0.6.3 '@sphereon/ssi-sdk-ext.key-utils': 0.24.1-unstable.130 '@sphereon/ssi-sdk-ext.x509-utils': 0.24.1-unstable.130 - '@sphereon/ssi-sdk.agent-config': 0.29.1-unstable.161(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) + '@sphereon/ssi-sdk.agent-config': 0.29.1-unstable.161(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4)) '@sphereon/ssi-sdk.core': 0.29.1-unstable.161 '@sphereon/ssi-types': 0.29.1-unstable.161 '@stablelib/ed25519': 1.0.3 @@ -10531,12 +12591,12 @@ snapshots: - ts-node - typeorm-aurora-data-api-driver - '@sphereon/ssi-sdk-ext.identifier-resolution@0.24.1-unstable.130(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4))': + '@sphereon/ssi-sdk-ext.identifier-resolution@0.24.1-unstable.130(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4))': dependencies: - '@sphereon/ssi-sdk-ext.did-utils': 0.24.1-unstable.130(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) + '@sphereon/ssi-sdk-ext.did-utils': 0.24.1-unstable.130(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4)) '@sphereon/ssi-sdk-ext.key-utils': 0.24.1-unstable.130 '@sphereon/ssi-sdk-ext.x509-utils': 0.24.1-unstable.130 - '@sphereon/ssi-sdk.agent-config': 0.29.1-unstable.161(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) + '@sphereon/ssi-sdk.agent-config': 0.29.1-unstable.161(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4)) '@sphereon/ssi-types': 0.29.1-unstable.161 '@veramo/core': 4.2.0 '@veramo/utils': 4.2.0 @@ -10564,14 +12624,14 @@ snapshots: - ts-node - typeorm-aurora-data-api-driver - '@sphereon/ssi-sdk-ext.jwt-service@0.24.1-unstable.130(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4))': + '@sphereon/ssi-sdk-ext.jwt-service@0.24.1-unstable.130(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4))': dependencies: - '@sphereon/ssi-sdk-ext.did-utils': 0.24.1-unstable.130(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) - '@sphereon/ssi-sdk-ext.identifier-resolution': 0.24.1-unstable.130(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) + '@sphereon/ssi-sdk-ext.did-utils': 0.24.1-unstable.130(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4)) + '@sphereon/ssi-sdk-ext.identifier-resolution': 0.24.1-unstable.130(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4)) '@sphereon/ssi-sdk-ext.key-manager': 0.24.1-unstable.130 '@sphereon/ssi-sdk-ext.key-utils': 0.24.1-unstable.130 '@sphereon/ssi-sdk-ext.x509-utils': 0.24.1-unstable.130 - '@sphereon/ssi-sdk.agent-config': 0.29.1-unstable.161(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) + '@sphereon/ssi-sdk.agent-config': 0.29.1-unstable.161(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4)) '@sphereon/ssi-types': 0.29.1-unstable.161 '@veramo/core': 4.2.0 '@veramo/utils': 4.2.0 @@ -10639,12 +12699,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@sphereon/ssi-sdk.agent-config@0.29.1-unstable.161(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4))': + '@sphereon/ssi-sdk.agent-config@0.29.1-unstable.161(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4))': dependencies: '@veramo/core': 4.2.0 debug: 4.3.6 jsonpointer: 5.0.1 - typeorm: 0.3.20(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) + typeorm: 0.3.20(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4)) url-parse: 1.5.10 yaml: 2.5.0 transitivePeerDependencies: @@ -10688,11 +12748,11 @@ snapshots: transitivePeerDependencies: - supports-color - '@sphereon/ssi-types@0.30.1(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4))': + '@sphereon/ssi-types@0.30.1(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4))': dependencies: '@sd-jwt/decode': 0.7.2 '@sphereon/kmp-mdl-mdoc': 0.2.0-SNAPSHOT.22 - '@sphereon/ssi-sdk-ext.jwt-service': 0.24.1-unstable.130(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) + '@sphereon/ssi-sdk-ext.jwt-service': 0.24.1-unstable.130(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4)) debug: 4.3.6 events: 3.3.0 jwt-decode: 3.1.2 @@ -10826,60 +12886,9 @@ snapshots: '@stablelib/wipe': 1.0.1 '@stablelib/xchacha20': 1.0.1 - '@swc/core-darwin-arm64@1.7.40': - optional: true - - '@swc/core-darwin-x64@1.7.40': - optional: true - - '@swc/core-linux-arm-gnueabihf@1.7.40': - optional: true - - '@swc/core-linux-arm64-gnu@1.7.40': - optional: true - - '@swc/core-linux-arm64-musl@1.7.40': - optional: true - - '@swc/core-linux-x64-gnu@1.7.40': - optional: true - - '@swc/core-linux-x64-musl@1.7.40': - optional: true - - '@swc/core-win32-arm64-msvc@1.7.40': - optional: true - - '@swc/core-win32-ia32-msvc@1.7.40': - optional: true - - '@swc/core-win32-x64-msvc@1.7.40': - optional: true - - '@swc/core@1.7.40': - dependencies: - '@swc/counter': 0.1.3 - '@swc/types': 0.1.13 - optionalDependencies: - '@swc/core-darwin-arm64': 1.7.40 - '@swc/core-darwin-x64': 1.7.40 - '@swc/core-linux-arm-gnueabihf': 1.7.40 - '@swc/core-linux-arm64-gnu': 1.7.40 - '@swc/core-linux-arm64-musl': 1.7.40 - '@swc/core-linux-x64-gnu': 1.7.40 - '@swc/core-linux-x64-musl': 1.7.40 - '@swc/core-win32-arm64-msvc': 1.7.40 - '@swc/core-win32-ia32-msvc': 1.7.40 - '@swc/core-win32-x64-msvc': 1.7.40 - optional: true - - '@swc/counter@0.1.3': - optional: true - - '@swc/types@0.1.13': + '@szmarczak/http-timer@5.0.1': dependencies: - '@swc/counter': 0.1.3 - optional: true + defer-to-connect: 2.0.1 '@tokenizer/token@0.3.0': {} @@ -10897,6 +12906,10 @@ snapshots: '@tsconfig/node16@1.0.4': {} + '@types/accepts@1.3.7': + dependencies: + '@types/node': 18.18.8 + '@types/babel__core@7.20.5': dependencies: '@babel/parser': 7.25.3 @@ -10931,6 +12944,17 @@ snapshots: dependencies: '@types/node': 18.18.8 + '@types/content-disposition@0.5.8': {} + + '@types/cookiejar@2.1.5': {} + + '@types/cookies@0.9.0': + dependencies: + '@types/connect': 3.4.38 + '@types/express': 4.17.21 + '@types/keygrip': 1.0.6 + '@types/node': 18.18.8 + '@types/cors@2.8.17': dependencies: '@types/node': 18.18.8 @@ -10964,6 +12988,10 @@ snapshots: dependencies: '@types/node': 18.18.8 + '@types/http-assert@1.5.5': {} + + '@types/http-cache-semantics@4.0.4': {} + '@types/http-errors@2.0.4': {} '@types/inquirer@8.2.10': @@ -10997,10 +13025,29 @@ snapshots: '@types/jsonpath@0.2.4': {} + '@types/keygrip@1.0.6': {} + + '@types/koa-compose@3.2.8': + dependencies: + '@types/koa': 2.15.0 + + '@types/koa@2.15.0': + dependencies: + '@types/accepts': 1.3.7 + '@types/content-disposition': 0.5.8 + '@types/cookies': 0.9.0 + '@types/http-assert': 1.5.5 + '@types/http-errors': 2.0.4 + '@types/keygrip': 1.0.6 + '@types/koa-compose': 3.2.8 + '@types/node': 18.18.8 + '@types/long@4.0.2': {} '@types/luxon@3.4.2': {} + '@types/methods@1.1.4': {} + '@types/mime@1.3.5': {} '@types/multer@1.4.11': @@ -11017,6 +13064,11 @@ snapshots: '@types/object-inspect@1.13.0': {} + '@types/oidc-provider@8.5.2': + dependencies: + '@types/koa': 2.15.0 + '@types/node': 18.18.8 + '@types/qs@6.9.15': {} '@types/range-parser@1.2.7': {} @@ -11052,6 +13104,18 @@ snapshots: '@types/stack-utils@2.0.3': {} + '@types/superagent@8.1.9': + dependencies: + '@types/cookiejar': 2.1.5 + '@types/methods': 1.1.4 + '@types/node': 18.18.8 + form-data: 4.0.0 + + '@types/supertest@6.0.2': + dependencies: + '@types/methods': 1.1.4 + '@types/superagent': 8.1.9 + '@types/through@0.0.33': dependencies: '@types/node': 18.18.8 @@ -11483,13 +13547,13 @@ snapshots: dependencies: '@babel/core': 7.25.2 - babel-jest@29.7.0(@babel/core@7.25.2): + babel-jest@29.7.0(@babel/core@7.25.8): dependencies: - '@babel/core': 7.25.2 + '@babel/core': 7.25.8 '@jest/transform': 29.7.0 '@types/babel__core': 7.20.5 babel-plugin-istanbul: 6.1.1 - babel-preset-jest: 29.6.3(@babel/core@7.25.2) + babel-preset-jest: 29.6.3(@babel/core@7.25.8) chalk: 4.1.2 graceful-fs: 4.2.11 slash: 3.0.0 @@ -11522,6 +13586,15 @@ snapshots: transitivePeerDependencies: - supports-color + babel-plugin-polyfill-corejs2@0.4.11(@babel/core@7.25.8): + dependencies: + '@babel/compat-data': 7.25.2 + '@babel/core': 7.25.8 + '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.25.8) + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + babel-plugin-polyfill-corejs3@0.10.6(@babel/core@7.25.2): dependencies: '@babel/core': 7.25.2 @@ -11530,6 +13603,14 @@ snapshots: transitivePeerDependencies: - supports-color + babel-plugin-polyfill-corejs3@0.10.6(@babel/core@7.25.8): + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.25.8) + core-js-compat: 3.38.1 + transitivePeerDependencies: + - supports-color + babel-plugin-polyfill-regenerator@0.6.2(@babel/core@7.25.2): dependencies: '@babel/core': 7.25.2 @@ -11537,10 +13618,17 @@ snapshots: transitivePeerDependencies: - supports-color + babel-plugin-polyfill-regenerator@0.6.2(@babel/core@7.25.8): + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.25.8) + transitivePeerDependencies: + - supports-color + babel-plugin-react-compiler@0.0.0-experimental-7d62301-20240819: dependencies: '@babel/generator': 7.2.0 - '@babel/types': 7.25.2 + '@babel/types': 7.25.8 chalk: 4.1.2 invariant: 2.2.4 pretty-format: 24.9.0 @@ -11556,35 +13644,60 @@ snapshots: '@babel/plugin-syntax-flow': 7.24.7(@babel/core@7.25.2) transitivePeerDependencies: - '@babel/core' + optional: true - babel-preset-current-node-syntax@1.1.0(@babel/core@7.25.2): + babel-plugin-transform-flow-enums@0.0.2(@babel/core@7.25.8): dependencies: - '@babel/core': 7.25.2 - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.25.2) - '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.25.2) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.25.2) - '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.25.2) - '@babel/plugin-syntax-import-attributes': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.25.2) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.25.2) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.25.2) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.25.2) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.25.2) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.25.2) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.25.2) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.25.2) - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.25.2) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.25.2) + '@babel/plugin-syntax-flow': 7.24.7(@babel/core@7.25.8) + transitivePeerDependencies: + - '@babel/core' - babel-preset-expo@11.0.14(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2)): + babel-preset-current-node-syntax@1.1.0(@babel/core@7.25.8): + dependencies: + '@babel/core': 7.25.8 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.25.8) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.25.8) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.25.8) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.25.8) + '@babel/plugin-syntax-import-attributes': 7.24.7(@babel/core@7.25.8) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.25.8) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.25.8) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.25.8) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.25.8) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.25.8) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.25.8) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.25.8) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.25.8) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.25.8) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.25.8) + + babel-preset-expo@11.0.14(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2)): dependencies: '@babel/plugin-proposal-decorators': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-transform-export-namespace-from': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-transform-object-rest-spread': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-transform-parameters': 7.24.7(@babel/core@7.25.2) + '@babel/plugin-transform-export-namespace-from': 7.25.8(@babel/core@7.25.2) + '@babel/plugin-transform-object-rest-spread': 7.25.8(@babel/core@7.25.2) + '@babel/plugin-transform-parameters': 7.25.7(@babel/core@7.25.2) '@babel/preset-react': 7.24.7(@babel/core@7.25.2) '@babel/preset-typescript': 7.24.7(@babel/core@7.25.2) - '@react-native/babel-preset': 0.74.87(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2)) + '@react-native/babel-preset': 0.74.87(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2)) + babel-plugin-react-compiler: 0.0.0-experimental-7d62301-20240819 + babel-plugin-react-native-web: 0.19.12 + react-refresh: 0.14.2 + transitivePeerDependencies: + - '@babel/core' + - '@babel/preset-env' + - supports-color + optional: true + + babel-preset-expo@11.0.14(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8)): + dependencies: + '@babel/plugin-proposal-decorators': 7.24.7(@babel/core@7.25.8) + '@babel/plugin-transform-export-namespace-from': 7.25.8(@babel/core@7.25.8) + '@babel/plugin-transform-object-rest-spread': 7.25.8(@babel/core@7.25.8) + '@babel/plugin-transform-parameters': 7.25.7(@babel/core@7.25.8) + '@babel/preset-react': 7.24.7(@babel/core@7.25.8) + '@babel/preset-typescript': 7.24.7(@babel/core@7.25.8) + '@react-native/babel-preset': 0.74.87(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8)) babel-plugin-react-compiler: 0.0.0-experimental-7d62301-20240819 babel-plugin-react-native-web: 0.19.12 react-refresh: 0.14.2 @@ -11626,11 +13739,44 @@ snapshots: transitivePeerDependencies: - supports-color - babel-preset-jest@29.6.3(@babel/core@7.25.2): + babel-preset-fbjs@3.4.0(@babel/core@7.25.8): + dependencies: + '@babel/core': 7.25.8 + '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.25.8) + '@babel/plugin-proposal-object-rest-spread': 7.20.7(@babel/core@7.25.8) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.25.8) + '@babel/plugin-syntax-flow': 7.24.7(@babel/core@7.25.8) + '@babel/plugin-syntax-jsx': 7.24.7(@babel/core@7.25.8) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.25.8) + '@babel/plugin-transform-arrow-functions': 7.24.7(@babel/core@7.25.8) + '@babel/plugin-transform-block-scoped-functions': 7.24.7(@babel/core@7.25.8) + '@babel/plugin-transform-block-scoping': 7.25.0(@babel/core@7.25.8) + '@babel/plugin-transform-classes': 7.25.0(@babel/core@7.25.8) + '@babel/plugin-transform-computed-properties': 7.24.7(@babel/core@7.25.8) + '@babel/plugin-transform-destructuring': 7.24.8(@babel/core@7.25.8) + '@babel/plugin-transform-flow-strip-types': 7.25.2(@babel/core@7.25.8) + '@babel/plugin-transform-for-of': 7.24.7(@babel/core@7.25.8) + '@babel/plugin-transform-function-name': 7.25.1(@babel/core@7.25.8) + '@babel/plugin-transform-literals': 7.25.2(@babel/core@7.25.8) + '@babel/plugin-transform-member-expression-literals': 7.24.7(@babel/core@7.25.8) + '@babel/plugin-transform-modules-commonjs': 7.24.8(@babel/core@7.25.8) + '@babel/plugin-transform-object-super': 7.24.7(@babel/core@7.25.8) + '@babel/plugin-transform-parameters': 7.24.7(@babel/core@7.25.8) + '@babel/plugin-transform-property-literals': 7.24.7(@babel/core@7.25.8) + '@babel/plugin-transform-react-display-name': 7.24.7(@babel/core@7.25.8) + '@babel/plugin-transform-react-jsx': 7.25.2(@babel/core@7.25.8) + '@babel/plugin-transform-shorthand-properties': 7.24.7(@babel/core@7.25.8) + '@babel/plugin-transform-spread': 7.24.7(@babel/core@7.25.8) + '@babel/plugin-transform-template-literals': 7.24.7(@babel/core@7.25.8) + babel-plugin-syntax-trailing-function-commas: 7.0.0-beta.0 + transitivePeerDependencies: + - supports-color + + babel-preset-jest@29.6.3(@babel/core@7.25.8): dependencies: - '@babel/core': 7.25.2 + '@babel/core': 7.25.8 babel-plugin-jest-hoist: 29.6.3 - babel-preset-current-node-syntax: 1.1.0(@babel/core@7.25.2) + babel-preset-current-node-syntax: 1.1.0(@babel/core@7.25.8) balanced-match@1.0.2: {} @@ -11745,6 +13891,13 @@ snapshots: node-releases: 2.0.18 update-browserslist-db: 1.1.0(browserslist@4.23.3) + browserslist@4.24.0: + dependencies: + caniuse-lite: 1.0.30001668 + electron-to-chromium: 1.5.38 + node-releases: 2.0.18 + update-browserslist-db: 1.1.0(browserslist@4.24.0) + bs-logger@0.2.6: dependencies: fast-json-stable-stringify: 2.1.0 @@ -11805,6 +13958,23 @@ snapshots: tar: 6.2.1 unique-filename: 3.0.0 + cache-content-type@1.0.1: + dependencies: + mime-types: 2.1.35 + ylru: 1.4.0 + + cacheable-lookup@7.0.0: {} + + cacheable-request@10.2.14: + dependencies: + '@types/http-cache-semantics': 4.0.4 + get-stream: 6.0.1 + http-cache-semantics: 4.1.1 + keyv: 4.5.4 + mimic-response: 4.0.0 + normalize-url: 8.0.1 + responselike: 3.0.0 + call-bind@1.0.7: dependencies: es-define-property: 1.0.0 @@ -11831,6 +14001,8 @@ snapshots: caniuse-lite@1.0.30001651: {} + caniuse-lite@1.0.30001668: {} + canonicalize@1.0.8: {} canonicalize@2.0.0: {} @@ -11930,6 +14102,14 @@ snapshots: clone@2.1.2: {} + co-body@6.2.0: + dependencies: + '@hapi/bourne': 3.0.0 + inflation: 2.1.0 + qs: 6.13.0 + raw-body: 2.5.2 + type-is: 1.6.18 + co@4.6.0: {} collect-v8-coverage@1.0.2: {} @@ -11994,6 +14174,8 @@ snapshots: compare-versions@6.1.1: {} + component-emitter@1.3.1: {} + component-type@1.2.2: {} compressible@2.0.18: @@ -12044,6 +14226,13 @@ snapshots: cookie@0.6.0: {} + cookiejar@2.1.4: {} + + cookies@0.9.1: + dependencies: + depd: 2.0.0 + keygrip: 1.1.0 + core-js-compat@3.38.1: dependencies: browserslist: 4.23.3 @@ -12067,13 +14256,13 @@ snapshots: long: 4.0.0 protobufjs: 6.11.4 - create-jest@29.7.0(@types/node@18.18.8)(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)): + create-jest@29.7.0(@types/node@18.18.8)(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4)): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@18.18.8)(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) + jest-config: 29.7.0(@types/node@18.18.8)(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4)) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -12178,8 +14367,14 @@ snapshots: decode-uri-component@0.2.2: {} + decompress-response@6.0.0: + dependencies: + mimic-response: 3.1.0 + dedent@1.5.3: {} + deep-equal@1.0.1: {} + deep-extend@0.6.0: {} deep-is@0.1.4: {} @@ -12197,6 +14392,8 @@ snapshots: dependencies: clone: 1.0.4 + defer-to-connect@2.0.1: {} + define-data-property@1.1.4: dependencies: es-define-property: 1.0.0 @@ -12228,6 +14425,8 @@ snapshots: denodeify@1.2.1: {} + depd@1.1.2: {} + depd@2.0.0: {} deprecated-react-native-prop-types@3.0.2: @@ -12251,6 +14450,11 @@ snapshots: detect-newline@3.1.0: {} + dezalgo@1.0.4: + dependencies: + asap: 2.0.6 + wrappy: 1.0.2 + did-jwt-vc@3.2.15: dependencies: did-jwt: 7.4.7 @@ -12333,6 +14537,8 @@ snapshots: electron-to-chromium@1.5.13: {} + electron-to-chromium@1.5.38: {} + elliptic@6.5.4: dependencies: bn.js: 4.12.0 @@ -12487,6 +14693,33 @@ snapshots: d: 1.0.2 ext: 1.7.0 + esbuild@0.23.1: + optionalDependencies: + '@esbuild/aix-ppc64': 0.23.1 + '@esbuild/android-arm': 0.23.1 + '@esbuild/android-arm64': 0.23.1 + '@esbuild/android-x64': 0.23.1 + '@esbuild/darwin-arm64': 0.23.1 + '@esbuild/darwin-x64': 0.23.1 + '@esbuild/freebsd-arm64': 0.23.1 + '@esbuild/freebsd-x64': 0.23.1 + '@esbuild/linux-arm': 0.23.1 + '@esbuild/linux-arm64': 0.23.1 + '@esbuild/linux-ia32': 0.23.1 + '@esbuild/linux-loong64': 0.23.1 + '@esbuild/linux-mips64el': 0.23.1 + '@esbuild/linux-ppc64': 0.23.1 + '@esbuild/linux-riscv64': 0.23.1 + '@esbuild/linux-s390x': 0.23.1 + '@esbuild/linux-x64': 0.23.1 + '@esbuild/netbsd-x64': 0.23.1 + '@esbuild/openbsd-arm64': 0.23.1 + '@esbuild/openbsd-x64': 0.23.1 + '@esbuild/sunos-x64': 0.23.1 + '@esbuild/win32-arm64': 0.23.1 + '@esbuild/win32-ia32': 0.23.1 + '@esbuild/win32-x64': 0.23.1 + escalade@3.1.2: {} escape-html@1.0.3: {} @@ -12666,6 +14899,8 @@ snapshots: esutils@2.0.3: {} + eta@3.5.0: {} + etag@1.8.1: {} event-emitter@0.3.5: @@ -12711,35 +14946,70 @@ snapshots: jest-message-util: 29.7.0 jest-util: 29.7.0 - expo-asset@10.0.10(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))): + expo-asset@10.0.10(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))): + dependencies: + expo: 51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2)) + expo-constants: 16.0.2(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))) + invariant: 2.2.4 + md5-file: 3.2.3 + transitivePeerDependencies: + - supports-color + optional: true + + expo-asset@10.0.10(expo@51.0.29(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))): dependencies: - expo: 51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2)) - expo-constants: 16.0.2(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))) + expo: 51.0.29(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8)) + expo-constants: 16.0.2(expo@51.0.29(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))) invariant: 2.2.4 md5-file: 3.2.3 transitivePeerDependencies: - supports-color - expo-constants@16.0.2(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))): + expo-constants@16.0.2(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))): + dependencies: + '@expo/config': 9.0.3 + '@expo/env': 0.3.0 + expo: 51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2)) + transitivePeerDependencies: + - supports-color + optional: true + + expo-constants@16.0.2(expo@51.0.29(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))): dependencies: '@expo/config': 9.0.3 '@expo/env': 0.3.0 - expo: 51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2)) + expo: 51.0.29(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8)) transitivePeerDependencies: - supports-color - expo-file-system@17.0.1(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))): + expo-file-system@17.0.1(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))): + dependencies: + expo: 51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2)) + optional: true + + expo-file-system@17.0.1(expo@51.0.29(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))): + dependencies: + expo: 51.0.29(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8)) + + expo-font@12.0.9(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))): dependencies: - expo: 51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2)) + expo: 51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2)) + fontfaceobserver: 2.3.0 + optional: true - expo-font@12.0.9(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))): + expo-font@12.0.9(expo@51.0.29(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))): dependencies: - expo: 51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2)) + expo: 51.0.29(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8)) fontfaceobserver: 2.3.0 - expo-keep-awake@13.0.2(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))): + expo-keep-awake@13.0.2(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))): + dependencies: + expo: 51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2)) + optional: true + + expo-keep-awake@13.0.2(expo@51.0.29(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))): dependencies: - expo: 51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2)) + expo: 51.0.29(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8)) expo-modules-autolinking@0.0.3: dependencies: @@ -12764,13 +15034,39 @@ snapshots: dependencies: invariant: 2.2.4 - expo-random@14.0.1(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))): + expo-random@14.0.1(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))): dependencies: base64-js: 1.5.1 - expo: 51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2)) + expo: 51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2)) + optional: true + + expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2)): + dependencies: + '@babel/runtime': 7.25.0 + '@expo/cli': 0.18.29(expo-modules-autolinking@1.11.2) + '@expo/config': 9.0.3 + '@expo/config-plugins': 8.0.8 + '@expo/metro-config': 0.18.11 + '@expo/vector-icons': 14.0.2 + babel-preset-expo: 11.0.14(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2)) + expo-asset: 10.0.10(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))) + expo-file-system: 17.0.1(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))) + expo-font: 12.0.9(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))) + expo-keep-awake: 13.0.2(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))) + expo-modules-autolinking: 1.11.2 + expo-modules-core: 1.12.21 + fbemitter: 3.0.0 + whatwg-url-without-unicode: 8.0.0-3 + transitivePeerDependencies: + - '@babel/core' + - '@babel/preset-env' + - bufferutil + - encoding + - supports-color + - utf-8-validate optional: true - expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2)): + expo@51.0.29(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8)): dependencies: '@babel/runtime': 7.25.0 '@expo/cli': 0.18.29(expo-modules-autolinking@1.11.2) @@ -12778,11 +15074,11 @@ snapshots: '@expo/config-plugins': 8.0.8 '@expo/metro-config': 0.18.11 '@expo/vector-icons': 14.0.2 - babel-preset-expo: 11.0.14(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2)) - expo-asset: 10.0.10(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))) - expo-file-system: 17.0.1(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))) - expo-font: 12.0.9(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))) - expo-keep-awake: 13.0.2(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))) + babel-preset-expo: 11.0.14(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8)) + expo-asset: 10.0.10(expo@51.0.29(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))) + expo-file-system: 17.0.1(expo@51.0.29(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))) + expo-font: 12.0.9(expo@51.0.29(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))) + expo-keep-awake: 13.0.2(expo@51.0.29(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))) expo-modules-autolinking: 1.11.2 expo-modules-core: 1.12.21 fbemitter: 3.0.0 @@ -12861,6 +15157,8 @@ snapshots: fast-levenshtein@2.0.6: {} + fast-safe-stringify@2.1.1: {} + fast-text-encoding@1.0.6: {} fast-uri@3.0.1: {} @@ -12994,7 +15292,7 @@ snapshots: find-yarn-workspace-root@2.0.0: dependencies: - micromatch: 4.0.7 + micromatch: 4.0.8 fix-esm@1.0.1: dependencies: @@ -13027,6 +15325,8 @@ snapshots: cross-spawn: 7.0.3 signal-exit: 4.1.0 + form-data-encoder@2.1.4: {} + form-data@3.0.1: dependencies: asynckit: 0.4.0 @@ -13045,6 +15345,12 @@ snapshots: dependencies: fetch-blob: 3.2.0 + formidable@3.5.2: + dependencies: + dezalgo: 1.0.4 + hexoid: 2.0.0 + once: 1.4.0 + forwarded@0.2.0: {} freeport-async@2.0.0: {} @@ -13224,6 +15530,20 @@ snapshots: dependencies: get-intrinsic: 1.2.4 + got@13.0.0: + dependencies: + '@sindresorhus/is': 5.6.0 + '@szmarczak/http-timer': 5.0.1 + cacheable-lookup: 7.0.0 + cacheable-request: 10.2.14 + decompress-response: 6.0.0 + form-data-encoder: 2.1.4 + get-stream: 6.0.1 + http2-wrapper: 2.2.1 + lowercase-keys: 3.0.0 + p-cancelable: 3.0.0 + responselike: 3.0.0 + graceful-fs@4.2.11: {} graphemer@1.4.0: {} @@ -13290,6 +15610,8 @@ snapshots: dependencies: source-map: 0.7.4 + hexoid@2.0.0: {} + highlight.js@10.7.3: {} hmac-drbg@1.0.1: @@ -13304,6 +15626,21 @@ snapshots: html-escaper@2.0.2: {} + http-assert@1.5.0: + dependencies: + deep-equal: 1.0.1 + http-errors: 1.8.1 + + http-cache-semantics@4.1.1: {} + + http-errors@1.8.1: + dependencies: + depd: 1.1.2 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 1.5.0 + toidentifier: 1.0.1 + http-errors@2.0.0: dependencies: depd: 2.0.0 @@ -13312,6 +15649,11 @@ snapshots: statuses: 2.0.1 toidentifier: 1.0.1 + http2-wrapper@2.2.1: + dependencies: + quick-lru: 5.1.1 + resolve-alpn: 1.2.1 + https-proxy-agent@5.0.1: dependencies: agent-base: 6.0.2 @@ -13354,6 +15696,8 @@ snapshots: indent-string@4.0.0: {} + inflation@2.1.0: {} + inflight@1.0.6: dependencies: once: 1.4.0 @@ -13493,6 +15837,8 @@ snapshots: is-negative-zero@2.0.3: {} + is-node-process@1.2.0: {} + is-number-object@1.0.7: dependencies: has-tostringtag: 1.0.2 @@ -13564,7 +15910,7 @@ snapshots: isobject@3.0.1: {} - isomorphic-webcrypto@2.3.8(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2)))(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))(react@18.3.1)): + isomorphic-webcrypto@2.3.8(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2)))(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(react@18.3.1)): dependencies: '@peculiar/webcrypto': 1.5.0 asmcrypto.js: 0.22.0 @@ -13576,8 +15922,8 @@ snapshots: optionalDependencies: '@unimodules/core': 7.1.2 '@unimodules/react-native-adapter': 6.3.9 - expo-random: 14.0.1(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))) - react-native-securerandom: 0.1.1(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))(react@18.3.1)) + expo-random: 14.0.1(expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))) + react-native-securerandom: 0.1.1(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(react@18.3.1)) transitivePeerDependencies: - expo - react-native @@ -13590,7 +15936,7 @@ snapshots: istanbul-lib-instrument@5.2.1: dependencies: - '@babel/core': 7.25.2 + '@babel/core': 7.25.8 '@babel/parser': 7.25.3 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 @@ -13600,7 +15946,7 @@ snapshots: istanbul-lib-instrument@6.0.3: dependencies: - '@babel/core': 7.25.2 + '@babel/core': 7.25.8 '@babel/parser': 7.25.3 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 @@ -13672,16 +16018,16 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@29.7.0(@types/node@18.18.8)(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)): + jest-cli@29.7.0(@types/node@18.18.8)(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) + '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4)) '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@18.18.8)(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) + create-jest: 29.7.0(@types/node@18.18.8)(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4)) exit: 0.1.2 import-local: 3.2.0 - jest-config: 29.7.0(@types/node@18.18.8)(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) + jest-config: 29.7.0(@types/node@18.18.8)(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4)) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -13691,12 +16037,12 @@ snapshots: - supports-color - ts-node - jest-config@29.7.0(@types/node@18.18.8)(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)): + jest-config@29.7.0(@types/node@18.18.8)(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4)): dependencies: - '@babel/core': 7.25.2 + '@babel/core': 7.25.8 '@jest/test-sequencer': 29.7.0 '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.25.2) + babel-jest: 29.7.0(@babel/core@7.25.8) chalk: 4.1.2 ci-info: 3.9.0 deepmerge: 4.3.1 @@ -13717,7 +16063,7 @@ snapshots: strip-json-comments: 3.1.1 optionalDependencies: '@types/node': 18.18.8 - ts-node: 10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4) + ts-node: 10.9.2(@types/node@18.18.8)(typescript@5.5.4) transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -13887,15 +16233,15 @@ snapshots: jest-snapshot@29.7.0: dependencies: - '@babel/core': 7.25.2 + '@babel/core': 7.25.8 '@babel/generator': 7.25.0 - '@babel/plugin-syntax-jsx': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-syntax-typescript': 7.24.7(@babel/core@7.25.2) + '@babel/plugin-syntax-jsx': 7.24.7(@babel/core@7.25.8) + '@babel/plugin-syntax-typescript': 7.24.7(@babel/core@7.25.8) '@babel/types': 7.25.2 '@jest/expect-utils': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - babel-preset-current-node-syntax: 1.1.0(@babel/core@7.25.2) + babel-preset-current-node-syntax: 1.1.0(@babel/core@7.25.8) chalk: 4.1.2 expect: 29.7.0 graceful-fs: 4.2.11 @@ -13970,12 +16316,12 @@ snapshots: merge-stream: 2.0.0 supports-color: 8.1.1 - jest@29.7.0(@types/node@18.18.8)(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)): + jest@29.7.0(@types/node@18.18.8)(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) + '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4)) '@jest/types': 29.6.3 import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@18.18.8)(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) + jest-cli: 29.7.0(@types/node@18.18.8)(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -13994,6 +16340,8 @@ snapshots: join-component@1.1.0: {} + jose@5.8.0: {} + js-base64@3.7.7: {} js-crypto-aes@1.0.6: @@ -14090,7 +16438,32 @@ snapshots: jsc-safe-url@0.2.4: {} - jscodeshift@0.14.0(@babel/preset-env@7.25.3(@babel/core@7.25.2)): + jscodeshift@0.14.0(@babel/preset-env@7.25.8(@babel/core@7.25.2)): + dependencies: + '@babel/core': 7.25.2 + '@babel/parser': 7.25.3 + '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.25.2) + '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6(@babel/core@7.25.2) + '@babel/plugin-proposal-optional-chaining': 7.21.0(@babel/core@7.25.2) + '@babel/plugin-transform-modules-commonjs': 7.24.8(@babel/core@7.25.2) + '@babel/preset-env': 7.25.8(@babel/core@7.25.2) + '@babel/preset-flow': 7.24.7(@babel/core@7.25.2) + '@babel/preset-typescript': 7.24.7(@babel/core@7.25.2) + '@babel/register': 7.24.6(@babel/core@7.25.2) + babel-core: 7.0.0-bridge.0(@babel/core@7.25.2) + chalk: 4.1.2 + flow-parser: 0.185.2 + graceful-fs: 4.2.11 + micromatch: 4.0.7 + neo-async: 2.6.2 + node-dir: 0.1.17 + recast: 0.21.5 + temp: 0.8.4 + write-file-atomic: 2.4.3 + transitivePeerDependencies: + - supports-color + + jscodeshift@0.14.0(@babel/preset-env@7.25.8(@babel/core@7.25.8)): dependencies: '@babel/core': 7.25.2 '@babel/parser': 7.25.3 @@ -14098,7 +16471,7 @@ snapshots: '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6(@babel/core@7.25.2) '@babel/plugin-proposal-optional-chaining': 7.21.0(@babel/core@7.25.2) '@babel/plugin-transform-modules-commonjs': 7.24.8(@babel/core@7.25.2) - '@babel/preset-env': 7.25.3(@babel/core@7.25.2) + '@babel/preset-env': 7.25.8(@babel/core@7.25.8) '@babel/preset-flow': 7.24.7(@babel/core@7.25.2) '@babel/preset-typescript': 7.24.7(@babel/core@7.25.2) '@babel/register': 7.24.6(@babel/core@7.25.2) @@ -14119,6 +16492,8 @@ snapshots: jsesc@2.5.2: {} + jsesc@3.0.2: {} + json-buffer@3.0.1: {} json-parse-better-errors@1.0.2: {} @@ -14194,6 +16569,10 @@ snapshots: jwt-decode@4.0.0: {} + keygrip@1.1.0: + dependencies: + tsscmp: 1.0.6 + keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -14202,6 +16581,41 @@ snapshots: kleur@3.0.3: {} + koa-compose@4.1.0: {} + + koa-convert@2.0.0: + dependencies: + co: 4.6.0 + koa-compose: 4.1.0 + + koa@2.15.3: + dependencies: + accepts: 1.3.8 + cache-content-type: 1.0.1 + content-disposition: 0.5.4 + content-type: 1.0.5 + cookies: 0.9.1 + debug: 4.3.6 + delegates: 1.0.0 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 1.0.2 + escape-html: 1.0.3 + fresh: 0.5.2 + http-assert: 1.5.0 + http-errors: 1.8.1 + is-generator-function: 1.0.10 + koa-compose: 4.1.0 + koa-convert: 2.0.0 + on-finished: 2.4.1 + only: 0.0.2 + parseurl: 1.3.3 + statuses: 1.5.0 + type-is: 1.6.18 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + ky-universal@0.11.0(ky@0.33.3)(web-streams-polyfill@3.3.3): dependencies: abort-controller: 3.0.0 @@ -14362,6 +16776,8 @@ snapshots: dependencies: js-tokens: 4.0.0 + lowercase-keys@3.0.0: {} + lru-cache@10.4.3: {} lru-cache@4.1.5: @@ -14556,6 +16972,49 @@ snapshots: transitivePeerDependencies: - supports-color + metro-react-native-babel-preset@0.73.10(@babel/core@7.25.8): + dependencies: + '@babel/core': 7.25.8 + '@babel/plugin-proposal-async-generator-functions': 7.20.7(@babel/core@7.25.8) + '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.25.8) + '@babel/plugin-proposal-export-default-from': 7.24.7(@babel/core@7.25.8) + '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6(@babel/core@7.25.8) + '@babel/plugin-proposal-object-rest-spread': 7.20.7(@babel/core@7.25.8) + '@babel/plugin-proposal-optional-catch-binding': 7.18.6(@babel/core@7.25.8) + '@babel/plugin-proposal-optional-chaining': 7.21.0(@babel/core@7.25.8) + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.25.8) + '@babel/plugin-syntax-export-default-from': 7.24.7(@babel/core@7.25.8) + '@babel/plugin-syntax-flow': 7.24.7(@babel/core@7.25.8) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.25.8) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.25.8) + '@babel/plugin-transform-arrow-functions': 7.24.7(@babel/core@7.25.8) + '@babel/plugin-transform-async-to-generator': 7.24.7(@babel/core@7.25.8) + '@babel/plugin-transform-block-scoping': 7.25.0(@babel/core@7.25.8) + '@babel/plugin-transform-classes': 7.25.0(@babel/core@7.25.8) + '@babel/plugin-transform-computed-properties': 7.24.7(@babel/core@7.25.8) + '@babel/plugin-transform-destructuring': 7.24.8(@babel/core@7.25.8) + '@babel/plugin-transform-flow-strip-types': 7.25.2(@babel/core@7.25.8) + '@babel/plugin-transform-function-name': 7.25.1(@babel/core@7.25.8) + '@babel/plugin-transform-literals': 7.25.2(@babel/core@7.25.8) + '@babel/plugin-transform-modules-commonjs': 7.24.8(@babel/core@7.25.8) + '@babel/plugin-transform-named-capturing-groups-regex': 7.24.7(@babel/core@7.25.8) + '@babel/plugin-transform-parameters': 7.24.7(@babel/core@7.25.8) + '@babel/plugin-transform-react-display-name': 7.24.7(@babel/core@7.25.8) + '@babel/plugin-transform-react-jsx': 7.25.2(@babel/core@7.25.8) + '@babel/plugin-transform-react-jsx-self': 7.24.7(@babel/core@7.25.8) + '@babel/plugin-transform-react-jsx-source': 7.24.7(@babel/core@7.25.8) + '@babel/plugin-transform-runtime': 7.24.7(@babel/core@7.25.8) + '@babel/plugin-transform-shorthand-properties': 7.24.7(@babel/core@7.25.8) + '@babel/plugin-transform-spread': 7.24.7(@babel/core@7.25.8) + '@babel/plugin-transform-sticky-regex': 7.24.7(@babel/core@7.25.8) + '@babel/plugin-transform-template-literals': 7.24.7(@babel/core@7.25.8) + '@babel/plugin-transform-typescript': 7.25.2(@babel/core@7.25.8) + '@babel/plugin-transform-unicode-regex': 7.24.7(@babel/core@7.25.8) + '@babel/template': 7.25.0 + react-refresh: 0.4.3 + transitivePeerDependencies: + - supports-color + metro-react-native-babel-transformer@0.73.10(@babel/core@7.25.2): dependencies: '@babel/core': 7.25.2 @@ -14568,6 +17027,18 @@ snapshots: transitivePeerDependencies: - supports-color + metro-react-native-babel-transformer@0.73.10(@babel/core@7.25.8): + dependencies: + '@babel/core': 7.25.8 + babel-preset-fbjs: 3.4.0(@babel/core@7.25.8) + hermes-parser: 0.8.0 + metro-babel-transformer: 0.73.10 + metro-react-native-babel-preset: 0.73.10(@babel/core@7.25.8) + metro-source-map: 0.73.10 + nullthrows: 1.1.1 + transitivePeerDependencies: + - supports-color + metro-resolver@0.73.10: dependencies: absolute-path: 0.0.0 @@ -14696,6 +17167,11 @@ snapshots: braces: 3.0.3 picomatch: 2.3.1 + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + mime-db@1.52.0: {} mime-db@1.53.0: {} @@ -14712,6 +17188,10 @@ snapshots: mimic-fn@2.1.0: {} + mimic-response@3.1.0: {} + + mimic-response@4.0.0: {} + minimalistic-assert@1.0.1: {} minimalistic-crypto-utils@1.0.1: {} @@ -14809,6 +17289,8 @@ snapshots: nanoid@3.3.7: {} + nanoid@5.0.7: {} + natural-compare@1.4.0: {} negotiator@0.6.3: {} @@ -14841,13 +17323,17 @@ snapshots: nocache@3.0.4: {} - nock@13.5.5: + nock@14.0.0-beta.15: dependencies: - debug: 4.3.6 + '@mswjs/interceptors': 0.36.9 + json-stringify-safe: 5.0.1 + propagate: 2.0.1 + + nock@14.0.0-beta.16: + dependencies: + '@mswjs/interceptors': 0.36.9 json-stringify-safe: 5.0.1 propagate: 2.0.1 - transitivePeerDependencies: - - supports-color node-addon-api@3.2.1: {} @@ -14892,6 +17378,8 @@ snapshots: normalize-path@3.0.0: {} + normalize-url@8.0.1: {} + npm-package-arg@7.0.0: dependencies: hosted-git-info: 3.0.8 @@ -14920,6 +17408,8 @@ snapshots: object-assign@4.1.1: {} + object-hash@3.0.0: {} + object-inspect@1.13.2: {} object-keys@1.1.1: {} @@ -14950,6 +17440,26 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.0.0 + oidc-provider@8.5.1: + dependencies: + '@koa/cors': 5.0.0 + '@koa/router': 12.0.1 + debug: 4.3.6 + eta: 3.5.0 + got: 13.0.0 + jose: 5.8.0 + jsesc: 3.0.2 + koa: 2.15.3 + nanoid: 5.0.7 + object-hash: 3.0.0 + oidc-token-hash: 5.0.3 + quick-lru: 7.0.0 + raw-body: 2.5.2 + transitivePeerDependencies: + - supports-color + + oidc-token-hash@5.0.3: {} + on-finished@2.3.0: dependencies: ee-first: 1.1.1 @@ -14972,6 +17482,8 @@ snapshots: dependencies: mimic-fn: 2.1.0 + only@0.0.2: {} + open@6.4.0: dependencies: is-wsl: 1.1.0 @@ -15037,6 +17549,10 @@ snapshots: outdent@0.5.0: {} + outvariant@1.4.3: {} + + p-cancelable@3.0.0: {} + p-filter@2.1.0: dependencies: p-map: 2.1.0 @@ -15129,6 +17645,8 @@ snapshots: path-to-regexp@0.1.7: {} + path-to-regexp@6.2.2: {} + path-type@4.0.0: {} peek-readable@4.1.0: {} @@ -15314,6 +17832,10 @@ snapshots: queue-microtask@1.2.3: {} + quick-lru@5.1.1: {} + + quick-lru@7.0.0: {} + range-parser@1.2.1: {} raw-body@2.5.2: @@ -15352,36 +17874,46 @@ snapshots: react-is@18.3.1: {} - react-native-codegen@0.71.6(@babel/preset-env@7.25.3(@babel/core@7.25.2)): + react-native-codegen@0.71.6(@babel/preset-env@7.25.8(@babel/core@7.25.2)): + dependencies: + '@babel/parser': 7.25.3 + flow-parser: 0.185.2 + jscodeshift: 0.14.0(@babel/preset-env@7.25.8(@babel/core@7.25.2)) + nullthrows: 1.1.1 + transitivePeerDependencies: + - '@babel/preset-env' + - supports-color + + react-native-codegen@0.71.6(@babel/preset-env@7.25.8(@babel/core@7.25.8)): dependencies: '@babel/parser': 7.25.3 flow-parser: 0.185.2 - jscodeshift: 0.14.0(@babel/preset-env@7.25.3(@babel/core@7.25.2)) + jscodeshift: 0.14.0(@babel/preset-env@7.25.8(@babel/core@7.25.8)) nullthrows: 1.1.1 transitivePeerDependencies: - '@babel/preset-env' - supports-color - react-native-fs@2.20.0(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))(react@18.3.1)): + react-native-fs@2.20.0(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(react@18.3.1)): dependencies: base-64: 0.1.0 - react-native: 0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))(react@18.3.1) + react-native: 0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(react@18.3.1) utf8: 3.0.0 - react-native-get-random-values@1.11.0(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))(react@18.3.1)): + react-native-get-random-values@1.11.0(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(react@18.3.1)): dependencies: fast-base64-decode: 1.0.0 - react-native: 0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))(react@18.3.1) + react-native: 0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(react@18.3.1) react-native-gradle-plugin@0.71.19: {} - react-native-securerandom@0.1.1(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))(react@18.3.1)): + react-native-securerandom@0.1.1(react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(react@18.3.1)): dependencies: base64-js: 1.5.1 - react-native: 0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))(react@18.3.1) + react-native: 0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(react@18.3.1) optional: true - react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))(react@18.3.1): + react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(react@18.3.1): dependencies: '@jest/create-cache-key-function': 29.7.0 '@react-native-community/cli': 10.2.7(@babel/core@7.25.2) @@ -15409,7 +17941,53 @@ snapshots: promise: 8.3.0 react: 18.3.1 react-devtools-core: 4.28.5 - react-native-codegen: 0.71.6(@babel/preset-env@7.25.3(@babel/core@7.25.2)) + react-native-codegen: 0.71.6(@babel/preset-env@7.25.8(@babel/core@7.25.2)) + react-native-gradle-plugin: 0.71.19 + react-refresh: 0.4.3 + react-shallow-renderer: 16.15.0(react@18.3.1) + regenerator-runtime: 0.13.11 + scheduler: 0.23.2 + stacktrace-parser: 0.1.10 + use-sync-external-store: 1.2.2(react@18.3.1) + whatwg-fetch: 3.6.20 + ws: 6.2.3 + transitivePeerDependencies: + - '@babel/core' + - '@babel/preset-env' + - bufferutil + - encoding + - supports-color + - utf-8-validate + + react-native@0.71.19(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(react@18.3.1): + dependencies: + '@jest/create-cache-key-function': 29.7.0 + '@react-native-community/cli': 10.2.7(@babel/core@7.25.8) + '@react-native-community/cli-platform-android': 10.2.0 + '@react-native-community/cli-platform-ios': 10.2.5 + '@react-native/assets': 1.0.0 + '@react-native/normalize-color': 2.1.0 + '@react-native/polyfills': 2.0.0 + abort-controller: 3.0.0 + anser: 1.4.10 + ansi-regex: 5.0.1 + base64-js: 1.5.1 + deprecated-react-native-prop-types: 3.0.2 + event-target-shim: 5.0.1 + invariant: 2.2.4 + jest-environment-node: 29.7.0 + jsc-android: 250231.0.0 + memoize-one: 5.2.1 + metro-react-native-babel-transformer: 0.73.10(@babel/core@7.25.8) + metro-runtime: 0.73.10 + metro-source-map: 0.73.10 + mkdirp: 0.5.6 + nullthrows: 1.1.1 + pretty-format: 26.6.2 + promise: 8.3.0 + react: 18.3.1 + react-devtools-core: 4.28.5 + react-native-codegen: 0.71.6(@babel/preset-env@7.25.8(@babel/core@7.25.8)) react-native-gradle-plugin: 0.71.19 react-refresh: 0.4.3 react-shallow-renderer: 16.15.0(react@18.3.1) @@ -15503,6 +18081,10 @@ snapshots: dependencies: regenerate: 1.4.2 + regenerate-unicode-properties@10.2.0: + dependencies: + regenerate: 1.4.2 + regenerate@1.4.2: {} regenerator-runtime@0.13.11: {} @@ -15529,6 +18111,21 @@ snapshots: unicode-match-property-ecmascript: 2.0.0 unicode-match-property-value-ecmascript: 2.1.0 + regexpu-core@6.1.1: + dependencies: + regenerate: 1.4.2 + regenerate-unicode-properties: 10.2.0 + regjsgen: 0.8.0 + regjsparser: 0.11.1 + unicode-match-property-ecmascript: 2.0.0 + unicode-match-property-value-ecmascript: 2.1.0 + + regjsgen@0.8.0: {} + + regjsparser@0.11.1: + dependencies: + jsesc: 3.0.2 + regjsparser@0.9.1: dependencies: jsesc: 0.5.0 @@ -15549,6 +18146,8 @@ snapshots: requires-port@1.0.0: {} + resolve-alpn@1.2.1: {} + resolve-cwd@3.0.0: dependencies: resolve-from: 5.0.0 @@ -15573,6 +18172,10 @@ snapshots: dependencies: path-parse: 1.0.7 + responselike@3.0.0: + dependencies: + lowercase-keys: 3.0.0 + restore-cursor@2.0.0: dependencies: onetime: 2.0.1 @@ -15846,6 +18449,8 @@ snapshots: streamsearch@1.1.0: {} + strict-event-emitter@0.5.1: {} + strict-uri-encode@2.0.0: {} string-length@4.0.2: @@ -15941,6 +18546,27 @@ snapshots: sudo-prompt@9.2.1: {} + superagent@9.0.2: + dependencies: + component-emitter: 1.3.1 + cookiejar: 2.1.4 + debug: 4.3.6 + fast-safe-stringify: 2.1.1 + form-data: 4.0.0 + formidable: 3.5.2 + methods: 1.1.2 + mime: 2.6.0 + qs: 6.13.0 + transitivePeerDependencies: + - supports-color + + supertest@7.0.0: + dependencies: + methods: 1.1.2 + superagent: 9.0.2 + transitivePeerDependencies: + - supports-color + supports-color@5.5.0: dependencies: has-flag: 3.0.0 @@ -16085,12 +18711,12 @@ snapshots: ts-interface-checker@0.1.13: {} - ts-jest@29.2.4(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@18.18.8)(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)))(typescript@5.5.4): + ts-jest@29.2.4(@babel/core@7.25.8)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.8))(jest@29.7.0(@types/node@18.18.8)(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4)))(typescript@5.5.4): dependencies: bs-logger: 0.2.6 ejs: 3.1.10 fast-json-stable-stringify: 2.1.0 - jest: 29.7.0(@types/node@18.18.8)(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)) + jest: 29.7.0(@types/node@18.18.8)(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4)) jest-util: 29.7.0 json5: 2.2.3 lodash.memoize: 4.1.2 @@ -16099,12 +18725,12 @@ snapshots: typescript: 5.5.4 yargs-parser: 21.1.1 optionalDependencies: - '@babel/core': 7.25.2 + '@babel/core': 7.25.8 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.25.2) + babel-jest: 29.7.0(@babel/core@7.25.8) - ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4): + ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 @@ -16121,8 +18747,6 @@ snapshots: typescript: 5.5.4 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 - optionalDependencies: - '@swc/core': 1.7.40 ts-typed-json@0.3.2: optional: true @@ -16140,6 +18764,15 @@ snapshots: tslog@4.9.3: {} + tsscmp@1.0.6: {} + + tsx@4.19.0: + dependencies: + esbuild: 0.23.1 + get-tsconfig: 4.7.6 + optionalDependencies: + fsevents: 2.3.3 + tsyringe@4.8.0: dependencies: tslib: 1.14.1 @@ -16216,7 +18849,7 @@ snapshots: typedarray@0.0.6: {} - typeorm@0.3.20(ts-node@10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4)): + typeorm@0.3.20(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4)): dependencies: '@sqltools/formatter': 1.2.5 app-root-path: 3.1.0 @@ -16234,7 +18867,7 @@ snapshots: uuid: 9.0.1 yargs: 17.7.2 optionalDependencies: - ts-node: 10.9.2(@swc/core@1.7.40)(@types/node@18.18.8)(typescript@5.5.4) + ts-node: 10.9.2(@types/node@18.18.8)(typescript@5.5.4) transitivePeerDependencies: - supports-color @@ -16271,9 +18904,7 @@ snapshots: undici-types@5.26.5: {} - undici@5.28.4: - dependencies: - '@fastify/busboy': 2.1.1 + undici@6.20.1: {} unicode-canonical-property-names-ecmascript@2.0.0: {} @@ -16316,6 +18947,12 @@ snapshots: escalade: 3.1.2 picocolors: 1.0.1 + update-browserslist-db@1.1.0(browserslist@4.24.0): + dependencies: + browserslist: 4.24.0 + escalade: 3.1.2 + picocolors: 1.0.1 + uri-js@4.4.1: dependencies: punycode: 2.3.1 @@ -16600,6 +19237,8 @@ snapshots: y18n: 5.0.8 yargs-parser: 21.1.1 + ylru@1.4.0: {} + yn@3.1.1: {} yocto-queue@0.1.0: {} diff --git a/samples/extension-module/package.json b/samples/extension-module/package.json index 9ef28e5b2e..7edfc4069e 100644 --- a/samples/extension-module/package.json +++ b/samples/extension-module/package.json @@ -13,7 +13,7 @@ "responder": "ts-node responder.ts" }, "devDependencies": { - "ts-node": "^10.4.0", + "ts-node": "^10.9.2", "@types/express": "^4.17.13", "@types/uuid": "^9.0.1", "@types/ws": "^8.5.4" diff --git a/samples/tails/package.json b/samples/tails/package.json index c23828be26..e916f02368 100644 --- a/samples/tails/package.json +++ b/samples/tails/package.json @@ -12,7 +12,7 @@ "start": "ts-node server.ts" }, "devDependencies": { - "ts-node": "^10.4.0" + "ts-node": "^10.9.2" }, "dependencies": { "@credo-ts/anoncreds": "workspace:*", diff --git a/tests/nockToExpress.ts b/tests/nockToExpress.ts new file mode 100644 index 0000000000..154bd9c856 --- /dev/null +++ b/tests/nockToExpress.ts @@ -0,0 +1,78 @@ +import type { Express } from 'express' +import type { ReplyFnContext, Body } from 'nock' + +import nock, { cleanAll } from 'nock' +import request from 'supertest' + +// Helper function to forward requests from nock to express +export function setupNockToExpress(baseUrl: string, app: Express) { + async function reply(this: ReplyFnContext, uri: string, body: Body) { + // Get the original request details + const { method, path, headers } = this.req + + // Forward the request to our Express app using supertest + const supertestInstance = request(app) + + let testRequest = supertestInstance[method.toLowerCase() as 'post'](path) + + // Add original headers (excluding some that might interfere) + Object.entries(headers).forEach(([key, value]) => { + if (!['host', 'content-length'].includes(key.toLowerCase())) { + testRequest = testRequest.set(key, value) + } + }) + + // Add marker header to prevent infinite loops + testRequest = testRequest.set('x-forwarded-from-nock', 'true') + + // Add body for POST/PUT/PATCH requests + if (['POST', 'PUT', 'PATCH'].includes(method) && body) { + testRequest = testRequest.send(body) + } + + // Disable automatic JSON parsing, there's something weird if a string is returned + testRequest = testRequest.buffer(true).parse((res, cb) => { + let data = Buffer.from('') + res.on('data', (chunk) => { + data = Buffer.concat([data, chunk]) + }) + res.on('end', function () { + cb(null, data.toString()) + }) + }) + + try { + const response = await testRequest + return [response.status, response.body, response.headers] + } catch (error) { + // eslint-disable-next-line no-console + console.error('Error forwarding request:', error) + return [500, { error: 'Internal Server Error' }] + } + } + + // Intercept all HTTP methods + nock(baseUrl) + .persist() + .post(() => true) + .reply(reply) + .get(() => true) + .reply(reply) + .put(() => true) + .reply(reply) + .delete(() => true) + .reply(reply) + .options(() => true) + .reply(reply) + .patch(() => true) + .reply(reply) + + jest.mock('cross-fetch', () => ({ + fetch, + })) + + return () => { + cleanAll() + jest.clearAllMocks() + } +} diff --git a/tests/runInVersion.ts b/tests/runInVersion.ts deleted file mode 100644 index 743e8de158..0000000000 --- a/tests/runInVersion.ts +++ /dev/null @@ -1,12 +0,0 @@ -type NodeVersions = 18 | 20 - -export function describeRunInNodeVersion(versions: NodeVersions[], ...parameters: Parameters) { - const runtimeVersion = process.version - const mappedVersions = versions.map((version) => `v${version}.`) - - if (mappedVersions.some((version) => runtimeVersion.startsWith(version))) { - describe(...parameters) - } else { - describe.skip(...parameters) - } -}