From 5cd08fedcb4debabd42d44ebd695e2a76197fb67 Mon Sep 17 00:00:00 2001 From: Martin Auer Date: Fri, 30 Aug 2024 11:04:20 +0200 Subject: [PATCH 1/9] fix: auth code flow alpha --- .../OpenId4VciHolderService.ts | 40 ++- .../OpenId4VcIssuerService.ts | 111 ++++++- .../OpenId4VcIssuerServiceOptions.ts | 23 +- .../repository/OpenId4VcCNonceStateManager.ts | 19 +- ...Id4VcCredentialOfferSessionStateManager.ts | 89 +++--- .../OpenId4VcIssuanceSessionRecord.ts | 9 + .../repository/OpenId4VcIssuerRecord.ts | 9 +- .../router/accessTokenEndpoint.ts | 271 +++++++++++++++--- .../router/credentialEndpoint.ts | 30 +- .../router/metadataEndpoint.ts | 4 +- .../router/verifyAccessToken.ts | 25 +- .../src/shared/models/AuthorizationServer.ts | 4 + packages/openid4vc/src/shared/models/index.ts | 6 +- 13 files changed, 513 insertions(+), 127 deletions(-) create mode 100644 packages/openid4vc/src/shared/models/AuthorizationServer.ts diff --git a/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderService.ts b/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderService.ts index 6c25997df5..dfd03d93b5 100644 --- a/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderService.ts +++ b/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderService.ts @@ -165,9 +165,11 @@ export class OpenId4VciHolderService { authCodeFlowOptions: OpenId4VciAuthCodeFlowOptions ): Promise { const { credentialOfferPayload, metadata, offeredCredentials } = resolvedCredentialOffer + const codeVerifier = ( - await Promise.allSettled([agentContext.wallet.generateNonce(), agentContext.wallet.generateNonce()]) - ).join() + await Promise.all([agentContext.wallet.generateNonce(), agentContext.wallet.generateNonce()]) + ).join('') + const codeVerifierSha256 = Hasher.hash(codeVerifier, 'sha-256') const codeChallenge = TypedArrayEncoder.toBase64URL(codeVerifierSha256) @@ -178,13 +180,13 @@ export class OpenId4VciHolderService { }) const authDetailsLocation = metadata.credentialIssuerMetadata.authorization_server - ? metadata.credentialIssuerMetadata.authorization_server - : undefined + const authDetails = offeredCredentials .map((credential) => this.getAuthDetailsFromOfferedCredential(credential, authDetailsLocation)) .filter((authDetail): authDetail is AuthorizationDetails => authDetail !== undefined) const { clientId, redirectUri, scope } = authCodeFlowOptions + const authorizationRequestUri = await createAuthorizationRequestUri({ clientId, codeChallenge, @@ -256,6 +258,7 @@ export class OpenId4VciHolderService { code, codeVerifier, redirectUri, + asOpts: { clientId: resolvedAuthorizationRequestWithCode.clientId }, }) } else { accessTokenResponse = await accessTokenClient.acquireAccessToken({ @@ -642,8 +645,21 @@ async function createAuthorizationRequestUri(options: { authDetails?: AuthorizationDetails | AuthorizationDetails[] redirectUri: string scope?: string[] + userHint?: string + walletIssuer?: string }) { - const { scope, authDetails, metadata, clientId, codeChallenge, codeChallengeMethod, redirectUri } = options + const { + scope, + authDetails, + metadata, + clientId, + codeChallenge, + codeChallengeMethod, + redirectUri, + userHint, + walletIssuer, + } = options + let nonEmptyScope = !scope || scope.length === 0 ? undefined : scope const nonEmptyAuthDetails = !authDetails || authDetails.length === 0 ? undefined : authDetails @@ -655,9 +671,13 @@ async function createAuthorizationRequestUri(options: { // Authorization servers supporting PAR SHOULD include the URL of their pushed authorization request endpoint in their authorization server metadata document // Note that the presence of pushed_authorization_request_endpoint is sufficient for a client to determine that it may use the PAR flow. - const parEndpoint = metadata.credentialIssuerMetadata.pushed_authorization_request_endpoint + const parEndpoint = + metadata.credentialIssuerMetadata.pushed_authorization_request_endpoint ?? + metadata.authorizationServerMetadata?.pushed_authorization_request_endpoint - const authorizationEndpoint = metadata.credentialIssuerMetadata?.authorization_endpoint + const authorizationEndpoint = + metadata.credentialIssuerMetadata?.authorization_endpoint ?? + metadata.authorizationServerMetadata?.authorization_endpoint if (!authorizationEndpoint && !parEndpoint) { throw new CredoError( @@ -680,8 +700,12 @@ async function createAuthorizationRequestUri(options: { if (nonEmptyScope) queryObj['scope'] = nonEmptyScope.join(' ') - if (nonEmptyAuthDetails) + if (nonEmptyAuthDetails) { queryObj['authorization_details'] = JSON.stringify(handleAuthorizationDetails(nonEmptyAuthDetails, metadata)) + } + + if (userHint) queryObj['user_hint'] = userHint + if (walletIssuer) queryObj['wallet_issuer'] = walletIssuer const issuerState = options.credentialOffer.grants?.authorization_code?.issuer_state if (issuerState) queryObj['issuer_state'] = issuerState diff --git a/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerService.ts b/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerService.ts index e70a8ad324..c7e40c5ff4 100644 --- a/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerService.ts +++ b/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerService.ts @@ -6,6 +6,7 @@ import type { OpenId4VcIssuerMetadata, OpenId4VciSignSdJwtCredential, OpenId4VciSignW3cCredential, + OpenId4VciAuthorizationCodeFlowConfig, } from './OpenId4VcIssuerServiceOptions' import type { OpenId4VcIssuanceSessionRecord } from './repository' import type { @@ -94,7 +95,11 @@ export class OpenId4VcIssuerService { agentContext: AgentContext, options: OpenId4VciCreateCredentialOfferOptions & { issuer: OpenId4VcIssuerRecord } ) { - const { preAuthorizedCodeFlowConfig, issuer, offeredCredentials } = options + const { preAuthorizedCodeFlowConfig, authorizationCodeFlowConfig, issuer, offeredCredentials } = options + + if (!preAuthorizedCodeFlowConfig && !authorizationCodeFlowConfig) { + throw new CredoError('Authorization Config or Pre-Authorized Config must be provided.') + } const vcIssuer = this.getVcIssuer(agentContext, issuer) @@ -114,8 +119,37 @@ export class OpenId4VcIssuerService { utils.uuid(), ]) + // TODO: HAIP + // TODO: for grant type authorization_code, the issuer must include a scope value in order to allow the wallet to identify the desired credential type. + // TODO: The wallet MUST use that value in the scope Authorization parameter. + // TODO: add support for scope in the credential offer in sphereon-oid4vci + const issuerMetadata = this.getIssuerMetadata(agentContext, issuer) + + if ( + authorizationCodeFlowConfig && + (issuerMetadata.authorizationServers?.length ?? 0 > 1) && + authorizationCodeFlowConfig?.authorizationServerUrl + ) { + throw new CredoError( + 'The authorization code flow requires an explicit authorization server url, if multiple authorization servers are present.' + ) + } + let { uri } = await vcIssuer.createCredentialOfferURI({ - grants: await this.getGrantsFromConfig(agentContext, preAuthorizedCodeFlowConfig), + grants: await this.getGrantsFromConfig(agentContext, { + preAuthorizedCodeFlowConfig, + authorizationCodeFlowConfig: authorizationCodeFlowConfig + ? { + ...authorizationCodeFlowConfig, + + // Must only be used if multiple authorization servers are present + authorizationServerUrl: + issuerMetadata.authorizationServers?.length ?? 0 > 1 + ? authorizationCodeFlowConfig?.authorizationServerUrl + : undefined, + } + : undefined, + }), credentials: offeredCredentials, credentialOfferUri: hostedCredentialOfferUri, baseUri: options.baseUri, @@ -239,17 +273,35 @@ export class OpenId4VcIssuerService { } public async createIssuer(agentContext: AgentContext, options: OpenId4VciCreateIssuerOptions) { + const { authorizationServerConfigs: authorizationServers, issuerId, display } = options // TODO: ideally we can store additional data with a key, such as: // - createdAt // - purpose const accessTokenSignerKey = await agentContext.wallet.createKey({ keyType: KeyType.Ed25519, }) + + // If we have an authorization server, we also want to publish a scope to request each credential + // this is required for HAIP + const credentialsSupported = options.credentialsSupported.map((credentialSupported) => { + return { + ...credentialSupported, + // TODO: do we also need to provide some way to let the wallet know which authorization server + // TODO: can issue which credentials? + scope: credentialSupported.scope + ? credentialSupported.scope + : authorizationServers?.length ?? 0 > 0 + ? credentialSupported.id + : undefined, + } + }) + const openId4VcIssuer = new OpenId4VcIssuerRecord({ - issuerId: options.issuerId ?? utils.uuid(), - display: options.display, + issuerId: issuerId ?? utils.uuid(), + display: display, accessTokenPublicKeyFingerprint: accessTokenSignerKey.fingerprint, - credentialsSupported: options.credentialsSupported, + credentialsSupported, + authorizationServerConfigs: authorizationServers, }) await this.openId4VcIssuerRepository.save(agentContext, openId4VcIssuer) @@ -271,12 +323,22 @@ export class OpenId4VcIssuerService { const config = agentContext.dependencyManager.resolve(OpenId4VcIssuerModuleConfig) const issuerUrl = joinUriParts(config.baseUrl, [issuerRecord.issuerId]) + const authorizationServers = + issuerRecord.authorizationServerConfigs && issuerRecord.authorizationServerConfigs.length > 0 + ? issuerRecord.authorizationServerConfigs.map((authorizationServer) => authorizationServer.baseUrl) + : undefined + + const tokenEndpoint = authorizationServers + ? undefined + : joinUriParts(issuerUrl, [config.accessTokenEndpoint.endpointPath]) + const issuerMetadata = { issuerUrl, - tokenEndpoint: joinUriParts(issuerUrl, [config.accessTokenEndpoint.endpointPath]), + tokenEndpoint, credentialEndpoint: joinUriParts(issuerUrl, [config.credentialEndpoint.endpointPath]), credentialsSupported: issuerRecord.credentialsSupported, issuerDisplay: issuerRecord.display, + authorizationServers, } satisfies OpenId4VcIssuerMetadata return issuerMetadata @@ -323,7 +385,6 @@ export class OpenId4VcIssuerService { const builder = new VcIssuerBuilder() .withCredentialIssuer(issuerMetadata.issuerUrl) .withCredentialEndpoint(issuerMetadata.credentialEndpoint) - .withTokenEndpoint(issuerMetadata.tokenEndpoint) .withCredentialsSupported(issuerMetadata.credentialsSupported) .withCNonceStateManager(new OpenId4VcCNonceStateManager(agentContext, issuer.issuerId)) .withCredentialOfferStateManager(new OpenId4VcCredentialOfferSessionStateManager(agentContext, issuer.issuerId)) @@ -333,8 +394,14 @@ export class OpenId4VcIssuerService { throw new CredoError('Credential signer callback should be overwritten. This is a no-op') }) - if (issuerMetadata.authorizationServer) { - builder.withAuthorizationServer(issuerMetadata.authorizationServer) + if (issuerMetadata.authorizationServers) { + // TODO: add support for multiple authorization servers + builder.withAuthorizationServer(issuerMetadata.authorizationServers[0]) + } else { + if (!issuerMetadata.tokenEndpoint) { + throw new CredoError('Missing required token endpoint. No authorization server is set.') + } + builder.withTokenEndpoint(issuerMetadata.tokenEndpoint) } if (issuerMetadata.issuerDisplay) { @@ -346,14 +413,28 @@ export class OpenId4VcIssuerService { private async getGrantsFromConfig( agentContext: AgentContext, - preAuthorizedCodeFlowConfig: OpenId4VciPreAuthorizedCodeFlowConfig + config: { + preAuthorizedCodeFlowConfig?: OpenId4VciPreAuthorizedCodeFlowConfig + authorizationCodeFlowConfig?: OpenId4VciAuthorizationCodeFlowConfig + } ) { + const { preAuthorizedCodeFlowConfig, authorizationCodeFlowConfig } = config + const grants: Grant = { - 'urn:ietf:params:oauth:grant-type:pre-authorized_code': { - 'pre-authorized_code': - preAuthorizedCodeFlowConfig.preAuthorizedCode ?? (await agentContext.wallet.generateNonce()), - user_pin_required: preAuthorizedCodeFlowConfig.userPinRequired ?? false, - }, + ...(preAuthorizedCodeFlowConfig && { + 'urn:ietf:params:oauth:grant-type:pre-authorized_code': { + 'pre-authorized_code': + preAuthorizedCodeFlowConfig.preAuthorizedCode ?? (await agentContext.wallet.generateNonce()), + user_pin_required: preAuthorizedCodeFlowConfig.userPinRequired ?? false, + }, + }), + + ...(authorizationCodeFlowConfig && { + authorization_code: { + issuer_state: authorizationCodeFlowConfig.issuerState, + authorization_server: authorizationCodeFlowConfig.authorizationServerUrl, + }, + }), } return grants diff --git a/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerServiceOptions.ts b/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerServiceOptions.ts index 5775d01f63..c9341be76b 100644 --- a/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerServiceOptions.ts +++ b/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerServiceOptions.ts @@ -7,6 +7,7 @@ import type { OpenId4VciCredentialSupportedWithId, OpenId4VciIssuerMetadataDisplay, } from '../shared' +import type { OpenId4VciAuthorizationServerConfig } from '../shared/models/AuthorizationServer' import type { AgentContext, ClaimFormat, W3cCredential, SdJwtVcSignOptions } from '@credo-ts/core' export interface OpenId4VciPreAuthorizedCodeFlowConfig { @@ -14,15 +15,26 @@ export interface OpenId4VciPreAuthorizedCodeFlowConfig { userPinRequired?: boolean } +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. + 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 +} + export type OpenId4VcIssuerMetadata = { // The Credential Issuer's identifier. (URL using the https scheme) issuerUrl: string credentialEndpoint: string - tokenEndpoint: string - authorizationServer?: string + tokenEndpoint?: string + authorizationServers?: string[] issuerDisplay?: OpenId4VciIssuerMetadataDisplay[] - credentialsSupported: OpenId4VciCredentialSupported[] + credentialsSupported: OpenId4VciCredentialSupportedWithId[] } export interface OpenId4VciCreateCredentialOfferOptions { @@ -37,7 +49,9 @@ export interface OpenId4VciCreateCredentialOfferOptions { */ baseUri?: string - preAuthorizedCodeFlowConfig: OpenId4VciPreAuthorizedCodeFlowConfig + preAuthorizedCodeFlowConfig?: OpenId4VciPreAuthorizedCodeFlowConfig + + authorizationCodeFlowConfig?: OpenId4VciAuthorizationCodeFlowConfig /** * Metadata about the issuance, that will be stored in the issuance session record and @@ -123,4 +137,5 @@ export interface OpenId4VciCreateIssuerOptions { credentialsSupported: OpenId4VciCredentialSupportedWithId[] display?: OpenId4VciIssuerMetadataDisplay[] + authorizationServerConfigs?: OpenId4VciAuthorizationServerConfig[] } diff --git a/packages/openid4vc/src/openid4vc-issuer/repository/OpenId4VcCNonceStateManager.ts b/packages/openid4vc/src/openid4vc-issuer/repository/OpenId4VcCNonceStateManager.ts index 7473719435..c0cadd0a9c 100644 --- a/packages/openid4vc/src/openid4vc-issuer/repository/OpenId4VcCNonceStateManager.ts +++ b/packages/openid4vc/src/openid4vc-issuer/repository/OpenId4VcCNonceStateManager.ts @@ -1,4 +1,6 @@ -import type { AgentContext } from '@credo-ts/core' +import type { OpenId4VcIssuanceCodeType } from './OpenId4VcCredentialOfferSessionStateManager' +import type { OpenId4VcIssuanceSessionRecord } from './OpenId4VcIssuanceSessionRecord' +import type { AgentContext, Query } from '@credo-ts/core' import type { CNonceState, IStateManager } from '@sphereon/oid4vci-common' import { CredoError } from '@credo-ts/core' @@ -16,21 +18,26 @@ export class OpenId4VcCNonceStateManager implements IStateManager { this.openId4VcIssuerModuleConfig = agentContext.dependencyManager.resolve(OpenId4VcIssuerModuleConfig) } - public async set(cNonce: string, stateValue: CNonceState): Promise { + public async set(cNonce: string, stateValue: CNonceState, type?: OpenId4VcIssuanceCodeType): 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") + if (!stateValue.preAuthorizedCode && !stateValue.issuerState) { + throw new CredoError("Expected the stateValue to have a 'preAuthorizedCode' or 'issuerState' property") } // Record MUST exist (otherwise there's no issuance session active yet) + + const $or: Query[] = [] + + if (!type || type === 'preAuthorized') $or.push({ preAuthorizedCode: stateValue.preAuthorizedCode }) + if (!type || type === 'issuerState') $or.push({ issuerState: stateValue.issuerState }) + 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, + $or, }) // cNonce already matches, no need to update diff --git a/packages/openid4vc/src/openid4vc-issuer/repository/OpenId4VcCredentialOfferSessionStateManager.ts b/packages/openid4vc/src/openid4vc-issuer/repository/OpenId4VcCredentialOfferSessionStateManager.ts index 83cd20d27e..f2463ba68b 100644 --- a/packages/openid4vc/src/openid4vc-issuer/repository/OpenId4VcCredentialOfferSessionStateManager.ts +++ b/packages/openid4vc/src/openid4vc-issuer/repository/OpenId4VcCredentialOfferSessionStateManager.ts @@ -1,5 +1,5 @@ import type { OpenId4VcIssuanceSessionStateChangedEvent } from '../OpenId4VcIssuerEvents' -import type { AgentContext } from '@credo-ts/core' +import type { AgentContext, Query } from '@credo-ts/core' import type { CredentialOfferSession, IStateManager } from '@sphereon/oid4vci-common' import { CredoError, EventEmitter } from '@credo-ts/core' @@ -11,6 +11,21 @@ import { OpenId4VcIssuerEvents } from '../OpenId4VcIssuerEvents' import { OpenId4VcIssuanceSessionRecord } from './OpenId4VcIssuanceSessionRecord' import { OpenId4VcIssuanceSessionRepository } from './OpenId4VcIssuanceSessionRepository' +export type OpenId4VcIssuanceCodeType = 'preAuthorized' | 'issuerState' + +const createCodeQuery = ( + issuerId: string, + code: string, + type?: OpenId4VcIssuanceCodeType +): Query => { + const $or: Query[] = [] + + if (!type || type === 'preAuthorized') $or.push({ preAuthorizedCode: code }) + if (!type || type === 'issuerState') $or.push({ issuerState: code }) + + return { issuerId, $or } +} + export class OpenId4VcCredentialOfferSessionStateManager implements IStateManager { private openId4VcIssuanceSessionRepository: OpenId4VcIssuanceSessionRepository private eventEmitter: EventEmitter @@ -20,22 +35,31 @@ export class OpenId4VcCredentialOfferSessionStateManager implements IStateManage this.eventEmitter = agentContext.dependencyManager.resolve(EventEmitter) } - public async set(preAuthorizedCode: string, stateValue: CredentialOfferSession): Promise { + public async set(code: string, stateValue: CredentialOfferSession, type?: OpenId4VcIssuanceCodeType): 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 ( + (type === 'preAuthorized' && code !== stateValue.preAuthorizedCode) || + (type === 'issuerState' && code !== stateValue.issuerState) + ) { + throw new CredoError(`Expected the id of the credential offer state to be equal to the '${type}'`) } - if (!stateValue.preAuthorizedCode) { - throw new CredoError("Expected the stateValue to have a 'preAuthorizedCode' property") + if (code !== stateValue.issuerState && code !== stateValue.preAuthorizedCode) { + throw new CredoError( + `Expected the id of the credential offer state to be equal to the 'preAuthorizedCode' or 'issuerState'` + ) + } + + if (!stateValue.issuerState && !stateValue.preAuthorizedCode) { + throw new CredoError("Expected the stateValue to have a 'preAuthorizedCode' or 'issuerState' property") } // Record may already exist - let record = await this.openId4VcIssuanceSessionRepository.findSingleByQuery(this.agentContext, { - issuerId: this.issuerId, - preAuthorizedCode: stateValue.preAuthorizedCode, - }) + let record = await this.openId4VcIssuanceSessionRepository.findSingleByQuery( + this.agentContext, + createCodeQuery(this.issuerId, code, type) + ) const previousState = record?.state ?? null @@ -67,6 +91,7 @@ export class OpenId4VcCredentialOfferSessionStateManager implements IStateManage record.credentialOfferPayload = stateValue.credentialOffer.credential_offer record.userPin = stateValue.userPin record.preAuthorizedCode = stateValue.preAuthorizedCode + record.issuerState = stateValue.issuerState record.errorMessage = stateValue.error record.credentialOfferUri = credentialOfferUri record.state = state @@ -75,6 +100,7 @@ export class OpenId4VcCredentialOfferSessionStateManager implements IStateManage record = new OpenId4VcIssuanceSessionRecord({ issuerId: this.issuerId, preAuthorizedCode: stateValue.preAuthorizedCode, + issuerState: stateValue.issuerState, issuanceMetadata: stateValue.credentialDataSupplierInput, credentialOfferPayload: stateValue.credentialOffer.credential_offer, credentialOfferUri, @@ -89,18 +115,16 @@ export class OpenId4VcCredentialOfferSessionStateManager implements IStateManage this.emitStateChangedEvent(this.agentContext, record, previousState) } - public async get(preAuthorizedCode: string): Promise { - const record = await this.openId4VcIssuanceSessionRepository.findSingleByQuery(this.agentContext, { - issuerId: this.issuerId, - preAuthorizedCode, - }) + public async get(code: string, type?: OpenId4VcIssuanceCodeType): Promise { + const record = await this.openId4VcIssuanceSessionRepository.findSingleByQuery( + this.agentContext, + createCodeQuery(this.issuerId, code, type) + ) 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.preAuthorizedCode && !record.issuerState) { + throw new CredoError("No 'preAuthorizedCode' and 'issuerState' found on record.") } if (!record.credentialOfferPayload) { @@ -114,6 +138,7 @@ export class OpenId4VcCredentialOfferSessionStateManager implements IStateManage }, status: sphereonIssueStatusFromOpenId4VcIssuanceState(record.state), preAuthorizedCode: record.preAuthorizedCode, + issuerState: record.issuerState, credentialDataSupplierInput: record.issuanceMetadata, error: record.errorMessage, userPin: record.userPin, @@ -122,20 +147,20 @@ export class OpenId4VcCredentialOfferSessionStateManager implements IStateManage } } - public async has(preAuthorizedCode: string): Promise { - const record = await this.openId4VcIssuanceSessionRepository.findSingleByQuery(this.agentContext, { - issuerId: this.issuerId, - preAuthorizedCode, - }) + public async has(code: string, type?: OpenId4VcIssuanceCodeType): Promise { + const record = await this.openId4VcIssuanceSessionRepository.findSingleByQuery( + this.agentContext, + createCodeQuery(this.issuerId, code, type) + ) return record !== undefined } - public async delete(preAuthorizedCode: string): Promise { - const record = await this.openId4VcIssuanceSessionRepository.findSingleByQuery(this.agentContext, { - issuerId: this.issuerId, - preAuthorizedCode, - }) + public async delete(code: string, type?: OpenId4VcIssuanceCodeType): Promise { + const record = await this.openId4VcIssuanceSessionRepository.findSingleByQuery( + this.agentContext, + createCodeQuery(this.issuerId, code, type) + ) if (!record) return false @@ -153,11 +178,11 @@ export class OpenId4VcCredentialOfferSessionStateManager implements IStateManage throw new Error('Method not implemented.') } - public async getAsserted(preAuthorizedCode: string): Promise { - const state = await this.get(preAuthorizedCode) + public async getAsserted(code: string, type?: OpenId4VcIssuanceCodeType): Promise { + const state = await this.get(code, type) if (!state) { - throw new CredoError(`No credential offer state found for id ${preAuthorizedCode}`) + throw new CredoError(`No credential offer state found for id '${code}'`) } return state diff --git a/packages/openid4vc/src/openid4vc-issuer/repository/OpenId4VcIssuanceSessionRecord.ts b/packages/openid4vc/src/openid4vc-issuer/repository/OpenId4VcIssuanceSessionRecord.ts index 3de3af4313..f2e76255b3 100644 --- a/packages/openid4vc/src/openid4vc-issuer/repository/OpenId4VcIssuanceSessionRecord.ts +++ b/packages/openid4vc/src/openid4vc-issuer/repository/OpenId4VcIssuanceSessionRecord.ts @@ -27,6 +27,7 @@ export interface OpenId4VcIssuanceSessionRecordProps { cNonceExpiresAt?: Date preAuthorizedCode?: string + issuerState?: string userPin?: string credentialOfferUri: string @@ -81,6 +82,12 @@ export class OpenId4VcIssuanceSessionRecord extends BaseRecord { + if (assertedState.credentialOffer?.credential_offer?.grants) { + const validPreAuthorizedGrant = + Object.keys(assertedState.credentialOffer.credential_offer.grants).includes(GrantTypes.PRE_AUTHORIZED_CODE) && + grantType === GrantTypes.PRE_AUTHORIZED_CODE + + const validAuthorizationCodeGrant = + Object.keys(assertedState.credentialOffer.credential_offer.grants).includes(GrantTypes.AUTHORIZATION_CODE) && + grantType === GrantTypes.AUTHORIZATION_CODE + return validAuthorizationCodeGrant || validPreAuthorizedGrant + } + return false +} + +// TODO: Update in Sphereon OID4VCI +export const assertValidAccessTokenRequest = async ( + request: AccessTokenRequest, + opts: { + credentialOfferSessions: IStateManager + expirationDuration: number + } +) => { + const { credentialOfferSessions, expirationDuration } = opts + // Only pre-auth supported for now + if (request.grant_type !== GrantTypes.PRE_AUTHORIZED_CODE && request.grant_type !== GrantTypes.AUTHORIZATION_CODE) { + throw new TokenError(400, TokenErrorResponse.invalid_grant, UNSUPPORTED_GRANT_TYPE_ERROR) + } + + // Pre-auth flow + const preAuthorizedCode = + request.grant_type === GrantTypes.PRE_AUTHORIZED_CODE ? request[PRE_AUTH_CODE_LITERAL] : undefined + const issuerStatus = request.grant_type === GrantTypes.AUTHORIZATION_CODE ? request.issuer_state : undefined + + if (!preAuthorizedCode && !issuerStatus) { + throw new TokenError( + 400, + TokenErrorResponse.invalid_request, + "Either 'pre-authorized_code' or 'authorization_code' is required" + ) + } + + const code = (preAuthorizedCode ?? issuerStatus) as string + + const credentialOfferSession = await credentialOfferSessions.getAsserted(code) + + if (![IssueStatus.OFFER_CREATED, IssueStatus.OFFER_URI_RETRIEVED].includes(credentialOfferSession.status)) { + throw new TokenError(400, TokenErrorResponse.invalid_request, 'Access token has already been retrieved') + } + + credentialOfferSession.status = IssueStatus.ACCESS_TOKEN_REQUESTED + credentialOfferSession.lastUpdatedAt = +new Date() + await credentialOfferSessions.set(code, credentialOfferSession) + + if (!isValidGrant(credentialOfferSession, request.grant_type)) { + throw new TokenError(400, TokenErrorResponse.invalid_grant, UNSUPPORTED_GRANT_TYPE_ERROR) + } + + if (preAuthorizedCode) { + /* + invalid_request: + the Authorization Server expects a PIN in the pre-authorized flow but the client does not provide a PIN + */ + if ( + credentialOfferSession.credentialOffer.credential_offer.grants?.[GrantTypes.PRE_AUTHORIZED_CODE] + ?.user_pin_required && + !request.user_pin + ) { + throw new TokenError(400, TokenErrorResponse.invalid_request, USER_PIN_REQUIRED_ERROR) + } + + /* + invalid_request: + the Authorization Server does not expect a PIN in the pre-authorized flow but the client provides a PIN + */ + if ( + !credentialOfferSession.credentialOffer.credential_offer.grants?.[GrantTypes.PRE_AUTHORIZED_CODE] + ?.user_pin_required && + request.user_pin + ) { + throw new TokenError(400, TokenErrorResponse.invalid_request, USER_PIN_NOT_REQUIRED_ERROR) + } + + /* + invalid_grant: + the Authorization Server expects a PIN in the pre-authorized flow but the client provides the wrong PIN + the End-User provides the wrong Pre-Authorized Code or the Pre-Authorized Code has expired + */ + if (request.user_pin && !/[0-9{,8}]/.test(request.user_pin)) { + throw new TokenError(400, TokenErrorResponse.invalid_grant, PIN_VALIDATION_ERROR) + } else if (request.user_pin !== credentialOfferSession.userPin) { + throw new TokenError(400, TokenErrorResponse.invalid_grant, PIN_NOT_MATCH_ERROR) + } else if (isPreAuthorizedCodeExpired(credentialOfferSession, expirationDuration)) { + throw new TokenError(400, TokenErrorResponse.invalid_grant, EXPIRED_PRE_AUTHORIZED_CODE) + } else if ( + request[PRE_AUTH_CODE_LITERAL] !== + credentialOfferSession.credentialOffer.credential_offer.grants?.[GrantTypes.PRE_AUTHORIZED_CODE]?.[ + PRE_AUTH_CODE_LITERAL + ] + ) { + throw new TokenError(400, TokenErrorResponse.invalid_grant, INVALID_PRE_AUTHORIZED_CODE) + } + return { preAuthSession: credentialOfferSession } + } + + // Authorization code flow + + const authorizationCodeGrant = credentialOfferSession.credentialOffer.credential_offer.grants?.authorization_code + + if (authorizationCodeGrant?.issuer_state !== credentialOfferSession.issuerState) { + throw new TokenError( + 400, + TokenErrorResponse.invalid_request, + 'Issuer state does not match credential offer issuance state' + ) + } + + // TODO: rename to isCodeExpired + if (isPreAuthorizedCodeExpired(credentialOfferSession, expirationDuration)) { + throw new TokenError(400, TokenErrorResponse.invalid_grant, 'Issuer state is expired') + } + + if (!authorizationCodeGrant?.issuer_state || request.issuer_state !== authorizationCodeGrant.issuer_state) { + throw new TokenError(400, TokenErrorResponse.invalid_grant, 'Issuer state is invalid') + } + return { preAuthSession: credentialOfferSession } +} + +// TODO: Update in Sphereon OID4VCI +export interface AccessTokenRequest { + client_id?: string + code?: string + code_verifier?: string + grant_type: GrantTypes + 'pre-authorized_code'?: string + issuer_state?: string + redirect_uri?: string + scope?: string + user_pin?: string +} + +/** + * TODO: create pr to support issuerState in `createAccessTokenResponse` + * Copy from '@sphereon/oid4vci-issuer' + * @param opts + * @returns + */ +export const generateAccessToken = async ( + opts: Required> & { + preAuthorizedCode?: string + issuerState?: string + alg?: Alg + } +): Promise => { + const { accessTokenIssuer, alg, accessTokenSignerCallback, tokenExpiresIn, preAuthorizedCode, issuerState } = opts + // JWT uses seconds for iat and exp + const iat = new Date().getTime() / 1000 + const exp = iat + tokenExpiresIn + const jwt: Jwt = { + header: { typ: 'JWT', alg: alg ?? Alg.ES256K }, + payload: { + iat, + exp, + iss: accessTokenIssuer, + ...(preAuthorizedCode && { preAuthorizedCode }), + ...(issuerState && { issuerState }), + }, + } + return await accessTokenSignerCallback(jwt) +} + export interface OpenId4VciAccessTokenEndpointConfig { /** * The path at which the token endpoint should be made available. Note that it will be @@ -53,7 +238,7 @@ export interface OpenId4VciAccessTokenEndpointConfig { export function configureAccessTokenEndpoint(router: Router, config: OpenId4VciAccessTokenEndpointConfig) { router.post( config.endpointPath, - verifyTokenRequest({ preAuthorizedCodeExpirationInSeconds: config.preAuthorizedCodeExpirationInSeconds }), + verifyTokenRequest({ codeExpirationInSeconds: config.preAuthorizedCodeExpirationInSeconds }), handleTokenRequest(config) ) } @@ -106,30 +291,52 @@ export function handleTokenRequest(config: OpenId4VciAccessTokenEndpointConfig) 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 accessTokenSigningKey = Key.fromFingerprint(issuer.accessTokenPublicKeyFingerprint) try { - const accessTokenResponse = await createAccessTokenResponse(request.body, { - credentialOfferSessions: new OpenId4VcCredentialOfferSessionStateManager(agentContext, issuer.issuerId), - tokenExpiresIn: tokenExpiresInSeconds, + const code = body.issuer_state + ? { type: 'issuerState' as const, value: body.issuer_state } + : { type: 'preAuthorized' as const, value: body[PRE_AUTH_CODE_LITERAL] as string } + + const cNonceStateManager = new OpenId4VcCNonceStateManager(agentContext, issuer.issuerId) + const cNonce = await agentContext.wallet.generateNonce() + await cNonceStateManager.set( + cNonce, + { + cNonce, + createdAt: +new Date(), + ...(code.type === 'issuerState' && { issuerState: code.value }), + ...(code.type === 'preAuthorized' && { preAuthorizedCode: code.value }), + }, + code.type + ) + + const access_token = await generateAccessToken({ + tokenExpiresIn: 3600, accessTokenIssuer: issuerMetadata.issuerUrl, - cNonce: await agentContext.wallet.generateNonce(), - cNonceExpiresIn: cNonceExpiresInSeconds, - cNonces: new OpenId4VcCNonceStateManager(agentContext, issuer.issuerId), accessTokenSignerCallback: getJwtSignerCallback(agentContext, accessTokenSigningKey, config), + ...(code.type === 'issuerState' && { issuerState: code.value }), + ...(code.type === 'preAuthorized' && { preAuthorizedCode: code.value }), }) + + const accessTokenResponse: AccessTokenResponse = { + access_token, + expires_in: tokenExpiresInSeconds, + c_nonce: cNonce, + c_nonce_expires_in: cNonceExpiresInSeconds, + authorization_pending: false, + interval: undefined, + } + + const credentialOfferStateManager = new OpenId4VcCredentialOfferSessionStateManager(agentContext, issuer.issuerId) + const credentialOfferSession = await credentialOfferStateManager.getAsserted(code.value, code.type) + credentialOfferSession.status = IssueStatus.ACCESS_TOKEN_CREATED + credentialOfferSession.lastUpdatedAt = +new Date() + await credentialOfferStateManager.set(code.value, credentialOfferSession, code.type) + response.status(200).json(accessTokenResponse) } catch (error) { sendErrorResponse(response, agentContext.config.logger, 400, TokenErrorResponse.invalid_request, error) @@ -140,31 +347,15 @@ export function handleTokenRequest(config: OpenId4VciAccessTokenEndpointConfig) } } -export function verifyTokenRequest(options: { preAuthorizedCodeExpirationInSeconds: number }) { +export function verifyTokenRequest(options: { codeExpirationInSeconds: number }) { return async (request: OpenId4VcIssuanceRequest, response: Response, next: NextFunction) => { const { agentContext, issuer } = getRequestContext(request) 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') - } - 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 assertValidAccessTokenRequest(request.body, { + expirationDuration: options.codeExpirationInSeconds, + credentialOfferSessions: new OpenId4VcCredentialOfferSessionStateManager(agentContext, issuer.issuerId), }) - - // 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') - } } catch (error) { if (error instanceof TokenError) { sendErrorResponse( diff --git a/packages/openid4vc/src/openid4vc-issuer/router/credentialEndpoint.ts b/packages/openid4vc/src/openid4vc-issuer/router/credentialEndpoint.ts index 90ad62ee12..c864bc5a1c 100644 --- a/packages/openid4vc/src/openid4vc-issuer/router/credentialEndpoint.ts +++ b/packages/openid4vc/src/openid4vc-issuer/router/credentialEndpoint.ts @@ -1,4 +1,5 @@ import type { OpenId4VcIssuanceRequest } from './requestContext' +import type { VerifyAccessTokenResult } from './verifyAccessToken' import type { OpenId4VciCredentialRequest } from '../../shared' import type { OpenId4VciCredentialRequestToCredentialMapper } from '../OpenId4VcIssuerServiceOptions' import type { Router, Response } from 'express' @@ -29,12 +30,11 @@ export function configureCredentialEndpoint(router: Router, config: OpenId4VciCr const { agentContext, issuer } = getRequestContext(request) const openId4VcIssuerService = agentContext.dependencyManager.resolve(OpenId4VcIssuerService) - let preAuthorizedCode: string + let verifyAccessTokenResult: VerifyAccessTokenResult // Verify the access token (should at some point be moved to a middleware function or something) try { - preAuthorizedCode = (await verifyAccessToken(agentContext, issuer, request.headers.authorization)) - .preAuthorizedCode + verifyAccessTokenResult = await verifyAccessToken(agentContext, issuer, request.headers.authorization) } catch (error) { return sendErrorResponse(response, agentContext.config.logger, 401, 'unauthorized', error) } @@ -47,9 +47,21 @@ export function configureCredentialEndpoint(router: Router, config: OpenId4VciCr credentialRequest, }) - if (issuanceSession?.preAuthorizedCode !== preAuthorizedCode) { + 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}` + ) + return sendErrorResponse(response, agentContext.config.logger, 404, 'invalid_request', null) + } + + if ( + (verifyAccessTokenResult.preAuthorizedCode && + issuanceSession.preAuthorizedCode !== verifyAccessTokenResult.preAuthorizedCode) || + (verifyAccessTokenResult.issuerState && issuanceSession.issuerState !== verifyAccessTokenResult.issuerState) + ) { 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}` + `Credential request used access token with different a pre-authorized code or issuerState code than was used for the issuance session ${issuanceSession?.id}` ) return sendErrorResponse( response, @@ -60,14 +72,6 @@ export function configureCredentialEndpoint(router: Router, config: OpenId4VciCr ) } - 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}` - ) - return sendErrorResponse(response, agentContext.config.logger, 404, 'invalid_request', null) - } - const { credentialResponse } = await openId4VcIssuerService.createCredentialResponse(agentContext, { issuanceSession, credentialRequest, diff --git a/packages/openid4vc/src/openid4vc-issuer/router/metadataEndpoint.ts b/packages/openid4vc/src/openid4vc-issuer/router/metadataEndpoint.ts index b3ecb4edc4..6f0acb9492 100644 --- a/packages/openid4vc/src/openid4vc-issuer/router/metadataEndpoint.ts +++ b/packages/openid4vc/src/openid4vc-issuer/router/metadataEndpoint.ts @@ -18,7 +18,9 @@ export function configureIssuerMetadataEndpoint(router: Router) { credential_issuer: issuerMetadata.issuerUrl, token_endpoint: issuerMetadata.tokenEndpoint, credential_endpoint: issuerMetadata.credentialEndpoint, - authorization_server: issuerMetadata.authorizationServer, + // TODO: this is a temporary solution to support the first authorization server + // TODO: CHANGE THIS TO SUPPORT MULTIPLE AUTHORIZATION SERVERS + authorization_server: issuerMetadata.authorizationServers?.[0], credentials_supported: issuerMetadata.credentialsSupported, display: issuerMetadata.issuerDisplay, } satisfies CredentialIssuerMetadata diff --git a/packages/openid4vc/src/openid4vc-issuer/router/verifyAccessToken.ts b/packages/openid4vc/src/openid4vc-issuer/router/verifyAccessToken.ts index 8ee900afae..dd7b819402 100644 --- a/packages/openid4vc/src/openid4vc-issuer/router/verifyAccessToken.ts +++ b/packages/openid4vc/src/openid4vc-issuer/router/verifyAccessToken.ts @@ -5,11 +5,15 @@ import { CredoError, JwsService, Jwt } from '@credo-ts/core' import { OpenId4VcIssuerService } from '../OpenId4VcIssuerService' +export type VerifyAccessTokenResult = + | { preAuthorizedCode: string; issuerState: undefined } + | { preAuthorizedCode: undefined; issuerState: string } + export async function verifyAccessToken( agentContext: AgentContext, issuer: OpenId4VcIssuerRecord, authorizationHeader?: string -) { +): Promise { const openId4VcIssuerService = agentContext.dependencyManager.resolve(OpenId4VcIssuerService) if (!authorizationHeader || !authorizationHeader.startsWith('Bearer ')) { @@ -42,11 +46,22 @@ export async function verifyAccessToken( 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') + const { preAuthorizedCode, issuerState } = accessToken.payload.additionalClaims + + if (!preAuthorizedCode && !issuerState) { + throw new CredoError('No preAuthorizedCode or issuerState present in access token') } - return { - preAuthorizedCode: accessToken.payload.additionalClaims.preAuthorizedCode, + if (preAuthorizedCode && typeof preAuthorizedCode !== 'string') { + throw new CredoError('Invalid preAuthorizedCode present in access token') } + + if (issuerState && typeof issuerState !== 'string') { + throw new CredoError('Invalid issuerState present in access token') + } + + return { + preAuthorizedCode: preAuthorizedCode || undefined, + issuerState: issuerState || undefined, + } as VerifyAccessTokenResult } diff --git a/packages/openid4vc/src/shared/models/AuthorizationServer.ts b/packages/openid4vc/src/shared/models/AuthorizationServer.ts new file mode 100644 index 0000000000..5225a4a561 --- /dev/null +++ b/packages/openid4vc/src/shared/models/AuthorizationServer.ts @@ -0,0 +1,4 @@ +export interface OpenId4VciAuthorizationServerConfig { + // The base Url of your OAuth Server + baseUrl: string +} diff --git a/packages/openid4vc/src/shared/models/index.ts b/packages/openid4vc/src/shared/models/index.ts index 779881dbed..13691c9f99 100644 --- a/packages/openid4vc/src/shared/models/index.ts +++ b/packages/openid4vc/src/shared/models/index.ts @@ -16,8 +16,9 @@ import type { UniformCredentialRequest, } from '@sphereon/oid4vci-common' -export type OpenId4VciCredentialSupportedWithId = CredentialSupported & { id: string } -export type OpenId4VciCredentialSupported = CredentialSupported +export type OpenId4VciCredentialSupported = CredentialSupported & { id?: string; scope?: string } +export type OpenId4VciCredentialSupportedWithId = OpenId4VciCredentialSupported & { id: string; scope?: string } +export type OpenId4VciCredentialSupportedWithIdAndScope = OpenId4VciCredentialSupportedWithId & { scope: string } export type OpenId4VciIssuerMetadata = CredentialIssuerMetadata export type OpenId4VciIssuerMetadataDisplay = MetadataDisplay @@ -37,3 +38,4 @@ export type OpenId4VcSiopIdTokenPayload = IDTokenPayload export * from './OpenId4VcJwtIssuer' export * from './CredentialHolderBinding' export * from './OpenId4VciCredentialFormatProfile' +export * from './AuthorizationServer' From 80b55b40fe345ad6864b43c8a9161e87f6379541 Mon Sep 17 00:00:00 2001 From: Martin Auer Date: Wed, 11 Sep 2024 10:45:13 +0200 Subject: [PATCH 2/9] fix: make eslint happy --- .../openid4vc/src/openid4vc-issuer/OpenId4VcIssuerService.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerService.ts b/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerService.ts index 77fd3dfc7c..d8c5e36b7b 100644 --- a/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerService.ts +++ b/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerService.ts @@ -17,7 +17,6 @@ import type { } from '../shared' import type { AgentContext, DidDocument, Query, QueryOptions } from '@credo-ts/core' import type { - CredentialConfigurationSupported, CredentialOfferPayloadV1_0_11, CredentialOfferPayloadV1_0_13, Grant, From 9ccb645e5f44cd2491d0b0a1c334c58639151594 Mon Sep 17 00:00:00 2001 From: Martin Auer Date: Wed, 11 Sep 2024 10:45:35 +0200 Subject: [PATCH 3/9] feat: add method to test auth code flow --- demo-openid/package.json | 10 +- demo-openid/src/Holder.ts | 24 +- demo-openid/src/Issuer.ts | 11 +- demo-openid/src/Provider.ts | 81 +++ pnpm-lock.yaml | 1071 ++++++++++++++++++++++++++++------- 5 files changed, 995 insertions(+), 202 deletions(-) create mode 100644 demo-openid/src/Provider.ts diff --git a/demo-openid/package.json b/demo-openid/package.json index 6993266ab9..ad24c863f2 100644 --- a/demo-openid/package.json +++ b/demo-openid/package.json @@ -10,6 +10,7 @@ "license": "Apache-2.0", "scripts": { "issuer": "ts-node src/IssuerInquirer.ts", + "provider": "tsx src/Provider.ts", "holder": "ts-node src/HolderInquirer.ts", "verifier": "ts-node src/VerifierInquirer.ts" }, @@ -17,8 +18,11 @@ "@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/openid4vc": "workspace:*", @@ -28,8 +32,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.4.0" + "ts-node": "^10.4.0", + "tsx": "^4.11.0" } } diff --git a/demo-openid/src/Holder.ts b/demo-openid/src/Holder.ts index d16cdc7498..625d704527 100644 --- a/demo-openid/src/Holder.ts +++ b/demo-openid/src/Holder.ts @@ -39,16 +39,34 @@ export class Holder extends BaseAgent> resolvedCredentialOffer: OpenId4VciResolvedCredentialOffer, credentialsToRequest: string[] ) { - const tokenResponse = await this.agent.modules.openId4VcHolder.requestToken({ resolvedCredentialOffer }) + const resolvedAuthorizationRequest = await this.agent.modules.openId4VcHolder.resolveIssuanceAuthorizationRequest( + resolvedCredentialOffer, + { + clientId: 'foo', + redirectUri: 'http://example.com', + scope: ['openid', 'UniversityDegree'], + } + ) + + console.log(resolvedAuthorizationRequest.authorizationRequestUri) + + let code = 'not a valid code!' + code = 'MU_MtTZjjhjmzuzGZdsLSam2GcC-7c4g_k5ukV2XO3i' + + const tokenResponse = await this.agent.modules.openId4VcHolder.requestToken({ + resolvedAuthorizationRequest, + code, + resolvedCredentialOffer, + }) + const credentialResponse = await this.agent.modules.openId4VcHolder.requestCredentials({ resolvedCredentialOffer, - ...tokenResponse, - // TODO: add jwk support for holder binding credentialsToRequest, credentialBindingResolver: async () => ({ method: 'did', didUrl: this.verificationMethod.id, }), + ...tokenResponse, }) const storedCredentials = await Promise.all( diff --git a/demo-openid/src/Issuer.ts b/demo-openid/src/Issuer.ts index 410080e647..52aebf2343 100644 --- a/demo-openid/src/Issuer.ts +++ b/demo-openid/src/Issuer.ts @@ -4,6 +4,7 @@ import type { OpenId4VcCredentialHolderDidBinding, OpenId4VciCredentialRequestToCredentialMapper, OpenId4VciCredentialSupportedWithId, + OpenId4VciCredentialSupportedWithIdAndScope, OpenId4VcIssuerRecord, } from '@credo-ts/openid4vc' @@ -28,13 +29,15 @@ export const universityDegreeCredential = { id: 'UniversityDegreeCredential', format: OpenId4VciCredentialFormatProfile.JwtVcJson, types: ['VerifiableCredential', 'UniversityDegreeCredential'], -} satisfies OpenId4VciCredentialSupportedWithId + scope: 'openid4vc:credential:UniversityDegreeCredential', +} satisfies OpenId4VciCredentialSupportedWithIdAndScope export const openBadgeCredential = { id: 'OpenBadgeCredential', format: OpenId4VciCredentialFormatProfile.JwtVcJson, types: ['VerifiableCredential', 'OpenBadgeCredential'], -} satisfies OpenId4VciCredentialSupportedWithId + scope: 'openid4vc:credential:OpenBadgeCredential', +} satisfies OpenId4VciCredentialSupportedWithIdAndScope export const universityDegreeCredentialSdJwt = { id: 'UniversityDegreeCredential-sdjwt', @@ -148,7 +151,9 @@ export class Issuer extends BaseAgent<{ const issuer = new Issuer(2000, 'OpenId4VcIssuer ' + Math.random().toString()) await issuer.initializeAgent('96213c3d7fc8d4d6754c7a0fd969598f') issuer.issuerRecord = await issuer.agent.modules.openId4VcIssuer.createIssuer({ + issuerId: '726222ad-7624-4f12-b15b-e08aa7042ffa', credentialsSupported, + authorizationServerConfigs: [{ baseUrl: 'http://localhost:3042' }], }) return issuer @@ -158,7 +163,7 @@ export class Issuer extends BaseAgent<{ const { credentialOffer } = await this.agent.modules.openId4VcIssuer.createCredentialOffer({ issuerId: this.issuerRecord.issuerId, offeredCredentials, - preAuthorizedCodeFlowConfig: { userPinRequired: false }, + authorizationCodeFlowConfig: { issuerState: 'f498b73c-144f-4eea-bd6b-7be89b35936e' }, }) return credentialOffer diff --git a/demo-openid/src/Provider.ts b/demo-openid/src/Provider.ts new file mode 100644 index 0000000000..ab0ebe1b88 --- /dev/null +++ b/demo-openid/src/Provider.ts @@ -0,0 +1,81 @@ +import { bodyParser } from '@koa/bodyparser' +import Provider from 'oidc-provider' + +const oidc = new Provider('http://localhost:3042', { + clients: [ + { + client_id: 'foo', + client_secret: 'bar', + redirect_uris: ['http://example.com'], + scope: 'openid', + grant_types: ['authorization_code'], + }, + ], + pkce: { + methods: ['S256'], + required: () => { + console.log('checking pkce') + return true + }, + }, + features: { + dPoP: { enabled: false }, + pushedAuthorizationRequests: { + enabled: true, + //requirePushedAuthorizationRequests: true, + }, + }, + + async findAccount(ctx, id) { + console.log('called findAccount') + return { + accountId: id, + async claims(use, scope) { + console.log('called claims', scope) + return { sub: id } + }, + } + }, +}) + +oidc.use(bodyParser()) +oidc.use(async (ctx, next) => { + /** pre-processing + * you may target a specific action here by matching `ctx.path` + */ + + console.log('pre middleware', ctx.method, ctx.path) + + if (ctx.path.includes('request')) { + console.log(ctx.request.body) + ctx.request.body.client_secret = 'bar' + } + + if (ctx.path.includes('auth')) { + const match = ctx.body?.match(/code=([^&]*)/) + const code = match ? match[1] : null + console.log('code', code) + } + + if (ctx.path.includes('token')) { + console.log('token endpoint') + ctx.request.body.client_secret = 'bar' + } + + await next() + + /** post-processing + * since internal route matching was already executed you may target a specific action here + * checking `ctx.oidc.route`, the unique route names used are + */ + + console.log('post middleware', ctx.method, ctx.oidc?.route) + if (ctx.path.includes('token')) { + console.log('token endpoint') + ctx.response.body + } +}) + +oidc.listen(3042, () => { + console.log('oidc-provider listening on port 3042, check http://localhost:3042/.well-known/openid-configuration') +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index caa634ac22..d134d3dca6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -49,7 +49,7 @@ importers: version: 8.5.12 '@typescript-eslint/eslint-plugin': specifier: ^7.14.1 - version: 7.18.0(@typescript-eslint/parser@7.18.0)(eslint@8.57.0)(typescript@5.5.4) + version: 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0)(typescript@5.5.4) '@typescript-eslint/parser': specifier: ^7.14.1 version: 7.18.0(eslint@8.57.0)(typescript@5.5.4) @@ -67,13 +67,13 @@ importers: version: 8.10.0(eslint@8.57.0) eslint-import-resolver-typescript: specifier: ^3.5.3 - version: 3.6.1(@typescript-eslint/parser@7.18.0)(eslint-plugin-import@2.29.1)(eslint@8.57.0) + version: 3.6.1(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4))(eslint-plugin-import@2.29.1)(eslint@8.57.0) eslint-plugin-import: specifier: ^2.23.4 - version: 2.29.1(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + version: 2.29.1(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) eslint-plugin-prettier: specifier: ^4.2.1 - version: 4.2.1(eslint-config-prettier@8.10.0)(eslint@8.57.0)(prettier@2.8.8) + version: 4.2.1(eslint-config-prettier@8.10.0(eslint@8.57.0))(eslint@8.57.0)(prettier@2.8.8) eslint-plugin-workspaces: specifier: ^0.8.0 version: 0.8.0 @@ -82,7 +82,7 @@ importers: version: 4.19.2 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@18.18.8)(ts-node@10.9.2) + version: 29.7.0(@types/node@18.18.8)(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4)) prettier: specifier: ^2.3.1 version: 2.8.8 @@ -91,7 +91,7 @@ importers: version: 7.8.1 ts-jest: specifier: ^29.1.2 - version: 29.2.4(@babel/core@7.25.2)(@jest/types@29.6.3)(jest@29.7.0)(typescript@5.5.4) + 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(@types/node@18.18.8)(typescript@5.5.4)))(typescript@5.5.4) ts-node: specifier: ^10.0.0 version: 10.9.2(@types/node@18.18.8)(typescript@5.5.4) @@ -165,12 +165,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 +202,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 @@ -202,6 +214,9 @@ importers: ts-node: specifier: ^10.4.0 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 +312,7 @@ importers: devDependencies: '@animo-id/expo-secure-environment': specifier: ^0.0.1-alpha.0 - version: 0.0.1-alpha.0(expo@51.0.29)(react-native@0.71.19)(react@18.3.1) + 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) '@hyperledger/aries-askar-nodejs': specifier: ^0.2.3 version: 0.2.3 @@ -327,7 +342,7 @@ importers: dependencies: '@animo-id/react-native-bbs-signatures': specifier: ^0.1.0 - version: 0.1.0(react-native@0.71.19)(react@18.3.1) + 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) '@credo-ts/core': specifier: workspace:* version: link:../core @@ -404,13 +419,13 @@ importers: dependencies: '@digitalcredentials/jsonld': specifier: ^6.0.0 - version: 6.0.0(expo@51.0.29)(react-native@0.71.19) + 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) '@digitalcredentials/jsonld-signatures': specifier: ^9.4.0 - version: 9.4.0(expo@51.0.29)(react-native@0.71.19) + 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) '@digitalcredentials/vc': specifier: ^6.0.1 - version: 6.0.1(expo@51.0.29)(react-native@0.71.19) + 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) '@multiformats/base-x': specifier: ^4.0.1 version: 4.0.1 @@ -769,13 +784,13 @@ importers: devDependencies: react-native: specifier: ^0.71.4 - version: 0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.3)(react@18.2.0) + version: 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: specifier: ^2.20.0 - version: 2.20.0(react-native@0.71.19) + 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)) react-native-get-random-values: specifier: ^1.8.0 - version: 1.11.0(react-native@0.71.19) + 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)) rimraf: specifier: ^4.4.0 version: 4.4.1 @@ -1856,6 +1871,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} @@ -1951,6 +2110,9 @@ packages: 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==} @@ -2112,6 +2274,20 @@ packages: '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + '@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==} @@ -2369,6 +2545,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==} @@ -2483,6 +2663,10 @@ packages: '@stablelib/xchacha20poly1305@1.0.1': resolution: {integrity: sha512-B1Abj0sMJ8h3HNmGnJ7vHBrAvxuNka6cJJoZ1ILN7iuacXp7sUYcgOVEOTLWj+rtQMpspY9tXSCRLPmN1mQNWg==} + '@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==} @@ -2498,6 +2682,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==} @@ -2519,6 +2706,12 @@ packages: '@types/connect@3.4.38': resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + '@types/content-disposition@0.5.8': + resolution: {integrity: sha512-QVSSvno3dE0MgO76pJhmv4Qyi/j0Yk9pBp0Y7TJ2Tlj+KCgJWY6qX7nnxCOLkZ3VYRSIk1WTxCvwUSdx6CCLdg==} + + '@types/cookies@0.9.0': + resolution: {integrity: sha512-40Zk8qR147RABiQ7NQnBzWzDcjKzNrntB5BAmeGCb2p/MIyOE+4BVvc17wumsUqUw00bJYqoXFHYygQnEFh4/Q==} + '@types/cors@2.8.17': resolution: {integrity: sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==} @@ -2543,6 +2736,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==} @@ -2573,6 +2772,15 @@ 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==} @@ -2594,6 +2802,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==} @@ -3162,6 +3373,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'} @@ -3284,6 +3507,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'} @@ -3397,6 +3624,10 @@ packages: resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} engines: {node: '>= 0.6'} + 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==} @@ -3520,6 +3751,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: @@ -3528,6 +3763,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'} @@ -3550,6 +3788,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'} @@ -3576,6 +3818,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'} @@ -3746,6 +3992,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'} @@ -3882,6 +4133,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'} @@ -4133,6 +4388,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'} @@ -4295,6 +4554,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==} @@ -4378,10 +4641,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'} @@ -4430,6 +4708,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. @@ -4536,6 +4818,10 @@ packages: resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} engines: {node: '>=6'} + is-generator-function@1.0.10: + resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} + engines: {node: '>= 0.4'} + is-glob@2.0.1: resolution: {integrity: sha512-a1dBeB19NXsf/E0+FHqkagizel/LQw2DjSQpvQrj3zT+jYPpaUCryPnrQajXKFLCMuf4I6FhRpaGtw4lPrG6Eg==} engines: {node: '>=0.10.0'} @@ -4850,6 +5136,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==} @@ -4888,6 +5177,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==} @@ -4950,6 +5244,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==} @@ -4961,6 +5259,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'} @@ -5134,6 +5443,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==} @@ -5315,6 +5628,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==} @@ -5418,6 +5739,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==} @@ -5505,6 +5831,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==} @@ -5530,6 +5860,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'} @@ -5554,6 +5888,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'} @@ -5577,6 +5918,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'} @@ -5620,6 +5964,10 @@ packages: outdent@0.5.0: resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} + 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'} @@ -5719,6 +6067,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'} @@ -5882,6 +6233,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'} @@ -5959,10 +6318,6 @@ packages: peerDependencies: react: ^16.0.0 || ^17.0.0 || ^18.0.0 - react@18.2.0: - resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} - engines: {node: '>=0.10.0'} - react@18.3.1: resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} engines: {node: '>=0.10.0'} @@ -6054,6 +6409,9 @@ packages: resolution: {integrity: sha512-nYzyjnFcPNGR3lx9lwPPPnuQxv6JWEZd2Ci0u9opN7N5zUEPIhY/GbL3vMGOr2UXwEg9WwSyV9X9Y/kLFgPsOg==} engines: {node: '>= 4.0.0'} + 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'} @@ -6084,6 +6442,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'} @@ -6639,6 +7001,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'} @@ -7066,6 +7437,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'} @@ -7110,19 +7485,19 @@ 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)(react-native@0.71.19)(react@18.3.1)': + '@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)': 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) + expo: 51.0.29(@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.3)(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) - '@animo-id/react-native-bbs-signatures@0.1.0(react-native@0.71.19)(react@18.3.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)': dependencies: react: 18.3.1 - react-native: 0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.3)(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) '@astronautlabs/jsonpath@1.1.2': dependencies: @@ -8420,10 +8795,10 @@ snapshots: base64url-universal: 2.0.0 pako: 2.1.0 - '@digitalbazaar/http-client@3.4.1': + '@digitalbazaar/http-client@3.4.1(web-streams-polyfill@3.3.3)': dependencies: ky: 0.33.3 - ky-universal: 0.11.0(ky@0.33.3) + ky-universal: 0.11.0(ky@0.33.3)(web-streams-polyfill@3.3.3) undici: 5.28.4 transitivePeerDependencies: - web-streams-polyfill @@ -8432,20 +8807,20 @@ snapshots: '@digitalbazaar/vc-status-list-context@3.1.1': {} - '@digitalbazaar/vc-status-list@7.1.0': + '@digitalbazaar/vc-status-list@7.1.0(web-streams-polyfill@3.3.3)': dependencies: '@digitalbazaar/bitstring': 3.1.0 - '@digitalbazaar/vc': 5.0.0 + '@digitalbazaar/vc': 5.0.0(web-streams-polyfill@3.3.3) '@digitalbazaar/vc-status-list-context': 3.1.1 credentials-context: 2.0.0 transitivePeerDependencies: - web-streams-polyfill - '@digitalbazaar/vc@5.0.0': + '@digitalbazaar/vc@5.0.0(web-streams-polyfill@3.3.3)': dependencies: credentials-context: 2.0.0 - jsonld: 8.3.2 - jsonld-signatures: 11.3.0 + jsonld: 8.3.2(web-streams-polyfill@3.3.3) + jsonld-signatures: 11.3.0(web-streams-polyfill@3.3.3) transitivePeerDependencies: - web-streams-polyfill @@ -8460,11 +8835,11 @@ snapshots: '@digitalcredentials/base64url-universal': 2.0.6 pako: 2.1.0 - '@digitalcredentials/ed25519-signature-2020@3.0.2(expo@51.0.29)(react-native@0.71.19)': + '@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)': 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)(react-native@0.71.19) + '@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) ed25519-signature-2018-context: 1.1.0 ed25519-signature-2020-context: 1.1.0 transitivePeerDependencies: @@ -8480,20 +8855,20 @@ snapshots: base64url-universal: 1.1.0 crypto-ld: 6.0.0 - '@digitalcredentials/http-client@1.2.2': + '@digitalcredentials/http-client@1.2.2(web-streams-polyfill@3.3.3)': dependencies: ky: 0.25.1 - ky-universal: 0.8.2(ky@0.25.1) + ky-universal: 0.8.2(ky@0.25.1)(web-streams-polyfill@3.3.3) transitivePeerDependencies: - domexception - web-streams-polyfill - '@digitalcredentials/jsonld-signatures@9.4.0(expo@51.0.29)(react-native@0.71.19)': + '@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)': dependencies: '@digitalbazaar/security-context': 1.0.1 - '@digitalcredentials/jsonld': 6.0.0(expo@51.0.29)(react-native@0.71.19) + '@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) fast-text-encoding: 1.0.6 - isomorphic-webcrypto: 2.3.8(expo@51.0.29)(react-native@0.71.19) + 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)) serialize-error: 8.1.0 transitivePeerDependencies: - domexception @@ -8501,10 +8876,10 @@ snapshots: - react-native - web-streams-polyfill - '@digitalcredentials/jsonld@5.2.2(expo@51.0.29)(react-native@0.71.19)': + '@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)': dependencies: - '@digitalcredentials/http-client': 1.2.2 - '@digitalcredentials/rdf-canonize': 1.0.0(expo@51.0.29)(react-native@0.71.19) + '@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)) canonicalize: 1.0.8 lru-cache: 6.0.0 transitivePeerDependencies: @@ -8513,10 +8888,10 @@ snapshots: - react-native - web-streams-polyfill - '@digitalcredentials/jsonld@6.0.0(expo@51.0.29)(react-native@0.71.19)': + '@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)': dependencies: - '@digitalcredentials/http-client': 1.2.2 - '@digitalcredentials/rdf-canonize': 1.0.0(expo@51.0.29)(react-native@0.71.19) + '@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)) canonicalize: 1.0.8 lru-cache: 6.0.0 transitivePeerDependencies: @@ -8527,19 +8902,19 @@ snapshots: '@digitalcredentials/open-badges-context@2.1.0': {} - '@digitalcredentials/rdf-canonize@1.0.0(expo@51.0.29)(react-native@0.71.19)': + '@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))': dependencies: fast-text-encoding: 1.0.6 - isomorphic-webcrypto: 2.3.8(expo@51.0.29)(react-native@0.71.19) + 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)) transitivePeerDependencies: - expo - react-native - '@digitalcredentials/vc-status-list@5.0.2(expo@51.0.29)(react-native@0.71.19)': + '@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)': dependencies: '@digitalbazaar/vc-status-list-context': 3.1.1 '@digitalcredentials/bitstring': 2.0.1 - '@digitalcredentials/vc': 4.2.0(expo@51.0.29)(react-native@0.71.19) + '@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) credentials-context: 2.0.0 transitivePeerDependencies: - domexception @@ -8547,10 +8922,10 @@ snapshots: - react-native - web-streams-polyfill - '@digitalcredentials/vc@4.2.0(expo@51.0.29)(react-native@0.71.19)': + '@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)': dependencies: - '@digitalcredentials/jsonld': 5.2.2(expo@51.0.29)(react-native@0.71.19) - '@digitalcredentials/jsonld-signatures': 9.4.0(expo@51.0.29)(react-native@0.71.19) + '@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) credentials-context: 2.0.0 transitivePeerDependencies: - domexception @@ -8558,14 +8933,14 @@ snapshots: - react-native - web-streams-polyfill - '@digitalcredentials/vc@6.0.1(expo@51.0.29)(react-native@0.71.19)': + '@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)': dependencies: - '@digitalbazaar/vc-status-list': 7.1.0 - '@digitalcredentials/ed25519-signature-2020': 3.0.2(expo@51.0.29)(react-native@0.71.19) - '@digitalcredentials/jsonld': 6.0.0(expo@51.0.29)(react-native@0.71.19) - '@digitalcredentials/jsonld-signatures': 9.4.0(expo@51.0.29)(react-native@0.71.19) + '@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/open-badges-context': 2.1.0 - '@digitalcredentials/vc-status-list': 5.0.2(expo@51.0.29)(react-native@0.71.19) + '@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) credentials-context: 2.0.0 fix-esm: 1.0.1 transitivePeerDependencies: @@ -8575,6 +8950,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 @@ -8881,6 +9328,8 @@ snapshots: dependencies: graphql: 15.8.0 + '@hapi/bourne@3.0.0': {} + '@hapi/hoek@9.3.0': {} '@hapi/topo@5.1.0': @@ -8973,7 +9422,7 @@ snapshots: jest-util: 29.7.0 slash: 3.0.0 - '@jest/core@29.7.0(ts-node@10.9.2)': + '@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 @@ -8987,7 +9436,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) + 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 @@ -9179,6 +9628,27 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 + '@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 @@ -9545,14 +10015,14 @@ snapshots: '@react-native/assets@1.0.0': {} - '@react-native/babel-plugin-codegen@0.74.87(@babel/preset-env@7.25.3)': + '@react-native/babel-plugin-codegen@0.74.87(@babel/preset-env@7.25.3(@babel/core@7.25.2))': dependencies: - '@react-native/codegen': 0.74.87(@babel/preset-env@7.25.3) + '@react-native/codegen': 0.74.87(@babel/preset-env@7.25.3(@babel/core@7.25.2)) transitivePeerDependencies: - '@babel/preset-env' - supports-color - '@react-native/babel-preset@0.74.87(@babel/core@7.25.2)(@babel/preset-env@7.25.3)': + '@react-native/babel-preset@0.74.87(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@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) @@ -9594,21 +10064,21 @@ snapshots: '@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) + '@react-native/babel-plugin-codegen': 0.74.87(@babel/preset-env@7.25.3(@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 - '@react-native/codegen@0.74.87(@babel/preset-env@7.25.3)': + '@react-native/codegen@0.74.87(@babel/preset-env@7.25.3(@babel/core@7.25.2))': dependencies: '@babel/parser': 7.25.3 '@babel/preset-env': 7.25.3(@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.3) + jscodeshift: 0.14.0(@babel/preset-env@7.25.3(@babel/core@7.25.2)) mkdirp: 0.5.6 nullthrows: 1.1.1 transitivePeerDependencies: @@ -9724,6 +10194,8 @@ snapshots: '@sinclair/typebox@0.27.8': {} + '@sindresorhus/is@5.6.0': {} + '@sinonjs/commons@3.0.1': dependencies: type-detect: 4.0.8 @@ -9934,6 +10406,10 @@ snapshots: '@stablelib/wipe': 1.0.1 '@stablelib/xchacha20': 1.0.1 + '@szmarczak/http-timer@5.0.1': + dependencies: + defer-to-connect: 2.0.1 + '@tokenizer/token@0.3.0': {} '@tsconfig/node10@1.0.11': {} @@ -9944,6 +10420,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 @@ -9978,6 +10458,15 @@ snapshots: dependencies: '@types/node': 18.18.8 + '@types/content-disposition@0.5.8': {} + + '@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 @@ -10011,6 +10500,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': @@ -10044,6 +10537,23 @@ 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': {} @@ -10064,6 +10574,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': {} @@ -10129,7 +10644,7 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 - '@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0)(eslint@8.57.0)(typescript@5.5.4)': + '@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0)(typescript@5.5.4)': dependencies: '@eslint-community/regexpp': 4.11.0 '@typescript-eslint/parser': 7.18.0(eslint@8.57.0)(typescript@5.5.4) @@ -10142,6 +10657,7 @@ snapshots: ignore: 5.3.2 natural-compare: 1.4.0 ts-api-utils: 1.3.0(typescript@5.5.4) + optionalDependencies: typescript: 5.5.4 transitivePeerDependencies: - supports-color @@ -10154,6 +10670,7 @@ snapshots: '@typescript-eslint/visitor-keys': 7.18.0 debug: 4.3.6 eslint: 8.57.0 + optionalDependencies: typescript: 5.5.4 transitivePeerDependencies: - supports-color @@ -10170,6 +10687,7 @@ snapshots: debug: 4.3.6 eslint: 8.57.0 ts-api-utils: 1.3.0(typescript@5.5.4) + optionalDependencies: typescript: 5.5.4 transitivePeerDependencies: - supports-color @@ -10186,6 +10704,7 @@ snapshots: minimatch: 9.0.5 semver: 7.6.3 ts-api-utils: 1.3.0(typescript@5.5.4) + optionalDependencies: typescript: 5.5.4 transitivePeerDependencies: - supports-color @@ -10270,7 +10789,7 @@ snapshots: indent-string: 4.0.0 ajv-formats@2.1.1(ajv@8.17.1): - dependencies: + optionalDependencies: ajv: 8.17.1 ajv@6.12.6: @@ -10557,7 +11076,7 @@ snapshots: '@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-preset-expo@11.0.14(@babel/core@7.25.2)(@babel/preset-env@7.25.3): + babel-preset-expo@11.0.14(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@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) @@ -10565,7 +11084,7 @@ snapshots: '@babel/plugin-transform-parameters': 7.24.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) + '@react-native/babel-preset': 0.74.87(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@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 @@ -10782,6 +11301,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 @@ -10892,6 +11428,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: {} @@ -11004,6 +11548,11 @@ snapshots: cookie@0.6.0: {} + cookies@0.9.1: + dependencies: + depd: 2.0.0 + keygrip: 1.1.0 + core-js-compat@3.38.1: dependencies: browserslist: 4.23.3 @@ -11027,13 +11576,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): + 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) + 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: @@ -11133,8 +11682,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: {} @@ -11152,6 +11707,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 @@ -11183,6 +11740,8 @@ snapshots: denodeify@1.2.1: {} + depd@1.1.2: {} + depd@2.0.0: {} deprecated-react-native-prop-types@3.0.2: @@ -11398,6 +11957,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: {} @@ -11429,13 +12015,13 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.18.0)(eslint-plugin-import@2.29.1)(eslint@8.57.0): + eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4))(eslint-plugin-import@2.29.1)(eslint@8.57.0): dependencies: debug: 4.3.6 enhanced-resolve: 5.17.1 eslint: 8.57.0 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4))(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) fast-glob: 3.3.2 get-tsconfig: 4.7.6 is-core-module: 2.15.0 @@ -11446,19 +12032,19 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.8.1(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0): + eslint-module-utils@2.8.1(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4))(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0): dependencies: - '@typescript-eslint/parser': 7.18.0(eslint@8.57.0)(typescript@5.5.4) debug: 3.2.7 + optionalDependencies: + '@typescript-eslint/parser': 7.18.0(eslint@8.57.0)(typescript@5.5.4) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.18.0)(eslint-plugin-import@2.29.1)(eslint@8.57.0) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4))(eslint-plugin-import@2.29.1)(eslint@8.57.0) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0): + eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0): dependencies: - '@typescript-eslint/parser': 7.18.0(eslint@8.57.0)(typescript@5.5.4) array-includes: 3.1.8 array.prototype.findlastindex: 1.2.5 array.prototype.flat: 1.3.2 @@ -11467,7 +12053,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4))(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0) hasown: 2.0.2 is-core-module: 2.15.0 is-glob: 4.0.3 @@ -11477,17 +12063,20 @@ snapshots: object.values: 1.2.0 semver: 6.3.1 tsconfig-paths: 3.15.0 + optionalDependencies: + '@typescript-eslint/parser': 7.18.0(eslint@8.57.0)(typescript@5.5.4) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-prettier@4.2.1(eslint-config-prettier@8.10.0)(eslint@8.57.0)(prettier@2.8.8): + eslint-plugin-prettier@4.2.1(eslint-config-prettier@8.10.0(eslint@8.57.0))(eslint@8.57.0)(prettier@2.8.8): dependencies: eslint: 8.57.0 - eslint-config-prettier: 8.10.0(eslint@8.57.0) prettier: 2.8.8 prettier-linter-helpers: 1.0.0 + optionalDependencies: + eslint-config-prettier: 8.10.0(eslint@8.57.0) eslint-plugin-workspaces@0.8.0: dependencies: @@ -11574,6 +12163,8 @@ snapshots: esutils@2.0.3: {} + eta@3.5.0: {} + etag@1.8.1: {} event-emitter@0.3.5: @@ -11619,35 +12210,35 @@ snapshots: jest-message-util: 29.7.0 jest-util: 29.7.0 - expo-asset@10.0.10(expo@51.0.29): + 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))): dependencies: - expo: 51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3) - expo-constants: 16.0.2(expo@51.0.29) + 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))) invariant: 2.2.4 md5-file: 3.2.3 transitivePeerDependencies: - supports-color - expo-constants@16.0.2(expo@51.0.29): + 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))): 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) + expo: 51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2)) transitivePeerDependencies: - supports-color - expo-file-system@17.0.1(expo@51.0.29): + 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))): dependencies: - expo: 51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3) + 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): + 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))): dependencies: - expo: 51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3) + expo: 51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2)) fontfaceobserver: 2.3.0 - expo-keep-awake@13.0.2(expo@51.0.29): + 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))): dependencies: - expo: 51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3) + expo: 51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2)) expo-modules-autolinking@0.0.3: dependencies: @@ -11672,13 +12263,13 @@ snapshots: dependencies: invariant: 2.2.4 - expo-random@14.0.1(expo@51.0.29): + 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))): dependencies: base64-js: 1.5.1 - expo: 51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3) + expo: 51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2)) optional: true - expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3): + expo@51.0.29(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2)): dependencies: '@babel/runtime': 7.25.0 '@expo/cli': 0.18.29(expo-modules-autolinking@1.11.2) @@ -11686,11 +12277,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) - expo-asset: 10.0.10(expo@51.0.29) - expo-file-system: 17.0.1(expo@51.0.29) - expo-font: 12.0.9(expo@51.0.29) - expo-keep-awake: 13.0.2(expo@51.0.29) + 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))) expo-modules-autolinking: 1.11.2 expo-modules-core: 1.12.21 fbemitter: 3.0.0 @@ -11935,6 +12526,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 @@ -12130,6 +12723,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: {} @@ -12208,6 +12815,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 @@ -12216,6 +12838,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 @@ -12256,6 +12883,8 @@ snapshots: indent-string@4.0.0: {} + inflation@2.1.0: {} + inflight@1.0.6: dependencies: once: 1.4.0 @@ -12370,6 +12999,10 @@ snapshots: is-generator-fn@2.1.0: {} + is-generator-function@1.0.10: + dependencies: + has-tostringtag: 1.0.2 + is-glob@2.0.1: dependencies: is-extglob: 1.0.0 @@ -12457,7 +13090,7 @@ snapshots: isobject@3.0.1: {} - isomorphic-webcrypto@2.3.8(expo@51.0.29)(react-native@0.71.19): + 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)): dependencies: '@peculiar/webcrypto': 1.5.0 asmcrypto.js: 0.22.0 @@ -12469,8 +13102,8 @@ snapshots: optionalDependencies: '@unimodules/core': 7.1.2 '@unimodules/react-native-adapter': 6.3.9 - expo-random: 14.0.1(expo@51.0.29) - react-native-securerandom: 0.1.1(react-native@0.71.19) + 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)) transitivePeerDependencies: - expo - react-native @@ -12565,16 +13198,16 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@29.7.0(@types/node@18.18.8)(ts-node@10.9.2): + 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) + '@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) + 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) + 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 @@ -12584,12 +13217,11 @@ snapshots: - supports-color - ts-node - jest-config@29.7.0(@types/node@18.18.8)(ts-node@10.9.2): + 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 '@jest/test-sequencer': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 18.18.8 babel-jest: 29.7.0(@babel/core@7.25.2) chalk: 4.1.2 ci-info: 3.9.0 @@ -12609,6 +13241,8 @@ snapshots: pretty-format: 29.7.0 slash: 3.0.0 strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 18.18.8 ts-node: 10.9.2(@types/node@18.18.8)(typescript@5.5.4) transitivePeerDependencies: - babel-plugin-macros @@ -12693,7 +13327,7 @@ snapshots: jest-util: 29.7.0 jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): - dependencies: + optionalDependencies: jest-resolve: 29.7.0 jest-regex-util@27.5.1: {} @@ -12862,12 +13496,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): + 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) + '@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) + 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 @@ -12886,6 +13520,8 @@ snapshots: join-component@1.1.0: {} + jose@5.8.0: {} + js-base64@3.7.7: {} js-sha3@0.8.0: {} @@ -12905,7 +13541,7 @@ snapshots: jsc-safe-url@0.2.4: {} - jscodeshift@0.14.0(@babel/preset-env@7.25.3): + jscodeshift@0.14.0(@babel/preset-env@7.25.3(@babel/core@7.25.2)): dependencies: '@babel/core': 7.25.2 '@babel/parser': 7.25.3 @@ -12934,6 +13570,8 @@ snapshots: jsesc@2.5.2: {} + jsesc@3.0.2: {} + json-buffer@3.0.1: {} json-parse-better-errors@1.0.2: {} @@ -12979,18 +13617,18 @@ snapshots: optionalDependencies: graceful-fs: 4.2.11 - jsonld-signatures@11.3.0: + jsonld-signatures@11.3.0(web-streams-polyfill@3.3.3): dependencies: '@digitalbazaar/security-context': 1.0.1 - jsonld: 8.3.2 + jsonld: 8.3.2(web-streams-polyfill@3.3.3) rdf-canonize: 4.0.1 serialize-error: 8.1.0 transitivePeerDependencies: - web-streams-polyfill - jsonld@8.3.2: + jsonld@8.3.2(web-streams-polyfill@3.3.3): dependencies: - '@digitalbazaar/http-client': 3.4.1 + '@digitalbazaar/http-client': 3.4.1(web-streams-polyfill@3.3.3) canonicalize: 1.0.8 lru-cache: 6.0.0 rdf-canonize: 3.4.0 @@ -13007,6 +13645,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 @@ -13015,17 +13657,56 @@ snapshots: kleur@3.0.3: {} - ky-universal@0.11.0(ky@0.33.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 ky: 0.33.3 node-fetch: 3.3.2 + optionalDependencies: + web-streams-polyfill: 3.3.3 - ky-universal@0.8.2(ky@0.25.1): + ky-universal@0.8.2(ky@0.25.1)(web-streams-polyfill@3.3.3): dependencies: abort-controller: 3.0.0 ky: 0.25.1 node-fetch: 3.0.0-beta.9 + optionalDependencies: + web-streams-polyfill: 3.3.3 transitivePeerDependencies: - domexception @@ -13163,6 +13844,8 @@ snapshots: dependencies: js-tokens: 4.0.0 + lowercase-keys@3.0.0: {} + lru-cache@10.4.3: {} lru-cache@4.1.5: @@ -13513,6 +14196,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: {} @@ -13602,6 +14289,8 @@ snapshots: nanoid@3.3.7: {} + nanoid@5.0.7: {} + natural-compare@1.4.0: {} negotiator@0.6.3: {} @@ -13683,6 +14372,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 @@ -13711,6 +14402,8 @@ snapshots: object-assign@4.1.1: {} + object-hash@3.0.0: {} + object-inspect@1.13.2: {} object-keys@1.1.1: {} @@ -13741,6 +14434,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 @@ -13763,6 +14476,8 @@ snapshots: dependencies: mimic-fn: 2.1.0 + only@0.0.2: {} + open@6.4.0: dependencies: is-wsl: 1.1.0 @@ -13828,6 +14543,8 @@ snapshots: outdent@0.5.0: {} + p-cancelable@3.0.0: {} + p-filter@2.1.0: dependencies: p-map: 2.1.0 @@ -13912,6 +14629,8 @@ snapshots: path-to-regexp@0.1.7: {} + path-to-regexp@6.2.2: {} + path-type@4.0.0: {} peek-readable@4.1.0: {} @@ -14086,6 +14805,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: @@ -14124,82 +14847,36 @@ snapshots: react-is@18.3.1: {} - react-native-codegen@0.71.6(@babel/preset-env@7.25.3): + react-native-codegen@0.71.6(@babel/preset-env@7.25.3(@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.3) + jscodeshift: 0.14.0(@babel/preset-env@7.25.3(@babel/core@7.25.2)) nullthrows: 1.1.1 transitivePeerDependencies: - '@babel/preset-env' - supports-color - react-native-fs@2.20.0(react-native@0.71.19): + 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)): dependencies: base-64: 0.1.0 - react-native: 0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.3)(react@18.2.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) utf8: 3.0.0 - react-native-get-random-values@1.11.0(react-native@0.71.19): + 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)): dependencies: fast-base64-decode: 1.0.0 - react-native: 0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.3)(react@18.2.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-gradle-plugin@0.71.19: {} - react-native-securerandom@0.1.1(react-native@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)): dependencies: base64-js: 1.5.1 - react-native: 0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.3)(react@18.2.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) optional: true - react-native@0.71.19(@babel/core@7.25.2)(@babel/preset-env@7.25.3)(react@18.2.0): - dependencies: - '@jest/create-cache-key-function': 29.7.0 - '@react-native-community/cli': 10.2.7(@babel/core@7.25.2) - '@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.2) - 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.2.0 - react-devtools-core: 4.28.5 - react-native-codegen: 0.71.6(@babel/preset-env@7.25.3) - react-native-gradle-plugin: 0.71.19 - react-refresh: 0.4.3 - react-shallow-renderer: 16.15.0(react@18.2.0) - regenerator-runtime: 0.13.11 - scheduler: 0.23.2 - stacktrace-parser: 0.1.10 - use-sync-external-store: 1.2.2(react@18.2.0) - 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.2)(@babel/preset-env@7.25.3)(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): dependencies: '@jest/create-cache-key-function': 29.7.0 '@react-native-community/cli': 10.2.7(@babel/core@7.25.2) @@ -14227,7 +14904,7 @@ 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) + react-native-codegen: 0.71.6(@babel/preset-env@7.25.3(@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) @@ -14249,22 +14926,12 @@ snapshots: react-refresh@0.4.3: {} - react-shallow-renderer@16.15.0(react@18.2.0): - dependencies: - object-assign: 4.1.1 - react: 18.2.0 - react-is: 18.3.1 - react-shallow-renderer@16.15.0(react@18.3.1): dependencies: object-assign: 4.1.1 react: 18.3.1 react-is: 18.3.1 - react@18.2.0: - dependencies: - loose-envify: 1.4.0 - react@18.3.1: dependencies: loose-envify: 1.4.0 @@ -14375,6 +15042,8 @@ snapshots: rc: 1.2.8 resolve: 1.7.1 + resolve-alpn@1.2.1: {} + resolve-cwd@3.0.0: dependencies: resolve-from: 5.0.0 @@ -14399,6 +15068,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 @@ -14916,14 +15589,12 @@ snapshots: ts-interface-checker@0.1.13: {} - ts-jest@29.2.4(@babel/core@7.25.2)(@jest/types@29.6.3)(jest@29.7.0)(typescript@5.5.4): + 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(@types/node@18.18.8)(typescript@5.5.4)))(typescript@5.5.4): dependencies: - '@babel/core': 7.25.2 - '@jest/types': 29.6.3 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) + 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 @@ -14931,6 +15602,11 @@ snapshots: semver: 7.6.3 typescript: 5.5.4 yargs-parser: 21.1.1 + optionalDependencies: + '@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) ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4): dependencies: @@ -14966,6 +15642,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 @@ -15126,10 +15811,6 @@ snapshots: url-join@4.0.0: {} - use-sync-external-store@1.2.2(react@18.2.0): - dependencies: - react: 18.2.0 - use-sync-external-store@1.2.2(react@18.3.1): dependencies: react: 18.3.1 @@ -15371,6 +16052,8 @@ snapshots: y18n: 5.0.8 yargs-parser: 21.1.1 + ylru@1.4.0: {} + yn@3.1.1: {} yocto-queue@0.1.0: {} From b2eb533838ed5051a1e8f629cc35933928c285e4 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Wed, 9 Oct 2024 11:07:24 +0200 Subject: [PATCH 4/9] fix: host separate oauth-auhtorization-server Signed-off-by: Timo Glastra --- .../openid4vc-issuer/OpenId4VcIssuerModule.ts | 2 + .../authorizationServerMetadataEndpoint.ts | 41 +++++++++++++++++++ .../src/openid4vc-issuer/router/index.ts | 1 + ...aEndpoint.ts => issuerMetadataEndpoint.ts} | 16 +++++--- 4 files changed, 55 insertions(+), 5 deletions(-) create mode 100644 packages/openid4vc/src/openid4vc-issuer/router/authorizationServerMetadataEndpoint.ts rename packages/openid4vc/src/openid4vc-issuer/router/{metadataEndpoint.ts => issuerMetadataEndpoint.ts} (86%) diff --git a/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerModule.ts b/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerModule.ts index 44f4f6e84c..655ad898d6 100644 --- a/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerModule.ts +++ b/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerModule.ts @@ -16,6 +16,7 @@ import { configureAccessTokenEndpoint, configureCredentialEndpoint, configureIssuerMetadataEndpoint, + configureOAuthAuthorizationServerMetadataEndpoint, } from './router' /** @@ -116,6 +117,7 @@ export class OpenId4VcIssuerModule implements Module { // Configure endpoints configureIssuerMetadataEndpoint(endpointRouter) + configureOAuthAuthorizationServerMetadataEndpoint(endpointRouter) configureCredentialOfferEndpoint(endpointRouter, this.config.credentialOfferEndpoint) configureAccessTokenEndpoint(endpointRouter, this.config.accessTokenEndpoint) configureCredentialEndpoint(endpointRouter, this.config.credentialEndpoint) 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..723aa1d3d9 --- /dev/null +++ b/packages/openid4vc/src/openid4vc-issuer/router/authorizationServerMetadataEndpoint.ts @@ -0,0 +1,41 @@ +import type { OpenId4VcIssuanceRequest } from './requestContext' +import type { AuthorizationServerMetadata } from '@sphereon/oid4vci-common' +import type { Router, Response } from 'express' + +import { getRequestContext, sendErrorResponse } 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', + (_request: OpenId4VcIssuanceRequest, response: Response, next) => { + const { agentContext, issuer } = getRequestContext(_request) + try { + const openId4VcIssuerService = agentContext.dependencyManager.resolve(OpenId4VcIssuerService) + const issuerMetadata = openId4VcIssuerService.getIssuerMetadata(agentContext, issuer) + + const authorizationServerMetadata = { + issuer: issuerMetadata.issuerUrl, + token_endpoint: issuerMetadata.tokenEndpoint, + dpop_signing_alg_values_supported: issuerMetadata.dpopSigningAlgValuesSupported, + 'pre-authorized_grant_anonymous_access_supported': true, + + // Required by sphereon types, but OID4VCI mentions it can be omitted if + // only the pre-auth code flow is supported. We use empty array + response_types_supported: [] + } satisfies AuthorizationServerMetadata + + response.status(200).json(authorizationServerMetadata) + } 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/index.ts b/packages/openid4vc/src/openid4vc-issuer/router/index.ts index d261c81c50..06a73f16c2 100644 --- a/packages/openid4vc/src/openid4vc-issuer/router/index.ts +++ b/packages/openid4vc/src/openid4vc-issuer/router/index.ts @@ -1,5 +1,6 @@ export { configureAccessTokenEndpoint, OpenId4VciAccessTokenEndpointConfig } from './accessTokenEndpoint' export { configureCredentialEndpoint, OpenId4VciCredentialEndpointConfig } from './credentialEndpoint' export { configureIssuerMetadataEndpoint } from './metadataEndpoint' +export { configureOAuthAuthorizationServerMetadataEndpoint } from './authorizationServerMetadataEndpoint' export { configureCredentialOfferEndpoint, OpenId4VciCredentialOfferEndpointConfig } from './credentialOfferEndpoint' export { OpenId4VcIssuanceRequest } from './requestContext' diff --git a/packages/openid4vc/src/openid4vc-issuer/router/metadataEndpoint.ts b/packages/openid4vc/src/openid4vc-issuer/router/issuerMetadataEndpoint.ts similarity index 86% rename from packages/openid4vc/src/openid4vc-issuer/router/metadataEndpoint.ts rename to packages/openid4vc/src/openid4vc-issuer/router/issuerMetadataEndpoint.ts index c6d214693b..0a267f55b7 100644 --- a/packages/openid4vc/src/openid4vc-issuer/router/metadataEndpoint.ts +++ b/packages/openid4vc/src/openid4vc-issuer/router/issuerMetadataEndpoint.ts @@ -13,17 +13,23 @@ export function configureIssuerMetadataEndpoint(router: Router) { 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, - // TODO: this is a temporary solution to support the first authorization server - // TODO: CHANGE THIS TO SUPPORT MULTIPLE AUTHORIZATION SERVERS + display: issuerMetadata.issuerDisplay, + + // OID4VCI draft 11 (only one auth server is supported) authorization_server: issuerMetadata.authorizationServers?.[0], - authorization_servers: issuerMetadata.authorizationServers, credentials_supported: issuerMetadata.credentialsSupported, + + // OID4VCI draft 13 + authorization_servers: issuerMetadata.authorizationServers, credential_configurations_supported: issuerMetadata.credentialConfigurationsSupported, - display: issuerMetadata.issuerDisplay, + + // 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: issuerMetadata.tokenEndpoint, dpop_signing_alg_values_supported: issuerMetadata.dpopSigningAlgValuesSupported, } satisfies CredentialIssuerMetadata From 3de4e822587cb4b0374c0eb4ac446f48056ac017 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Tue, 29 Oct 2024 15:49:54 +0530 Subject: [PATCH 5/9] temp Signed-off-by: Timo Glastra --- .babelrc | 11 + demo-openid/src/Holder.ts | 2 - demo-openid/src/Issuer.ts | 25 +- demo-openid/src/Provider.ts | 42 +- jest.config.base.ts | 6 +- package.json | 9 +- packages/core/package.json | 2 +- .../core/src/agent/context/AgentContext.ts | 3 + .../src/transport/HttpInboundTransport.ts | 1 + packages/openid4vc/package.json | 7 +- .../openid4vc-holder/OpenId4VcHolderApi.ts | 11 +- .../OpenId4VciHolderService.ts | 629 ++-- .../OpenId4VciHolderServiceOptions.ts | 37 +- .../OpenId4vcSiopHolderService.ts | 2 +- .../openid4vc-issuer/OpenId4VcIssuerModule.ts | 18 +- .../OpenId4VcIssuerService.ts | 181 +- .../OpenId4VcIssuerServiceOptions.ts | 10 +- .../authorization/clientAuth.ts | 82 + .../authorization/discover.ts | 18 + .../openid4vc-issuer/authorization/dpop.ts | 30 + .../authorization/externalAuthorization.ts | 131 + .../internalAuthorization.ts} | 73 +- .../authorization/introspect.ts | 29 + .../authorization/verifyResourceRequest.ts | 50 + .../openid4vc-issuer/oauth4webapi.native.ts | 5 + .../src/openid4vc-issuer/oauth4webapi.ts | 12 + .../router/accessTokenEndpoint.ts | 328 +- .../authorizationServerMetadataEndpoint.ts | 15 +- .../router/credentialEndpoint.ts | 86 +- .../router/credentialOfferEndpoint.ts | 20 +- .../src/openid4vc-issuer/router/index.ts | 2 +- .../router/issuerMetadataEndpoint.ts | 9 +- .../router/authorizationEndpoint.ts | 9 +- .../router/authorizationRequestEndpoint.ts | 13 +- .../src/shared/issuerMetadataUtils.ts | 46 +- .../src/shared/models/AuthorizationServer.ts | 8 +- .../OpenId4VciCredentialFormatProfile.ts | 1 + packages/openid4vc/src/shared/models/index.ts | 8 +- .../openid4vc/src/shared/router/context.ts | 28 +- packages/openid4vc/src/shared/utils.ts | 14 +- .../openid4vc/tests/openid4vc.e2e.test.ts | 403 +-- packages/openid4vc/tests/utils.ts | 8 +- pnpm-lock.yaml | 2924 ++++++++++++++--- tests/https-proxy.ts | 71 + 44 files changed, 3880 insertions(+), 1539 deletions(-) create mode 100644 .babelrc create mode 100644 packages/openid4vc/src/openid4vc-issuer/authorization/clientAuth.ts create mode 100644 packages/openid4vc/src/openid4vc-issuer/authorization/discover.ts create mode 100644 packages/openid4vc/src/openid4vc-issuer/authorization/dpop.ts create mode 100644 packages/openid4vc/src/openid4vc-issuer/authorization/externalAuthorization.ts rename packages/openid4vc/src/openid4vc-issuer/{router/verifyResourceRequest.ts => authorization/internalAuthorization.ts} (54%) create mode 100644 packages/openid4vc/src/openid4vc-issuer/authorization/introspect.ts create mode 100644 packages/openid4vc/src/openid4vc-issuer/authorization/verifyResourceRequest.ts create mode 100644 packages/openid4vc/src/openid4vc-issuer/oauth4webapi.native.ts create mode 100644 packages/openid4vc/src/openid4vc-issuer/oauth4webapi.ts create mode 100644 tests/https-proxy.ts diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000000..e3c483116e --- /dev/null +++ b/.babelrc @@ -0,0 +1,11 @@ +{ + "presets": [ + [ + "@babel/preset-env", + { + // "modules": "commonjs", + // "debug": true + } + ] + ] +} diff --git a/demo-openid/src/Holder.ts b/demo-openid/src/Holder.ts index 625d704527..c481e5db89 100644 --- a/demo-openid/src/Holder.ts +++ b/demo-openid/src/Holder.ts @@ -48,8 +48,6 @@ export class Holder extends BaseAgent> } ) - console.log(resolvedAuthorizationRequest.authorizationRequestUri) - let code = 'not a valid code!' code = 'MU_MtTZjjhjmzuzGZdsLSam2GcC-7c4g_k5ukV2XO3i' diff --git a/demo-openid/src/Issuer.ts b/demo-openid/src/Issuer.ts index 52aebf2343..848ff49320 100644 --- a/demo-openid/src/Issuer.ts +++ b/demo-openid/src/Issuer.ts @@ -153,17 +153,36 @@ export class Issuer extends BaseAgent<{ issuer.issuerRecord = await issuer.agent.modules.openId4VcIssuer.createIssuer({ issuerId: '726222ad-7624-4f12-b15b-e08aa7042ffa', credentialsSupported, - authorizationServerConfigs: [{ baseUrl: 'http://localhost:3042' }], + // FIXME: should be extraAuthorizationServerConfigs. + authorizationServerConfigs: [ + { + issuer: 'http://localhost:3042', + serverType: 'oidc', + clientId: 'issuer-server', + clientSecret: 'issuer-server', + }, + ], }) return issuer } public async createCredentialOffer(offeredCredentials: string[]) { - const { credentialOffer } = await this.agent.modules.openId4VcIssuer.createCredentialOffer({ + // 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, - authorizationCodeFlowConfig: { issuerState: 'f498b73c-144f-4eea-bd6b-7be89b35936e' }, + // FIXME: wait for PR in OID4VCI repo + // // Pre-auth using our own server + // preAuthorizedCodeFlowConfig: { + // authorizationServerUrl: issuerMetadata.issuerUrl, + // }, + // Auth using external authorization server + authorizationCodeFlowConfig: { + authorizationServerUrl: 'http://localhost:3042', + issuerState: 'f498b73c-144f-4eea-bd6b-7be89b35936e', + }, }) return credentialOffer diff --git a/demo-openid/src/Provider.ts b/demo-openid/src/Provider.ts index ab0ebe1b88..105ea044e5 100644 --- a/demo-openid/src/Provider.ts +++ b/demo-openid/src/Provider.ts @@ -5,12 +5,16 @@ const oidc = new Provider('http://localhost:3042', { clients: [ { client_id: 'foo', - client_secret: 'bar', - redirect_uris: ['http://example.com'], - scope: 'openid', + // client_secret: 'bar', + redirect_uris: ['http://localhost:1234/redirect'], grant_types: ['authorization_code'], }, + { + client_id: 'issuer-server', + client_secret: 'issuer-server', + }, ], + // scopes: ['UniversityDegreeCredential'], pkce: { methods: ['S256'], required: () => { @@ -18,20 +22,44 @@ const oidc = new Provider('http://localhost:3042', { return true }, }, + extraTokenClaims: async (context, token) => { + if (token.kind === 'AccessToken') { + console.log(context.body) + return { + issuer_state: (context.body as Record).issuer_state, + } + } + return undefined + }, features: { dPoP: { enabled: false }, pushedAuthorizationRequests: { enabled: true, //requirePushedAuthorizationRequests: true, }, + resourceIndicators: { + defaultResource: () => 'http://localhost:1234', + enabled: true, + getResourceServerInfo: (ctx, resourceIndicator, client) => { + return { + scope: 'UniversityDegreeCredential', + accessTokenTTL: 5 * 60, // 5 minutes + accessTokenFormat: 'jwt', + } + }, + useGrantedResource: (ctx, model) => { + // @param ctx - koa request context + // @param model - depending on the request's grant_type this can be either an AuthorizationCode, BackchannelAuthenticationRequest, + // RefreshToken, or DeviceCode model instance. + return true + }, + }, }, async findAccount(ctx, id) { - console.log('called findAccount') return { accountId: id, async claims(use, scope) { - console.log('called claims', scope) return { sub: id } }, } @@ -48,7 +76,7 @@ oidc.use(async (ctx, next) => { if (ctx.path.includes('request')) { console.log(ctx.request.body) - ctx.request.body.client_secret = 'bar' + // ctx.request.body.client_secret = 'bar' } if (ctx.path.includes('auth')) { @@ -59,7 +87,7 @@ oidc.use(async (ctx, next) => { if (ctx.path.includes('token')) { console.log('token endpoint') - ctx.request.body.client_secret = 'bar' + // ctx.request.body.client_secret = 'bar' } await next() diff --git a/jest.config.base.ts b/jest.config.base.ts index 5dfdf49c1f..8eab845e74 100644 --- a/jest.config.base.ts +++ b/jest.config.base.ts @@ -1,4 +1,5 @@ import type { Config } from '@jest/types' +import path from 'path' const config: Config.InitialOptions = { preset: 'ts-jest', @@ -9,13 +10,16 @@ const config: Config.InitialOptions = { moduleNameMapper: { '@credo-ts/(.+)': ['/../../packages/$1/src', '/../packages/$1/src', '/packages/$1/src'], }, + // oauth4webapi is an esm module + // transformIgnorePatterns: ['node_modules/.pnpm/(?!oauth4webapi)'], transform: { - '^.+\\.tsx?$': [ + '\\.tsx?$': [ 'ts-jest', { isolatedModules: true, }, ], + // '\\.jsx?$': ['babel-jest', { extends: path.join(__dirname, '.babelrc') }], }, } diff --git a/package.json b/package.json index 1b2f1cd497..03024a2746 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", @@ -45,6 +47,7 @@ "@types/ws": "^8.5.4", "@typescript-eslint/eslint-plugin": "^7.14.1", "@typescript-eslint/parser": "^7.14.1", + "babel-jest": "^29.7.0", "bn.js": "^5.2.1", "cors": "^2.8.5", "eslint": "^8.36.0", @@ -54,17 +57,21 @@ "eslint-plugin-prettier": "^4.2.1", "eslint-plugin-workspaces": "^0.8.0", "express": "^4.17.1", + "http-proxy-middleware": "^3.0.3", "jest": "^29.7.0", "prettier": "^2.3.1", "rxjs": "^7.8.0", + "selfsigned": "^2.4.1", "ts-jest": "^29.1.2", "ts-node": "^10.0.0", "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 64a08420e2..fc597dd07c 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -42,7 +42,7 @@ "@sd-jwt/utils": "^0.7.0", "@sphereon/pex": "5.0.0-unstable.2", "@sphereon/pex-models": "^2.3.1", - "@sphereon/ssi-types": "0.29.1-unstable.121", + "@sphereon/ssi-types": "^0.30.1", "@stablelib/ed25519": "^1.0.2", "@types/ws": "^8.5.4", "abort-controller": "^3.0.0", 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/node/src/transport/HttpInboundTransport.ts b/packages/node/src/transport/HttpInboundTransport.ts index ac86d80cec..11b4aa3fc9 100644 --- a/packages/node/src/transport/HttpInboundTransport.ts +++ b/packages/node/src/transport/HttpInboundTransport.ts @@ -1,6 +1,7 @@ import type { InboundTransport, Agent, TransportSession, EncryptedMessage, AgentContext } from '@credo-ts/core' import type { Express, Request, Response } from 'express' import type { Server } from 'http' +import https from 'https' import { DidCommMimeType, CredoError, TransportService, utils, MessageReceiver } from '@credo-ts/core' import express, { text } from 'express' diff --git a/packages/openid4vc/package.json b/packages/openid4vc/package.json index f1a69d7844..492c68286e 100644 --- a/packages/openid4vc/package.json +++ b/packages/openid4vc/package.json @@ -32,9 +32,12 @@ "@sphereon/oid4vci-client": "0.16.1-next.66", "@sphereon/oid4vci-common": "0.16.1-next.66", "@sphereon/oid4vci-issuer": "0.16.1-next.66", - "@sphereon/ssi-types": "0.29.1-unstable.121", + "@sphereon/ssi-types": "^0.30.1", "class-transformer": "^0.5.1", - "rxjs": "^7.8.0" + "oauth4webapi": "^3.0.1", + "rxjs": "^7.8.0", + "zod": "^3.23.8", + "@animo-id/oid4vci": "0.0.2-alpha-20241024080134" }, "devDependencies": { "@credo-ts/tenants": "workspace:*", diff --git a/packages/openid4vc/src/openid4vc-holder/OpenId4VcHolderApi.ts b/packages/openid4vc/src/openid4vc-holder/OpenId4VcHolderApi.ts index 1a9dd4ecd8..269534b6a8 100644 --- a/packages/openid4vc/src/openid4vc-holder/OpenId4VcHolderApi.ts +++ b/packages/openid4vc/src/openid4vc-holder/OpenId4VcHolderApi.ts @@ -147,8 +147,7 @@ export class OpenId4VcHolderApi { */ public async requestToken(options: OpenId4VciRequestTokenOptions): Promise { const { - access_token: accessToken, - c_nonce: cNonce, + accessTokenResponse: { access_token: accessToken, c_nonce: cNonce }, dpop, } = await this.openId4VciHolderService.requestAccessToken(this.agentContext, options) return { accessToken, cNonce, dpop } @@ -179,6 +178,12 @@ export class OpenId4VcHolderApi { * @param options OpenId4VciSendNotificationOptions */ public async sendNotification(options: OpenId4VciSendNotificationOptions) { - return this.openId4VciHolderService.sendNotification(options) + return this.openId4VciHolderService.sendNotification(this.agentContext, { + accessToken: options.accessToken, + metadata: await this.openId4VciHolderService.resolveIssuerMetadata(this.agentContext, {}), + notificationEvent: options.notificationEvent, + notificationId: options.notificationMetadata.notificationId, + dpop: options.dpop + }) } } diff --git a/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderService.ts b/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderService.ts index 89a8af86b4..6d00cfd091 100644 --- a/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderService.ts +++ b/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderService.ts @@ -11,22 +11,8 @@ import type { OpenId4VciSupportedCredentialFormats, OpenId4VciTokenRequestOptions, } from './OpenId4VciHolderServiceOptions' -import type { - OpenId4VciCredentialConfigurationSupported, - OpenId4VciCredentialSupported, - OpenId4VciIssuerMetadata, -} from '../shared' -import type { AgentContext, JwaSignatureAlgorithm, JwkJson, Key } from '@credo-ts/core' -import type { - AccessTokenResponse, - AuthorizationDetails, - AuthorizationDetailsJwtVcJson, - AuthorizationDetailsJwtVcJsonLdAndLdpVc, - AuthorizationDetailsSdJwtVc, - CredentialResponse, - Jwt, - OpenIDResponse, -} from '@sphereon/oid4vci-common' +import type { OpenId4VciCredentialConfigurationSupported, OpenId4VciIssuerMetadata } from '../shared' +import type { AgentContext, JwaSignatureAlgorithm, Key } from '@credo-ts/core' import { CredoError, @@ -39,7 +25,6 @@ import { Logger, SdJwtVcApi, SignatureSuiteRegistry, - TypedArrayEncoder, W3cCredentialService, W3cJsonLdVerifiableCredential, W3cJwtVerifiableCredential, @@ -52,30 +37,24 @@ import { 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 { getOfferedCredentials } from '../shared/issuerMetadataUtils' +import { getSupportedJwaSignatureAlgorithms } from '../shared/utils' -import { OpenId4VciNotificationMetadata, openId4VciSupportedCredentialFormats } from './OpenId4VciHolderServiceOptions' +import { openId4VciSupportedCredentialFormats } from './OpenId4VciHolderServiceOptions' +import { + AccessTokenResponse, + authorizationCodeGrantIdentifier, + CredentialResponse, + determineAuthorizationServerForOffer, + IssuerMetadataResult, + JwtSigner, + Oid4vciClient, + preAuthorizedCodeGrantIdentifier, + RequestDpopOptions, + SignJwtCallback, +} from '@animo-id/oid4vci' @injectable() export class OpenId4VciHolderService { @@ -93,93 +72,36 @@ export class OpenId4VciHolderService { this.logger = logger } + private getClient(agentContext: AgentContext) { + return new Oid4vciClient({ + callbacks: { + generateRandom: (length) => agentContext.wallet.getRandomValues(length), + hash: (data, alg) => Hasher.hash(data, alg.toLowerCase()), + signJwt: this.jwtSignerCallback(agentContext), + fetch: agentContext.config.agentDependencies.fetch, + }, + }) + } + public async resolveCredentialOffer( 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 client = this.getClient(agentContext) - const metadata = client.endpointMetadata - const credentialIssuerMetadata = metadata.credentialIssuerMetadata as OpenId4VciIssuerMetadata + 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 }) - 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, - }) - - this.logger.debug('Full server metadata', metadata) - - const credentialOfferPayload = client.credentialOffer.credential_offer - - const offeredCredentialsData = isCredentialOfferV1Draft13(credentialOfferPayload) - ? credentialOfferPayload.credential_configuration_ids - : credentialOfferPayload.credentials - - const { credentialConfigurationsSupported, credentialsSupported } = getOfferedCredentials( - agentContext, - offeredCredentialsData, - credentialIssuerMetadata.credentials_supported ?? credentialIssuerMetadata.credential_configurations_supported + const { credentialConfigurationsSupported } = getOfferedCredentials( + credentialOfferObject.credential_configuration_ids, + metadata.credentialIssuer.credential_configurations_supported ) 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 { - throw new CredoError(`Cannot create authorization_details. Unsupported credential format '${format}'.`) + credentialOfferPayload: credentialOfferObject, } } @@ -188,183 +110,179 @@ 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 authDetailsLocation = metadata.credentialIssuerMetadata.authorization_server - const authDetails = offeredCredentials - .map((credential) => this.getAuthDetailsFromOfferedCredential(credential, authDetailsLocation)) - .filter((authDetail): authDetail is AuthorizationDetails => authDetail !== undefined) - + // TODO: add support for scope based on metadata const { clientId, redirectUri, scope } = authCodeFlowOptions + const { metadata, credentialOfferPayload } = resolvedCredentialOffer - 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, - } + const client = this.getClient(agentContext) - const client = - resolvedCredentialOffer.version === OpenId4VCIVersion.VER_1_0_11 - ? await OpenID4VCIClientV1_0_11.fromState(vciClientState) - : await OpenID4VCIClientV1_0_13.fromState(vciClientState) + const authorizationServerMetadata = determineAuthorizationServerForOffer({ + credentialOffer: credentialOfferPayload, + grantType: authorizationCodeGrantIdentifier, + issuerMetadata: metadata, + }) - const authorizationRequestUri = await client.createAuthorizationRequestUrl({ - authorizationRequest: { - redirectUri, - scope: scope ? scope.join(' ') : undefined, - authorizationDetails: authDetails, - parMode: PARMode.AUTO, - clientId, - }, + const { authorizationRequestUrl, pkce, authorizationServer } = await client.createAuthorizationRequestUrl({ + authorizationServer: authorizationServerMetadata.issuer, + clientId, + issuerMetadata: metadata, + credentialOffer: credentialOfferPayload, + scope: scope?.join(' '), + redirectUri, }) return { ...authCodeFlowOptions, - codeVerifier, - authorizationRequestUri, + authorizationServer, + codeVerifier: pkce?.codeVerifier, + authorizationRequestUri: authorizationRequestUrl, } } - 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 (!response.successBody) { - throw new CredoError(`Failed to send notification event '${notificationId}' to '${notificationEndpoint}'`) - } + public async resolveIssuerMetadata(agentContext: AgentContext, { credentialIssuer }: { credentialIssuer: string }) { + return this.getClient(agentContext).resolveIssuerMetadata(credentialIssuer) } - 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) + return { + signer: { + method: 'jwk', + alg, + publicJwk: jwk.toJson(), + }, + nonce, + } } - const createDPoPOpts: CreateDPoPClientOpts = { - jwtIssuer: { alg: alg as unknown as SigningAlgo, jwk: jwk.toJson() }, - dPoPSigningAlgValuesSupported: dpopSigningAlgValuesSupported, - jwtPayloadProps: resourceRequestOptions?.jwtPayloadProps ?? {}, - createJwtCallback: getCreateJwtCallback(agentContext), + const alg = dpopSigningAlgValuesSupported.find((alg) => getJwkClassFromJwaSignatureAlgorithm(alg)) + const JwkClass = alg ? getJwkClassFromJwaSignatureAlgorithm(alg) : undefined + + if (!alg || !JwkClass) { + throw new CredoError( + `No supported dpop signature algorithms found in dpop_signing_alg_values_supported '${dpopSigningAlgValuesSupported.join( + ', ' + )}'` + ) + } + + const key = await agentContext.wallet.createKey({ keyType: JwkClass.keyType }) + return { + signer: { + method: 'jwk', + alg, + publicJwk: getJwkFromKey(key).toJson(), + }, + nonce, } - return createDPoPOpts } public async requestAccessToken(agentContext: AgentContext, options: OpenId4VciTokenRequestOptions) { const { resolvedCredentialOffer, txCode, resolvedAuthorizationRequest, code } = options - const { metadata, credentialOfferRequestWithBaseUrl } = resolvedCredentialOffer + const { metadata, credentialOfferPayload } = resolvedCredentialOffer - // acquire the access token - let accessTokenResponse: OpenIDResponse + const client = this.getClient(agentContext) - const accessTokenClient = new AccessTokenClient() - - const createDPoPOpts = await this.getCreateDpopOptions(agentContext, metadata) + const authorizationServerMetadata = determineAuthorizationServerForOffer({ + credentialOffer: credentialOfferPayload, + grantType: resolvedAuthorizationRequest ? authorizationCodeGrantIdentifier : preAuthorizedCodeGrantIdentifier, + issuerMetadata: metadata, + }) + const isDpopSupported = client.isDpopSupported({ + authorizationServer: authorizationServerMetadata.issuer, + issuerMetadata: metadata, + }) + const dpop = isDpopSupported.supported + ? await this.getDpopOptions(agentContext, { + dpopSigningAlgValuesSupported: isDpopSupported.dpopSigningAlgValuesSupported, + }) + : undefined - 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, + const result = await client.retrieveAuthorizationCodeAccessToken({ + issuerMetadata: metadata, + authorizationCode: code, + authorizationServer: authorizationServerMetadata.issuer, + dpop, + pkceCodeVerifier: codeVerifier, redirectUri, - createDPoPOpts, - additionalParams: { - client_id: resolvedAuthorizationRequest.clientId, - }, }) + + return { + ...result, + dpop: dpop + ? { + ...result.dpop, + alg: dpop.signer.alg as JwaSignatureAlgorithm, + jwk: getJwkFromJson(dpop.signer.publicJwk), + } + : undefined, + } } else { - accessTokenResponse = await accessTokenClient.acquireAccessToken({ - metadata: metadata, - credentialOffer: { credential_offer: credentialOfferRequestWithBaseUrl.credential_offer }, - pin: txCode, - createDPoPOpts, + const result = await client.retrievePreAuthorizedCodeAccessToken({ + credentialOffer: resolvedCredentialOffer.credentialOfferPayload, + issuerMetadata: metadata, + dpop, + txCode, }) - } - - if (!accessTokenResponse.successBody) { - throw new CredoError( - `could not acquire access token from '${metadata.issuer}'. ${accessTokenResponse.errorBody?.error}: ${accessTokenResponse.errorBody?.error_description}` - ) - } - this.logger.debug('Requested OpenId4VCI Access Token.') - - return { - ...accessTokenResponse.successBody, - ...(dpopJwk && { dpop: { jwk: dpopJwk, nonce: accessTokenResponse.params?.dpop?.dpopNonce } }), + return { + ...result, + dpop: dpop + ? { + ...result.dpop, + alg: dpop.signer.alg as JwaSignatureAlgorithm, + jwk: getJwkFromJson(dpop.signer.publicJwk), + } + : undefined, + } } } @@ -376,15 +294,16 @@ export class OpenId4VciHolderService { resolvedAuthorizationRequestWithCode?: OpenId4VciResolvedAuthorizationRequestWithCode accessToken?: string cNonce?: string - dpop?: { jwk: Jwk; nonce?: string } + dpop?: { jwk: Jwk; alg: JwaSignatureAlgorithm; nonce?: string } clientId?: string } ) { const { resolvedCredentialOffer, acceptCredentialOfferOptions } = options - const { metadata, version, offeredCredentialConfigurations } = resolvedCredentialOffer - + const { metadata, offeredCredentialConfigurations } = resolvedCredentialOffer const { credentialsToRequest, credentialBindingResolver, verifyCredentialStatus } = acceptCredentialOfferOptions + const client = this.getClient(agentContext) + if (credentialsToRequest?.length === 0) { this.logger.warn(`Accepting 0 credential offers. Returning`) return [] @@ -419,15 +338,12 @@ export class OpenId4VciHolderService { } as OpenId4VciTokenRequestOptions const tokenResponse = options.accessToken - ? { - access_token: options.accessToken, - c_nonce: options.cNonce, - dpop: options.dpop, - } + ? { accessTokenResponse: { 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 = tokenResponse.accessTokenResponse.c_nonce + let dpopNonce = tokenResponse.dpop?.nonce const credentialConfigurationToRequest = credentialsToRequest?.map((id) => { @@ -451,71 +367,58 @@ export class OpenId4VciHolderService { credentialBindingResolver, }) - // Create the proof of possession - const proofOfPossessionBuilder = ProofOfPossessionBuilder.fromAccessTokenResponse({ - accessTokenResponse: tokenResponse, - callbacks: { signCallback: this.proofOfPossessionSignCallback(agentContext) }, - version, + let jwtSigner: JwtSigner = + credentialBinding.method === 'did' + ? { + method: credentialBinding.method, + didUrl: credentialBinding.didUrl, + alg: signatureAlgorithm, + } + : { + method: 'jwk', + publicJwk: credentialBinding.jwk.toJson(), + alg: signatureAlgorithm, + } + + const { jwt } = await client.createCredentialRequestJwtProof({ + credentialConfigurationId: offeredCredentialId, + issuerMetadata: resolvedCredentialOffer.metadata, + signer: jwtSigner, + clientId: options.clientId, + nonce: cNonce, }) - .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()) - } - // 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() - - 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, + this.logger.debug('Generated credential request proof of possesion jwt', { jwt }) + + const { credentialResponse, dpop } = await client.retrieveCredentials({ + issuerMetadata: metadata, + accessToken: tokenResponse.accessTokenResponse.access_token, + credentialConfigurationId: offeredCredentialId, + dpop: tokenResponse.dpop + ? await this.getDpopOptions(agentContext, { + ...tokenResponse.dpop, + nonce: dpopNonce, + dpopSigningAlgValuesSupported: [tokenResponse.dpop.alg], + }) + : undefined, + proof: { + proof_type: 'jwt', + jwt, + }, }) - 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, }) - this.logger.debug('Full credential', credential) + this.logger.debug('received credential', credential) receivedCredentials.push(credential) } @@ -701,7 +604,7 @@ export class OpenId4VciHolderService { private async handleCredentialResponse( agentContext: AgentContext, - credentialResponse: OpenIDResponse, + credentialResponse: CredentialResponse, options: { verifyCredentialStatus: boolean credentialIssuerMetadata: OpenId4VciIssuerMetadata @@ -712,32 +615,38 @@ export class OpenId4VciHolderService { this.logger.debug('Credential request response', credentialResponse) - if (!credentialResponse.successBody || !credentialResponse.successBody.credential) { + if (credentialResponse.credentials) { + throw new CredoError( + `Credential response returned 'credentials' parameter in credential response which is not supported yet.` + ) + } + + if (!credentialResponse.credential) { throw new CredoError( - `Did not receive a successful credential response. ${credentialResponse.errorBody?.error}: ${credentialResponse.errorBody?.error_description}` + `Did not receive a successful credential response. Missing 'credential' parameter in credential response.` ) } const notificationMetadata = - credentialIssuerMetadata.notification_endpoint && credentialResponse.successBody.notification_id + credentialIssuerMetadata.notification_endpoint && credentialResponse.notification_id ? { notificationEndpoint: credentialIssuerMetadata.notification_endpoint, - notificationId: credentialResponse.successBody.notification_id, + notificationId: credentialResponse.notification_id, } : undefined const format = options.format if (format === OpenId4VciCredentialFormatProfile.SdJwtVc) { - if (typeof credentialResponse.successBody.credential !== 'string') + if (typeof credentialResponse.credential !== 'string') throw new CredoError( - `Received a credential of format ${ - OpenId4VciCredentialFormatProfile.SdJwtVc - }, but the credential is not a string. ${JSON.stringify(credentialResponse.successBody.credential)}` + `Received a credential of format ${format}, but the credential is not a string. ${JSON.stringify( + credentialResponse.credential + )}` ) const sdJwtVcApi = agentContext.dependencyManager.resolve(SdJwtVcApi) const verificationResult = await sdJwtVcApi.verify({ - compactSdJwtVc: credentialResponse.successBody.credential, + compactSdJwtVc: credentialResponse.credential, }) if (!verificationResult.isValid) { @@ -750,9 +659,14 @@ export class OpenId4VciHolderService { format === OpenId4VciCredentialFormatProfile.JwtVcJson || format === OpenId4VciCredentialFormatProfile.JwtVcJsonLd ) { - const credential = W3cJwtVerifiableCredential.fromSerializedJwt( - credentialResponse.successBody.credential as string - ) + if (typeof credentialResponse.credential !== 'string') + throw new CredoError( + `Received a credential of format ${format}, but the credential is not a string. ${JSON.stringify( + credentialResponse.credential + )}` + ) + + const credential = W3cJwtVerifiableCredential.fromSerializedJwt(credentialResponse.credential) const result = await this.w3cCredentialService.verifyCredential(agentContext, { credential, verifyCredentialStatus, @@ -764,9 +678,14 @@ export class OpenId4VciHolderService { return { credential, notificationMetadata } } else if (format === OpenId4VciCredentialFormatProfile.LdpVc) { - const credential = W3cJsonLdVerifiableCredential.fromJson( - credentialResponse.successBody.credential as Record - ) + if (typeof credentialResponse.credential === 'string') + throw new CredoError( + `Received a credential of format ${format}, but the credential is not an object. ${JSON.stringify( + credentialResponse.credential + )}` + ) + + const credential = W3cJsonLdVerifiableCredential.fromJson(credentialResponse.credential) const result = await this.w3cCredentialService.verifyCredential(agentContext, { credential, verifyCredentialStatus, @@ -779,61 +698,43 @@ export class OpenId4VciHolderService { return { credential, notificationMetadata } } - throw new CredoError(`Unsupported credential format ${credentialResponse.successBody.format}`) + throw new CredoError(`Unsupported credential 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') + private jwtSignerCallback(agentContext: AgentContext) { + const callback: SignJwtCallback = async (signer, { header, payload }) => { + if (signer.method === 'custom' || signer.method === 'x5c') { + throw new CredoError(`Jwt signer method 'custom' and 'x5c' are not supported for jwt signer.`) } 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.` - ) - } - + if (signer.method === 'did') { const didsApi = agentContext.dependencyManager.resolve(DidsApi) - const didDocument = await didsApi.resolveDidDocument(kid) - const verificationMethod = didDocument.dereferenceKey(kid, ['authentication']) - + const didDocument = await didsApi.resolveDidDocument(signer.didUrl) + const verificationMethod = didDocument.dereferenceKey(signer.didUrl, ['authentication']) 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') + key = getJwkFromJson(signer.publicJwk).key } 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 (!jwk.supportsSignatureAlgorithm(signer.alg)) { + throw new CredoError(`key type '${jwk.keyType}', does not support the JWS signature alg '${signer.alg}'`) } - // 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') - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { x5c: _x5c, ...supportedHeaderOptions } = jwt.header - const jws = await this.jwsService.createJwsCompact(agentContext, { key, - payload: JsonEncoder.toBuffer(jwt.payload), + payload: JsonEncoder.toBuffer(payload), protectedHeaderOptions: { - ...supportedHeaderOptions, - // only pass jwk if it was present in the header - jwk: jwt.header.jwk ? jwk : undefined, + ...header, + // only pass jwk if signer method is jwk + jwk: signer.method === 'jwk' ? jwk : undefined, }, }) return jws } + return callback } } diff --git a/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderServiceOptions.ts b/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderServiceOptions.ts index 0bd7ad0e8d..c2f19d994f 100644 --- a/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderServiceOptions.ts +++ b/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderServiceOptions.ts @@ -1,20 +1,14 @@ import type { OpenId4VcCredentialHolderBinding, - OpenId4VciCredentialSupportedWithId, - OpenId4VciIssuerMetadata, - OpenId4VciCredentialOfferPayload, - OpenId4VciCredentialConfigurationsSupported, } 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 { OpenId4VciCredentialFormatProfile } from '../shared/models/OpenId4VciCredentialFormatProfile' +import { CredentialConfigurationSupported, CredentialIssuerMetadata, CredentialOfferObject, IssuerMetadataResult } from '@animo-id/oid4vci' export type OpenId4VciSupportedCredentialFormats = | OpenId4VciCredentialFormatProfile.JwtVcJson @@ -46,7 +40,7 @@ export type OpenId4VciTokenResponse = Pick & { - credentialIssuerMetadata: OpenId4VciIssuerMetadata - } - credentialOfferRequestWithBaseUrl: CredentialOfferRequestWithBaseUrl - credentialOfferPayload: OpenId4VciCredentialOfferPayload - offeredCredentials: OpenId4VciCredentialSupportedWithId[] - offeredCredentialConfigurations: OpenId4VciCredentialConfigurationsSupported - version: OpenId4VCIVersion + metadata: IssuerMetadataResult + credentialOfferPayload: CredentialOfferObject + + /** + * Uses the flexible type meaning it can include any format string. + * You can cast it to `CredentialConfigurationSupportedWithFormat` for only supported formats. + */ + offeredCredentialConfigurations: Record } export interface OpenId4VciResolvedAuthorizationRequest extends OpenId4VciAuthCodeFlowOptions { - codeVerifier: string + /** + * The authorization server used for creating the authorization request url + */ + authorizationServer: string + + codeVerifier?: string authorizationRequestUri: string } @@ -93,6 +92,8 @@ export interface OpenId4VciSendNotificationOptions { * 'credential_failure' otherwise. */ notificationEvent: OpenId4VciNotificationEvent + + dpop?: { jwk: Jwk; alg: JwaSignatureAlgorithm; nonce?: string } } interface OpenId4VcTokenRequestBaseOptions { @@ -118,7 +119,7 @@ export interface OpenId4VciCredentialRequestOptions extends Omit { 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: any) => { 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/OpenId4VcIssuerService.ts b/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerService.ts index 60f0b07e92..5cbd9c004e 100644 --- a/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerService.ts +++ b/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerService.ts @@ -16,6 +16,7 @@ import type { OpenId4VciCredentialRequest, } from '../shared' import type { AgentContext, DidDocument, Query, QueryOptions } from '@credo-ts/core' +import { PRE_AUTH_GRANT_LITERAL } from '@sphereon/oid4vci-common' import type { CredentialOfferPayloadV1_0_11, CredentialOfferPayloadV1_0_13, @@ -108,34 +109,9 @@ export class OpenId4VcIssuerService { const vcIssuer = this.getVcIssuer(agentContext, issuer) - if ( - preAuthorizedCodeFlowConfig && - preAuthorizedCodeFlowConfig.userPinRequired === false && - preAuthorizedCodeFlowConfig.txCode - ) { - throw new CredoError('The userPinRequired option must be set to true when using txCode.') - } - - if ( - preAuthorizedCodeFlowConfig && - preAuthorizedCodeFlowConfig.userPinRequired && - !preAuthorizedCodeFlowConfig.txCode - ) { - preAuthorizedCodeFlowConfig.txCode = {} - } - - if ( - preAuthorizedCodeFlowConfig && - preAuthorizedCodeFlowConfig.txCode && - !preAuthorizedCodeFlowConfig.userPinRequired - ) { - preAuthorizedCodeFlowConfig.userPinRequired = true - } - // 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 ) @@ -156,28 +132,30 @@ export class OpenId4VcIssuerService { // TODO: The wallet MUST use that value in the scope Authorization parameter. // TODO: add support for scope in the credential offer in sphereon-oid4vci const issuerMetadata = this.getIssuerMetadata(agentContext, issuer) + this.verifyGrantAuthorizationServers(issuerMetadata, preAuthorizedCodeFlowConfig, authorizationCodeFlowConfig) - if ( - authorizationCodeFlowConfig && - (issuerMetadata.authorizationServers?.length ?? 0 > 1) && - authorizationCodeFlowConfig?.authorizationServerUrl - ) { - throw new CredoError( - 'The authorization code flow requires an explicit authorization server url, if multiple authorization servers are present.' - ) - } - + const hasMultipleAuthorizationServers = issuerMetadata.authorizationServers?.length ?? 0 > 1 const grants = await this.getGrantsFromConfig(agentContext, { - preAuthorizedCodeFlowConfig, + preAuthorizedCodeFlowConfig: preAuthorizedCodeFlowConfig + ? { + ...preAuthorizedCodeFlowConfig, + + // FIXME: this is removed from the offer. Need to wait for + // https://github.com/Sphereon-Opensource/OID4VC/pull/159 + // Must only be used if multiple authorization servers are present + authorizationServerUrl: hasMultipleAuthorizationServers + ? preAuthorizedCodeFlowConfig?.authorizationServerUrl + : undefined, + } + : undefined, authorizationCodeFlowConfig: authorizationCodeFlowConfig ? { ...authorizationCodeFlowConfig, // Must only be used if multiple authorization servers are present - authorizationServerUrl: - issuerMetadata.authorizationServers?.length ?? 0 > 1 - ? authorizationCodeFlowConfig?.authorizationServerUrl - : undefined, + authorizationServerUrl: hasMultipleAuthorizationServers + ? authorizationCodeFlowConfig?.authorizationServerUrl + : undefined, } : undefined, }) @@ -189,7 +167,7 @@ export class OpenId4VcIssuerService { credentialOfferUri: hostedCredentialOfferUri, baseUri: options.baseUri, credentialDataSupplierInput: options.issuanceMetadata, - pinLength: grants['urn:ietf:params:oauth:grant-type:pre-authorized_code']?.tx_code?.length, + pinLength: grants[PRE_AUTH_GRANT_LITERAL]?.tx_code?.length, }) // FIXME: https://github.com/Sphereon-Opensource/OID4VCI/issues/102 @@ -202,17 +180,15 @@ export class OpenId4VcIssuerService { credentialOfferUri: hostedCredentialOfferUri, }) + // TODO: is isuer_state stored? + // issuanceSession.issuerState = grants.authorization_code?.issuer_state + 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) @@ -412,16 +388,16 @@ export class OpenId4VcIssuerService { const authorizationServers = issuerRecord.authorizationServerConfigs && issuerRecord.authorizationServerConfigs.length > 0 - ? issuerRecord.authorizationServerConfigs.map((authorizationServer) => authorizationServer.baseUrl) + ? [ + ...issuerRecord.authorizationServerConfigs.map((authorizationServer) => authorizationServer.issuer), + // Our issuer is also a valid authorization server (only for pre-auth) + issuerUrl, + ] : undefined - const tokenEndpoint = authorizationServers - ? undefined - : joinUriParts(issuerUrl, [config.accessTokenEndpoint.endpointPath]) - const issuerMetadata = { issuerUrl, - tokenEndpoint, + tokenEndpoint: joinUriParts(issuerUrl, [config.accessTokenEndpoint.endpointPath]), credentialEndpoint: joinUriParts(issuerUrl, [config.credentialEndpoint.endpointPath]), credentialsSupported: issuerRecord.credentialsSupported, credentialConfigurationsSupported: @@ -491,9 +467,6 @@ export class OpenId4VcIssuerService { if (issuerMetadata.authorizationServers) { builder.withAuthorizationServers(issuerMetadata.authorizationServers) } else { - if (!issuerMetadata.tokenEndpoint) { - throw new CredoError('Missing required token endpoint. No authorization server is set.') - } builder.withTokenEndpoint(issuerMetadata.tokenEndpoint) } @@ -504,6 +477,61 @@ export class OpenId4VcIssuerService { return builder.build() } + private verifyGrantAuthorizationServers( + issuerMetadata: OpenId4VcIssuerMetadata, + preAuthorizedCodeFlowConfig?: OpenId4VciPreAuthorizedCodeFlowConfig, + authorizationCodeFlowConfig?: OpenId4VciAuthorizationCodeFlowConfig + ) { + const hasMultipleAuthorizationServers = issuerMetadata.authorizationServers?.length ?? 0 > 1 + if ( + authorizationCodeFlowConfig && + hasMultipleAuthorizationServers && + !authorizationCodeFlowConfig.authorizationServerUrl + ) { + throw new CredoError( + 'The authorization code flow requires an explicit authorization server url if multiple authorization servers are present in the credential issuer metadata.' + ) + } + + if ( + issuerMetadata.authorizationServers && + authorizationCodeFlowConfig?.authorizationServerUrl && + !issuerMetadata.authorizationServers.includes(authorizationCodeFlowConfig.authorizationServerUrl) + ) { + throw new CredoError( + `The authorizationServerlUrl '${ + authorizationCodeFlowConfig.authorizationServerUrl + }' in authorization code flow config is not present in issuer metadata authorization_servers. Allowed values are ${issuerMetadata.authorizationServers.join( + ', ' + )}` + ) + } + + if ( + issuerMetadata.authorizationServers && + preAuthorizedCodeFlowConfig?.authorizationServerUrl && + !issuerMetadata.authorizationServers.includes(preAuthorizedCodeFlowConfig.authorizationServerUrl) + ) { + throw new CredoError( + `The authorizationServerlUrl '${ + preAuthorizedCodeFlowConfig.authorizationServerUrl + }' in pre authorized code flow config is not present in issuer metadata authorization_servers. Allowed values are ${issuerMetadata.authorizationServers.join( + ', ' + )}` + ) + } + + if ( + preAuthorizedCodeFlowConfig && + hasMultipleAuthorizationServers && + !preAuthorizedCodeFlowConfig.authorizationServerUrl + ) { + throw new CredoError( + 'The pre authorized code flow requires an explicit authorization server url if multiple authorization servers are present in the credential issuer metadata.' + ) + } + } + private async getGrantsFromConfig( agentContext: AgentContext, config: { @@ -513,23 +541,31 @@ export class OpenId4VcIssuerService { ) { const { preAuthorizedCodeFlowConfig, authorizationCodeFlowConfig } = config - const grants: Grant = { - ...(preAuthorizedCodeFlowConfig && { - '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, - }, - }), + const grants: Grant = {} - ...(authorizationCodeFlowConfig && { - authorization_code: { - issuer_state: authorizationCodeFlowConfig.issuerState, - authorization_server: authorizationCodeFlowConfig.authorizationServerUrl, - }, - }), + // Pre auth + if (preAuthorizedCodeFlowConfig) { + const { userPinRequired, txCode, authorizationServerUrl, preAuthorizedCode } = preAuthorizedCodeFlowConfig + + if (userPinRequired === false && txCode) { + throw new CredoError('The userPinRequired option must be set to true when using txCode.') + } + + grants[PRE_AUTH_GRANT_LITERAL] = { + 'pre-authorized_code': preAuthorizedCode ?? (await agentContext.wallet.generateNonce()), + tx_code: txCode ?? (userPinRequired ? {} : undefined), + authorization_server: authorizationServerUrl, + // v11 only + user_pin_required: userPinRequired ?? txCode !== undefined, + } + } + + // Auth + if (authorizationCodeFlowConfig) { + grants.authorization_code = { + issuer_state: authorizationCodeFlowConfig.issuerState ?? (await agentContext.wallet.generateNonce()), + authorization_server: authorizationCodeFlowConfig.authorizationServerUrl, + } } return grants @@ -547,8 +583,7 @@ export class OpenId4VcIssuerService { : credentialOffer.credentials const { credentialConfigurationsSupported: offeredCredentialConfigurations } = getOfferedCredentials( - agentContext, - offeredCredentialsData, + offeredCredentialsData as string[], allCredentialConfigurationsSupported ) diff --git a/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerServiceOptions.ts b/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerServiceOptions.ts index 65730cd13c..c0286293ac 100644 --- a/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerServiceOptions.ts +++ b/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerServiceOptions.ts @@ -25,22 +25,29 @@ import type { 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 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 @@ -52,7 +59,7 @@ export type OpenId4VcIssuerMetadata = { // The Credential Issuer's identifier. (URL using the https scheme) issuerUrl: string credentialEndpoint: string - tokenEndpoint?: string + tokenEndpoint: string authorizationServers?: string[] issuerDisplay?: OpenId4VciIssuerMetadataDisplay[] @@ -74,7 +81,6 @@ export interface OpenId4VciCreateCredentialOfferOptions { baseUri?: string preAuthorizedCodeFlowConfig?: OpenId4VciPreAuthorizedCodeFlowConfig - authorizationCodeFlowConfig?: OpenId4VciAuthorizationCodeFlowConfig /** diff --git a/packages/openid4vc/src/openid4vc-issuer/authorization/clientAuth.ts b/packages/openid4vc/src/openid4vc-issuer/authorization/clientAuth.ts new file mode 100644 index 0000000000..5065249af9 --- /dev/null +++ b/packages/openid4vc/src/openid4vc-issuer/authorization/clientAuth.ts @@ -0,0 +1,82 @@ +import { CredoError } from '@credo-ts/core' +import { importOauth4webapi, type oauth } from '../oauth4webapi' + +// These two are well-supported and easy to implement +export enum SupportedClientAuthenticationMethod { + ClientSecretBasic = 'client_secret_basic', + ClientSecretPost = 'client_secret_post', +} + +const supportedClientAuthenticationMethodValues = Object.values(SupportedClientAuthenticationMethod) + +export type ClientAuthEndpointType = 'introspection' | 'token' + +function getSupportedClientAuthMethod( + authorizationServer: oauth.AuthorizationServer, + endpointType: ClientAuthEndpointType +): SupportedClientAuthenticationMethod { + if (endpointType === 'introspection' && authorizationServer.introspection_endpoint_auth_methods_supported) { + const supportedMethod = authorizationServer.introspection_endpoint_auth_methods_supported.find( + (m): m is SupportedClientAuthenticationMethod => + Object.values(SupportedClientAuthenticationMethod).includes(m as SupportedClientAuthenticationMethod) + ) + + if (!supportedMethod) { + throw new CredoError( + `Authorization server metadata for issuer '${ + authorizationServer.issuer + }' has 'introspection_endpoint_auth_methods_supported' metadata, but does not contain a supported value. Supported values by Credo are '${supportedClientAuthenticationMethodValues.join( + ', ' + )}', found values are '${authorizationServer.introspection_endpoint_auth_methods_supported.join(', ')}'` + ) + } + + return supportedMethod + } + + // We allow the introspection endpoint to fallback on the token endpoint metadata if the introspection + // metadata is not defined + if (authorizationServer.token_endpoint_auth_methods_supported) { + const supportedMethod = authorizationServer.token_endpoint_auth_methods_supported.find( + (m): m is SupportedClientAuthenticationMethod => + Object.values(SupportedClientAuthenticationMethod).includes(m as SupportedClientAuthenticationMethod) + ) + + if (!supportedMethod) { + throw new CredoError( + `Authorization server metadata for issuer '${ + authorizationServer.issuer + }' has 'token_endpoint_auth_methods_supported' metadata, but does not contain a supported value. Supported values by Credo are '${supportedClientAuthenticationMethodValues.join( + ', ' + )}', found values are '${authorizationServer.token_endpoint_auth_methods_supported.join(', ')}'` + ) + } + + return supportedMethod + } + + // If omitted from metadata, the default is "client_secret_basic" according to rfc8414 + return SupportedClientAuthenticationMethod.ClientSecretBasic +} + +export async function getClientAuth( + authorizationServer: oauth.AuthorizationServer, + { clientSecret, endpointType }: { clientSecret: string; endpointType: ClientAuthEndpointType } +): Promise { + const oauth = await importOauth4webapi() + const method = getSupportedClientAuthMethod(authorizationServer, endpointType) + + if (method === SupportedClientAuthenticationMethod.ClientSecretBasic) { + return oauth.ClientSecretBasic(clientSecret) + } + + if (method === SupportedClientAuthenticationMethod.ClientSecretPost) { + return oauth.ClientSecretPost(clientSecret) + } + + throw new CredoError( + `Unsupported client auth method ${method}. Supported values are ${Object.values( + SupportedClientAuthenticationMethod + ).join(', ')}` + ) +} diff --git a/packages/openid4vc/src/openid4vc-issuer/authorization/discover.ts b/packages/openid4vc/src/openid4vc-issuer/authorization/discover.ts new file mode 100644 index 0000000000..b6b1e4fc70 --- /dev/null +++ b/packages/openid4vc/src/openid4vc-issuer/authorization/discover.ts @@ -0,0 +1,18 @@ +import { importOauth4webapi } from '../oauth4webapi' + +interface DiscoverAuthorizationRequestMetadataOptions { + serverType: 'oidc' | 'oauth2' +} + +export async function discoverAuthorizationRequestMetadata( + issuer: string, + { serverType }: DiscoverAuthorizationRequestMetadataOptions +) { + const oauth = await importOauth4webapi() + + const as = await oauth + .discoveryRequest(new URL(issuer), { algorithm: serverType }) + .then((response) => oauth.processDiscoveryResponse(new URL(issuer), response)) + + return as +} diff --git a/packages/openid4vc/src/openid4vc-issuer/authorization/dpop.ts b/packages/openid4vc/src/openid4vc-issuer/authorization/dpop.ts new file mode 100644 index 0000000000..bf429baa95 --- /dev/null +++ b/packages/openid4vc/src/openid4vc-issuer/authorization/dpop.ts @@ -0,0 +1,30 @@ +import type { OpenId4VcIssuerRecord } from '../repository' +import type { OpenId4VcIssuanceRequest } from '../router' +import type { AgentContext } from '@credo-ts/core' +import type { SigningAlgo } from '@sphereon/oid4vc-common' + +import { joinUriParts } 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 verifyResourceRequestDpop( + agentContext: AgentContext, + issuer: OpenId4VcIssuerRecord, + request: OpenId4VcIssuanceRequest +) { + const openId4VcIssuerService = agentContext.dependencyManager.resolve(OpenId4VcIssuerService) + const issuerConfig = agentContext.dependencyManager.resolve(OpenId4VcIssuerModuleConfig) + const issuerMetadata = openId4VcIssuerService.getIssuerMetadata(agentContext, issuer) + + 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, + } + ) +} diff --git a/packages/openid4vc/src/openid4vc-issuer/authorization/externalAuthorization.ts b/packages/openid4vc/src/openid4vc-issuer/authorization/externalAuthorization.ts new file mode 100644 index 0000000000..35dab1497a --- /dev/null +++ b/packages/openid4vc/src/openid4vc-issuer/authorization/externalAuthorization.ts @@ -0,0 +1,131 @@ +import type { OpenId4VciAuthorizationServerConfig } from '../../shared' +import type { OpenId4VcIssuerRecord } from '../repository' +import type { OpenId4VcIssuanceRequest } from '../router' +import type { AgentContext, JwtPayloadJson } from '@credo-ts/core' + +import { Jwt, CredoError } from '@credo-ts/core' + +import { OpenId4VcIssuerService } from '../OpenId4VcIssuerService' + +import { discoverAuthorizationRequestMetadata } from './discover' +import { introspectToken } from './introspect' +import { importOauth4webapi, type oauth } from '../oauth4webapi' + +export async function verifyExternalAccessToken( + agentContext: AgentContext, + issuer: OpenId4VcIssuerRecord, + request: OpenId4VcIssuanceRequest, + accessToken: Jwt | string +) { + const oauth = await importOauth4webapi() + const openId4VcIssuerService = agentContext.dependencyManager.resolve(OpenId4VcIssuerService) + const issuerMetadata = openId4VcIssuerService.getIssuerMetadata(agentContext, issuer) + + let authorizationServer: OpenId4VciAuthorizationServerConfig + let tokenPayload: JwtPayloadJson | oauth.IntrospectionResponse + + if (!issuer.authorizationServerConfigs || issuer.authorizationServerConfigs.length === 0) { + throw new CredoError(`No external authorization servers configured on issuer '${issuerMetadata.issuerUrl}'`) + } + + if (accessToken instanceof Jwt) { + if (!accessToken.payload.iss) { + throw new CredoError("Missing 'iss' parameter in JWT access token.") + } + + const _authorizationServer = issuer.authorizationServerConfigs?.find( + (config) => config.issuer === accessToken.payload.iss + ) + + if (!_authorizationServer) { + throw new CredoError( + `Authorization server '${accessToken.payload.iss}' is not configured for issuer ${issuerMetadata.issuerUrl}` + ) + } + + const authorizationServerMetadata = await discoverAuthorizationRequestMetadata(_authorizationServer.issuer, { + serverType: _authorizationServer.serverType, + }) + + // This fetches the jwks_uri and uses that to verify the jwt access token. + // TODO: support dpop nonce + await oauth.validateJwtAccessToken( + authorizationServerMetadata, + new Request(request.originalUrl, { + headers: request.headers as Record, + method: request.method, + body: request.body, + }), + authorizationServerMetadata.issuer + ) + + authorizationServer = _authorizationServer + tokenPayload = accessToken.payload.toJson() + } + // JWT is of type string + else { + let _authorizationServer: OpenId4VciAuthorizationServerConfig | undefined = undefined + let _tokenPayload: oauth.IntrospectionResponse | undefined = undefined + + for (const authorizationServerConfig of issuer.authorizationServerConfigs) { + try { + if (!authorizationServerConfig.clientId || !authorizationServerConfig.clientSecret) { + throw new CredoError( + `Missing required clientId and clientSecret for authorization server '${authorizationServerConfig.issuer}' in issuer ${issuer.issuerId}. clientId and clientSecret are required for token introspection when using opaque tokens from external authorization servers.` + ) + } + + // TODO: store server type in authorization server config + const authorizationServerMetadata = await discoverAuthorizationRequestMetadata( + authorizationServerConfig.issuer, + { + serverType: 'oauth2', + } + ) + + const introspectionResponse = await introspectToken({ + authorizationServer: authorizationServerMetadata, + clientId: authorizationServerConfig.clientId, + clientSecret: authorizationServerConfig.clientSecret, + token: accessToken, + }) + + if (!introspectionResponse.active) { + throw new CredoError('Access token is not active') + } + + // TODO: support dpop verification + if (introspectionResponse.token_type === 'DPoP') { + throw new CredoError( + 'Access token with introspection is using DPoP which is not supported for opaque access tokens. DPoP is only supported for JWT access tokens.' + ) + } + + _authorizationServer = authorizationServerConfig + _tokenPayload = introspectionResponse + break + } catch (error) { + continue + } + } + + if (!_authorizationServer || !_tokenPayload) { + throw new CredoError( + 'Unable to verify opaque access token using introspection endpoint at any of the configured authorizaiton servers' + ) + } + authorizationServer = _authorizationServer + tokenPayload = _tokenPayload + } + + // we have verified the token payload here. Now we want to do some additional checks + if (tokenPayload.sub !== issuerMetadata.issuerUrl) { + throw new CredoError(`Expected access token 'sub' to equal issuer '${issuerMetadata.issuerUrl}'`) + } + + if (!tokenPayload.issuer_state || typeof tokenPayload.issuer_state !== 'string') { + throw new CredoError(`Missing 'issuer_state' parameter in access token or introspection response`) + } + + return { authorizationServerConfig: authorizationServer, issuerState: tokenPayload.issuer_state } +} diff --git a/packages/openid4vc/src/openid4vc-issuer/router/verifyResourceRequest.ts b/packages/openid4vc/src/openid4vc-issuer/authorization/internalAuthorization.ts similarity index 54% rename from packages/openid4vc/src/openid4vc-issuer/router/verifyResourceRequest.ts rename to packages/openid4vc/src/openid4vc-issuer/authorization/internalAuthorization.ts index a525e987da..b28760ca4f 100644 --- a/packages/openid4vc/src/openid4vc-issuer/router/verifyResourceRequest.ts +++ b/packages/openid4vc/src/openid4vc-issuer/authorization/internalAuthorization.ts @@ -1,37 +1,23 @@ -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 type { AgentContext, Jwt } from '@credo-ts/core' -import { CredoError, joinUriParts, JwsService, Jwt } from '@credo-ts/core' -import { verifyResourceDPoP } from '@sphereon/oid4vc-common' +import { CredoError, joinUriParts, JwsService } from '@credo-ts/core' -import { getVerifyJwtCallback } from '../../shared/utils' -import { OpenId4VcIssuerModuleConfig } from '../OpenId4VcIssuerModuleConfig' import { OpenId4VcIssuerService } from '../OpenId4VcIssuerService' +import { OpenId4VcIssuerModuleConfig } from '../OpenId4VcIssuerModuleConfig' +import { OpenId4VcIssuanceRequest } from '../router' +import { SigningAlgo, verifyResourceDPoP } from '@sphereon/oid4vc-common' +import { getVerifyJwtCallback } from '../../shared/utils' -export type VerifyAccessTokenResult = - | { preAuthorizedCode: string; issuerState: undefined } - | { preAuthorizedCode: undefined; issuerState: string } - -export async function verifyResourceRequest( +export async function verifyInternalAccessToken( agentContext: AgentContext, issuer: OpenId4VcIssuerRecord, - request: OpenId4VcIssuanceRequest -): Promise { + request: OpenId4VcIssuanceRequest, + accessToken: Jwt +) { 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, { @@ -41,16 +27,6 @@ export async function verifyResourceRequest( }, }) - 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') } @@ -66,22 +42,29 @@ export async function verifyResourceRequest( throw new CredoError('Access token was not issued by the expected issuer') } - const { preAuthorizedCode, issuerState } = accessToken.payload.additionalClaims + // TODO: support dpop nonce + // Verify DPoP + 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, + } + ) + + const { preAuthorizedCode } = accessToken.payload.additionalClaims - if (!preAuthorizedCode && !issuerState) { - throw new CredoError('No preAuthorizedCode or issuerState present in access token') + if (!preAuthorizedCode) { + throw new CredoError('No preAuthorizedCode present in access token') } - if (preAuthorizedCode && typeof preAuthorizedCode !== 'string') { + if (typeof preAuthorizedCode !== 'string') { throw new CredoError('Invalid preAuthorizedCode present in access token') } - if (issuerState && typeof issuerState !== 'string') { - throw new CredoError('Invalid issuerState present in access token') - } - return { - preAuthorizedCode: preAuthorizedCode || undefined, - issuerState: issuerState || undefined, - } as VerifyAccessTokenResult + preAuthorizedCode, + } } diff --git a/packages/openid4vc/src/openid4vc-issuer/authorization/introspect.ts b/packages/openid4vc/src/openid4vc-issuer/authorization/introspect.ts new file mode 100644 index 0000000000..8369cde5bb --- /dev/null +++ b/packages/openid4vc/src/openid4vc-issuer/authorization/introspect.ts @@ -0,0 +1,29 @@ +import { importOauth4webapi, type oauth } from '../oauth4webapi' +import { getClientAuth } from './clientAuth' + +export interface IntrospectTokenOptions { + authorizationServer: oauth.AuthorizationServer + + clientId: string + clientSecret: string + + token: string +} + +export async function introspectToken({ authorizationServer, clientId, clientSecret, token }: IntrospectTokenOptions) { + const oauth = await importOauth4webapi() + const response = await oauth.introspectionRequest( + authorizationServer, + { client_id: clientId }, + await getClientAuth(authorizationServer, { clientSecret, endpointType: 'introspection' }), + token + ) + + const introspectionResponse = await oauth.processIntrospectionResponse( + authorizationServer, + { client_id: clientId }, + response + ) + + return introspectionResponse +} diff --git a/packages/openid4vc/src/openid4vc-issuer/authorization/verifyResourceRequest.ts b/packages/openid4vc/src/openid4vc-issuer/authorization/verifyResourceRequest.ts new file mode 100644 index 0000000000..0132d8bba8 --- /dev/null +++ b/packages/openid4vc/src/openid4vc-issuer/authorization/verifyResourceRequest.ts @@ -0,0 +1,50 @@ +import type { OpenId4VcIssuerRecord } from '../repository' +import type { OpenId4VcIssuanceRequest } from '../router/requestContext' +import type { AgentContext } from '@credo-ts/core' + +import { CredoError, Jwt } from '@credo-ts/core' + +import { OpenId4VcIssuerService } from '../OpenId4VcIssuerService' + +import { verifyInternalAccessToken } from './internalAuthorization' +import { verifyExternalAccessToken } from './externalAuthorization' + +export async function verifyResourceRequest( + agentContext: AgentContext, + issuer: OpenId4VcIssuerRecord, + request: OpenId4VcIssuanceRequest +) { + const openId4VcIssuerService = agentContext.dependencyManager.resolve(OpenId4VcIssuerService) + const issuerMetadata = openId4VcIssuerService.getIssuerMetadata(agentContext, issuer) + 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.`) + } + + // Try parse it as JWT first, otherwise we treat it as an opaque token + let accessToken: Jwt | string + try { + accessToken = Jwt.fromSerializedJwt(authorizationHeader.replace('Bearer ', '').replace('DPoP ', '')) + } catch (error) { + accessToken = authorizationHeader.replace('Bearer ', '').replace('DPoP ', '') + } + + // TODO: we can support DPoP with opaque access token by extracting the data from the introspection + // endpiont, but that will require changes to the DPoP implementation in Sphereon's lib. + if (typeof accessToken === 'string' && authorizationHeader.startsWith('DPoP ')) { + throw new CredoError( + 'DPoP is not supported for opaque access tokens. Either disable DPoP on the authorization server, or use a JWT access token' + ) + } + + if (accessToken instanceof Jwt && accessToken.payload.iss === issuerMetadata.issuerUrl) { + return await verifyInternalAccessToken(agentContext, issuer, request, accessToken) + } + + return await verifyExternalAccessToken(agentContext, issuer, request, accessToken) +} diff --git a/packages/openid4vc/src/openid4vc-issuer/oauth4webapi.native.ts b/packages/openid4vc/src/openid4vc-issuer/oauth4webapi.native.ts new file mode 100644 index 0000000000..89dd3e084d --- /dev/null +++ b/packages/openid4vc/src/openid4vc-issuer/oauth4webapi.native.ts @@ -0,0 +1,5 @@ +export function importOauth4webapi() { + throw new Error( + "oauth4webapi cannot be imported in React Native. This is probably because you are trying to use the 'OpenId4VcIssuerModule' or the 'OpenId4VcVerifierModule'." + ) +} diff --git a/packages/openid4vc/src/openid4vc-issuer/oauth4webapi.ts b/packages/openid4vc/src/openid4vc-issuer/oauth4webapi.ts new file mode 100644 index 0000000000..190354ae24 --- /dev/null +++ b/packages/openid4vc/src/openid4vc-issuer/oauth4webapi.ts @@ -0,0 +1,12 @@ +export type * as oauth from 'oauth4webapi' + +export async function importOauth4webapi() { + try { + // NOTE: 'oauth4webapi' is required in when using OpenID4VC Issuer module. + // eslint-disable-next-line import/no-extraneous-dependencies, @typescript-eslint/no-var-requires + const oauth4webapi = await import('oauth4webapi') + return oauth4webapi.default + } catch (error) { + throw new Error(`Could not import oauth4webapi. ${error.message}`) + } +} diff --git a/packages/openid4vc/src/openid4vc-issuer/router/accessTokenEndpoint.ts b/packages/openid4vc/src/openid4vc-issuer/router/accessTokenEndpoint.ts index 35f003afd0..37d5e774d1 100644 --- a/packages/openid4vc/src/openid4vc-issuer/router/accessTokenEndpoint.ts +++ b/packages/openid4vc/src/openid4vc-issuer/router/accessTokenEndpoint.ts @@ -1,14 +1,7 @@ import type { OpenId4VcIssuanceRequest } from './requestContext' import type { AgentContext } from '@credo-ts/core' import type { JWK, SigningAlgo } from '@sphereon/oid4vc-common' -import type { - AccessTokenResponse, - CredentialOfferSession, - IStateManager, - JWTSignerCallback, - Jwt, -} from '@sphereon/oid4vci-common' -import type { ITokenEndpointOpts } from '@sphereon/oid4vci-issuer' +import type { JWTSignerCallback } from '@sphereon/oid4vci-common' import type { NextFunction, Response, Router } from 'express' import { @@ -20,210 +13,18 @@ import { Key, joinUriParts, } from '@credo-ts/core' -import { calculateJwkThumbprint, verifyDPoP } from '@sphereon/oid4vc-common' -import { - Alg, - EXPIRED_PRE_AUTHORIZED_CODE, - GrantTypes, - INVALID_PRE_AUTHORIZED_CODE, - IssueStatus, - PIN_NOT_MATCH_ERROR, - PIN_VALIDATION_ERROR, - PRE_AUTH_CODE_LITERAL, - TokenError, - TokenErrorResponse, - UNSUPPORTED_GRANT_TYPE_ERROR, - USER_PIN_NOT_REQUIRED_ERROR, - USER_PIN_REQUIRED_ERROR, -} from '@sphereon/oid4vci-common' -import { isPreAuthorizedCodeExpired } from '@sphereon/oid4vci-issuer' +import { verifyDPoP } from '@sphereon/oid4vc-common' +import { PRE_AUTH_CODE_LITERAL, TokenError, TokenErrorResponse } from '@sphereon/oid4vci-common' -import { getRequestContext, sendErrorResponse } from '../../shared/router' +import { getRequestContext, sendErrorResponse, sendJsonResponse } from '../../shared/router' import { getVerifyJwtCallback } from '../../shared/utils' import { OpenId4VcIssuerModuleConfig } from '../OpenId4VcIssuerModuleConfig' import { OpenId4VcIssuerService } from '../OpenId4VcIssuerService' import { OpenId4VcCNonceStateManager } from '../repository/OpenId4VcCNonceStateManager' import { OpenId4VcCredentialOfferSessionStateManager } from '../repository/OpenId4VcCredentialOfferSessionStateManager' - -// TODO -export const isValidGrant = (assertedState: CredentialOfferSession, grantType: string): boolean => { - if (assertedState.credentialOffer?.credential_offer?.grants) { - const validPreAuthorizedGrant = - Object.keys(assertedState.credentialOffer.credential_offer.grants).includes(GrantTypes.PRE_AUTHORIZED_CODE) && - grantType === GrantTypes.PRE_AUTHORIZED_CODE - - const validAuthorizationCodeGrant = - Object.keys(assertedState.credentialOffer.credential_offer.grants).includes(GrantTypes.AUTHORIZATION_CODE) && - grantType === GrantTypes.AUTHORIZATION_CODE - return validAuthorizationCodeGrant || validPreAuthorizedGrant - } - return false -} - -// TODO: Update in Sphereon OID4VCI -export const assertValidAccessTokenRequest = async ( - request: AccessTokenRequest, - opts: { - credentialOfferSessions: IStateManager - expirationDuration: number - } -) => { - const { credentialOfferSessions, expirationDuration } = opts - // Only pre-auth supported for now - if (request.grant_type !== GrantTypes.PRE_AUTHORIZED_CODE && request.grant_type !== GrantTypes.AUTHORIZATION_CODE) { - throw new TokenError(400, TokenErrorResponse.invalid_grant, UNSUPPORTED_GRANT_TYPE_ERROR) - } - - // Pre-auth flow - const preAuthorizedCode = - request.grant_type === GrantTypes.PRE_AUTHORIZED_CODE ? request[PRE_AUTH_CODE_LITERAL] : undefined - const issuerStatus = request.grant_type === GrantTypes.AUTHORIZATION_CODE ? request.issuer_state : undefined - - if (!preAuthorizedCode && !issuerStatus) { - throw new TokenError( - 400, - TokenErrorResponse.invalid_request, - "Either 'pre-authorized_code' or 'authorization_code' is required" - ) - } - - const code = (preAuthorizedCode ?? issuerStatus) as string - - const credentialOfferSession = await credentialOfferSessions.getAsserted(code) - - if (![IssueStatus.OFFER_CREATED, IssueStatus.OFFER_URI_RETRIEVED].includes(credentialOfferSession.status)) { - throw new TokenError(400, TokenErrorResponse.invalid_request, 'Access token has already been retrieved') - } - - credentialOfferSession.status = IssueStatus.ACCESS_TOKEN_REQUESTED - credentialOfferSession.lastUpdatedAt = +new Date() - await credentialOfferSessions.set(code, credentialOfferSession) - - if (!isValidGrant(credentialOfferSession, request.grant_type)) { - throw new TokenError(400, TokenErrorResponse.invalid_grant, UNSUPPORTED_GRANT_TYPE_ERROR) - } - - if (preAuthorizedCode) { - /* - invalid_request: - the Authorization Server expects a PIN in the pre-authorized flow but the client does not provide a PIN - */ - if ( - credentialOfferSession.credentialOffer.credential_offer.grants?.[GrantTypes.PRE_AUTHORIZED_CODE] - ?.user_pin_required && - !request.user_pin - ) { - throw new TokenError(400, TokenErrorResponse.invalid_request, USER_PIN_REQUIRED_ERROR) - } - - /* - invalid_request: - the Authorization Server does not expect a PIN in the pre-authorized flow but the client provides a PIN - */ - if ( - !credentialOfferSession.credentialOffer.credential_offer.grants?.[GrantTypes.PRE_AUTHORIZED_CODE] - ?.user_pin_required && - request.user_pin - ) { - throw new TokenError(400, TokenErrorResponse.invalid_request, USER_PIN_NOT_REQUIRED_ERROR) - } - - /* - invalid_grant: - the Authorization Server expects a PIN in the pre-authorized flow but the client provides the wrong PIN - the End-User provides the wrong Pre-Authorized Code or the Pre-Authorized Code has expired - */ - if (request.user_pin && !/[0-9{,8}]/.test(request.user_pin)) { - throw new TokenError(400, TokenErrorResponse.invalid_grant, PIN_VALIDATION_ERROR) - } else if (request.user_pin !== credentialOfferSession.txCode) { - throw new TokenError(400, TokenErrorResponse.invalid_grant, PIN_NOT_MATCH_ERROR) - } else if (isPreAuthorizedCodeExpired(credentialOfferSession, expirationDuration)) { - throw new TokenError(400, TokenErrorResponse.invalid_grant, EXPIRED_PRE_AUTHORIZED_CODE) - } else if ( - request[PRE_AUTH_CODE_LITERAL] !== - credentialOfferSession.credentialOffer.credential_offer.grants?.[GrantTypes.PRE_AUTHORIZED_CODE]?.[ - PRE_AUTH_CODE_LITERAL - ] - ) { - throw new TokenError(400, TokenErrorResponse.invalid_grant, INVALID_PRE_AUTHORIZED_CODE) - } - return { preAuthSession: credentialOfferSession } - } - - // Authorization code flow - - const authorizationCodeGrant = credentialOfferSession.credentialOffer.credential_offer.grants?.authorization_code - - if (authorizationCodeGrant?.issuer_state !== credentialOfferSession.issuerState) { - throw new TokenError( - 400, - TokenErrorResponse.invalid_request, - 'Issuer state does not match credential offer issuance state' - ) - } - - // TODO: rename to isCodeExpired - if (isPreAuthorizedCodeExpired(credentialOfferSession, expirationDuration)) { - throw new TokenError(400, TokenErrorResponse.invalid_grant, 'Issuer state is expired') - } - - if (!authorizationCodeGrant?.issuer_state || request.issuer_state !== authorizationCodeGrant.issuer_state) { - throw new TokenError(400, TokenErrorResponse.invalid_grant, 'Issuer state is invalid') - } - return { preAuthSession: credentialOfferSession } -} - -// TODO: Update in Sphereon OID4VCI - -export interface AccessTokenRequest { - client_id?: string - code?: string - code_verifier?: string - grant_type: GrantTypes - 'pre-authorized_code'?: string - issuer_state?: string - redirect_uri?: string - scope?: string - user_pin?: string -} - -/** - * TODO: create pr to support issuerState in `createAccessTokenResponse` - * Copy from '@sphereon/oid4vci-issuer' - * @param opts - * @returns - */ -export const generateAccessToken = async ( - opts: Required> & { - preAuthorizedCode?: string - issuerState?: string - alg?: Alg - dPoPJwk?: JWK - } -): Promise => { - const { dPoPJwk, accessTokenIssuer, alg, accessTokenSignerCallback, tokenExpiresIn, preAuthorizedCode, issuerState } = - opts - // JWT uses seconds for iat and exp - const iat = new Date().getTime() / 1000 - const exp = iat + tokenExpiresIn - const cnf = dPoPJwk ? { cnf: { jkt: await calculateJwkThumbprint(dPoPJwk, 'sha256') } } : undefined - const jwt: Jwt = { - header: { typ: 'JWT', alg: alg ?? Alg.ES256 }, - payload: { - iat, - exp, - iss: accessTokenIssuer, - ...cnf, - ...(preAuthorizedCode && { preAuthorizedCode }), - ...(issuerState && { issuerState }), - // Protected resources simultaneously supporting both the DPoP and Bearer schemes need to update how the - // evaluation process is performed for bearer tokens to prevent downgraded usage of a DPoP-bound access token. - // Specifically, such a protected resource MUST reject a DPoP-bound access token received as a bearer token per [RFC6750]. - token_type: dPoPJwk ? 'DPoP' : 'Bearer', - }, - } - return await accessTokenSignerCallback(jwt) -} +import { assertValidAccessTokenRequest, createAccessTokenResponse } from '@sphereon/oid4vci-issuer' +import { OpenId4VcIssuanceSessionRepository } from '../repository' +import { OpenId4VcIssuanceSessionState } from '../OpenId4VcIssuanceSessionState' export interface OpenId4VciAccessTokenEndpointConfig { /** @@ -245,6 +46,8 @@ export interface OpenId4VciAccessTokenEndpointConfig { * expire. * * @default 360 (5 minutes) + * + * @todo move to general config (not endpoint specific) */ cNonceExpiresInSeconds: number @@ -257,11 +60,7 @@ export interface OpenId4VciAccessTokenEndpointConfig { } export function configureAccessTokenEndpoint(router: Router, config: OpenId4VciAccessTokenEndpointConfig) { - router.post( - config.endpointPath, - verifyTokenRequest({ codeExpirationInSeconds: config.preAuthorizedCodeExpirationInSeconds }), - handleTokenRequest(config) - ) + router.post(config.endpointPath, verifyTokenRequest(config), handleTokenRequest(config)) } function getJwtSignerCallback( @@ -311,8 +110,6 @@ export function handleTokenRequest(config: OpenId4VciAccessTokenEndpointConfig) const requestContext = getRequestContext(request) const { agentContext, issuer } = requestContext - const body = request.body as AccessTokenRequest - const openId4VcIssuerService = agentContext.dependencyManager.resolve(OpenId4VcIssuerService) const issuerMetadata = openId4VcIssuerService.getIssuerMetadata(agentContext, issuer) const accessTokenSigningKey = Key.fromFingerprint(issuer.accessTokenPublicKeyFingerprint) @@ -335,6 +132,7 @@ export function handleTokenRequest(config: OpenId4VciAccessTokenEndpointConfig) } catch (error) { return sendErrorResponse( response, + next, agentContext.config.logger, 400, TokenErrorResponse.invalid_dpop_proof, @@ -344,82 +142,86 @@ export function handleTokenRequest(config: OpenId4VciAccessTokenEndpointConfig) } try { - const code = body.issuer_state - ? { type: 'issuerState' as const, value: body.issuer_state } - : { type: 'preAuthorized' as const, value: body[PRE_AUTH_CODE_LITERAL] as string } - - const cNonceStateManager = new OpenId4VcCNonceStateManager(agentContext, issuer.issuerId) - const cNonce = await agentContext.wallet.generateNonce() - await cNonceStateManager.set( - cNonce, - { - cNonce, - createdAt: +new Date(), - ...(code.type === 'issuerState' && { issuerState: code.value }), - ...(code.type === 'preAuthorized' && { preAuthorizedCode: code.value }), - }, - code.type - ) - - const access_token = await generateAccessToken({ - tokenExpiresIn: 3600, + 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), - ...(code.type === 'issuerState' && { issuerState: code.value }), - ...(code.type === 'preAuthorized' && { preAuthorizedCode: code.value }), dPoPJwk: dpopJwk, }) - const accessTokenResponse: AccessTokenResponse = { - access_token, - expires_in: tokenExpiresInSeconds, - c_nonce: cNonce, - c_nonce_expires_in: cNonceExpiresInSeconds, - authorization_pending: false, - interval: undefined, - token_type: dpopJwk ? 'DPoP' : 'Bearer', - } - - const credentialOfferStateManager = new OpenId4VcCredentialOfferSessionStateManager(agentContext, issuer.issuerId) - const credentialOfferSession = await credentialOfferStateManager.getAsserted(code.value, code.type) - credentialOfferSession.status = IssueStatus.ACCESS_TOKEN_CREATED - credentialOfferSession.lastUpdatedAt = +new Date() - await credentialOfferStateManager.set(code.value, credentialOfferSession, code.type) - - response.status(200).json(accessTokenResponse) + return sendJsonResponse(response, next, accessTokenResponse) } catch (error) { - sendErrorResponse(response, agentContext.config.logger, 400, TokenErrorResponse.invalid_request, error) + return sendErrorResponse( + response, + next, + 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: { codeExpirationInSeconds: number }) { +export function verifyTokenRequest(options: { preAuthorizedCodeExpirationInSeconds: number }) { return async (request: OpenId4VcIssuanceRequest, response: Response, next: NextFunction) => { const { agentContext, issuer } = getRequestContext(request) try { + const credentialOfferSessions = new OpenId4VcCredentialOfferSessionStateManager(agentContext, issuer.issuerId) + + const preAuthorizedCode = request.body[PRE_AUTH_CODE_LITERAL] + if (!preAuthorizedCode || typeof preAuthorizedCode !== 'string') { + throw new TokenError( + 400, + TokenErrorResponse.invalid_request, + `Missing '${PRE_AUTH_CODE_LITERAL}' parameter in access token request body` + ) + } + const issuanceSessionRepository = agentContext.dependencyManager.resolve(OpenId4VcIssuanceSessionRepository) + const openId4VcIssuanceSession = await issuanceSessionRepository.getSingleByQuery(agentContext, { + preAuthorizedCode, + issuerId: issuer.issuerId, + }) + + if ( + ![OpenId4VcIssuanceSessionState.OfferCreated, OpenId4VcIssuanceSessionState.OfferUriRetrieved].includes( + openId4VcIssuanceSession.state + ) + ) { + throw new TokenError(400, TokenErrorResponse.invalid_request, 'Access token has already been retrieved') + } + await assertValidAccessTokenRequest(request.body, { - expirationDuration: options.codeExpirationInSeconds, - credentialOfferSessions: new OpenId4VcCredentialOfferSessionStateManager(agentContext, issuer.issuerId), + expirationDuration: options.preAuthorizedCodeExpirationInSeconds, + credentialOfferSessions, }) + + next() } catch (error) { if (error instanceof TokenError) { - sendErrorResponse( + return sendErrorResponse( response, + next, agentContext.config.logger, error.statusCode, error.responseError, error.getDescription() ) } else { - sendErrorResponse(response, agentContext.config.logger, 400, TokenErrorResponse.invalid_request, error) + return sendErrorResponse( + response, + next, + agentContext.config.logger, + 400, + TokenErrorResponse.invalid_request, + 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/authorizationServerMetadataEndpoint.ts b/packages/openid4vc/src/openid4vc-issuer/router/authorizationServerMetadataEndpoint.ts index 723aa1d3d9..881e5036d4 100644 --- a/packages/openid4vc/src/openid4vc-issuer/router/authorizationServerMetadataEndpoint.ts +++ b/packages/openid4vc/src/openid4vc-issuer/router/authorizationServerMetadataEndpoint.ts @@ -1,8 +1,8 @@ import type { OpenId4VcIssuanceRequest } from './requestContext' -import type { AuthorizationServerMetadata } from '@sphereon/oid4vci-common' +import type { AuthorizationServerMetadata } from '@animo-id/oid4vci' import type { Router, Response } from 'express' -import { getRequestContext, sendErrorResponse } from '../../shared/router' +import { getRequestContext, sendErrorResponse, sendJsonResponse } from '../../shared/router' import { OpenId4VcIssuerService } from '../OpenId4VcIssuerService' /** @@ -23,19 +23,12 @@ export function configureOAuthAuthorizationServerMetadataEndpoint(router: Router token_endpoint: issuerMetadata.tokenEndpoint, dpop_signing_alg_values_supported: issuerMetadata.dpopSigningAlgValuesSupported, 'pre-authorized_grant_anonymous_access_supported': true, - - // Required by sphereon types, but OID4VCI mentions it can be omitted if - // only the pre-auth code flow is supported. We use empty array - response_types_supported: [] } satisfies AuthorizationServerMetadata - response.status(200).json(authorizationServerMetadata) + return sendJsonResponse(response, next, authorizationServerMetadata) } catch (e) { - sendErrorResponse(response, agentContext.config.logger, 500, 'invalid_request', e) + return sendErrorResponse(response, next, 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/credentialEndpoint.ts b/packages/openid4vc/src/openid4vc-issuer/router/credentialEndpoint.ts index 9b4c5601e8..f97c2fb58d 100644 --- a/packages/openid4vc/src/openid4vc-issuer/router/credentialEndpoint.ts +++ b/packages/openid4vc/src/openid4vc-issuer/router/credentialEndpoint.ts @@ -1,14 +1,16 @@ import type { OpenId4VcIssuanceRequest } from './requestContext' -import type { VerifyAccessTokenResult } from './verifyResourceRequest' import type { OpenId4VciCredentialRequest } from '../../shared' import type { OpenId4VciCredentialRequestToCredentialMapper } from '../OpenId4VcIssuerServiceOptions' import type { Router, Response } from 'express' -import { getRequestContext, sendErrorResponse } from '../../shared/router' +import { getRequestContext, sendErrorResponse, sendJsonResponse } from '../../shared/router' import { OpenId4VcIssuerService } from '../OpenId4VcIssuerService' import { getCNonceFromCredentialRequest } from '../util/credentialRequest' -import { verifyResourceRequest } from './verifyResourceRequest' +import { verifyResourceRequest } from '../authorization/verifyResourceRequest' +import { OpenId4VcIssuanceSessionRecord, OpenId4VcIssuanceSessionRepository } from '../repository' +import { OpenId4VcIssuerModuleConfig } from '../OpenId4VcIssuerModuleConfig' +import { utils } from '@credo-ts/core' export interface OpenId4VciCredentialEndpointConfig { /** @@ -29,60 +31,72 @@ export function configureCredentialEndpoint(router: Router, config: OpenId4VciCr router.post(config.endpointPath, async (request: OpenId4VcIssuanceRequest, response: Response, next) => { const { agentContext, issuer } = getRequestContext(request) const openId4VcIssuerService = agentContext.dependencyManager.resolve(OpenId4VcIssuerService) - - let verifyAccessTokenResult: VerifyAccessTokenResult + const issuanceModuleConfig = agentContext.dependencyManager.resolve(OpenId4VcIssuerModuleConfig) // Verify the access token (should at some point be moved to a middleware function or something) - try { - verifyAccessTokenResult = await verifyResourceRequest(agentContext, issuer, request) - } catch (error) { - return sendErrorResponse(response, agentContext.config.logger, 401, 'unauthorized', error) - } + const verifyAccessTokenResult = await verifyResourceRequest(agentContext, issuer, request).catch((error) => { + sendErrorResponse(response, next, agentContext.config.logger, 401, 'unauthorized', error) + }) + if (!verifyAccessTokenResult) return try { const credentialRequest = request.body as OpenId4VciCredentialRequest + const issuanceSessionRepository = agentContext.dependencyManager.resolve(OpenId4VcIssuanceSessionRepository) - const issuanceSession = await openId4VcIssuerService.findIssuanceSessionForCredentialRequest(agentContext, { - issuerId: issuer.issuerId, - credentialRequest, - }) + let issuanceSession: OpenId4VcIssuanceSessionRecord | null + if ('preAuthorizedCode' in verifyAccessTokenResult) { + issuanceSession = await issuanceSessionRepository.findSingleByQuery(agentContext, { + issuerId: issuer.issuerId, + preAuthorizedCode: verifyAccessTokenResult.preAuthorizedCode, + }) + } else { + issuanceSession = await issuanceSessionRepository.findSingleByQuery(agentContext, { + issuerId: issuer.issuerId, + issuerState: verifyAccessTokenResult.issuerState, + }) + } 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}` + `No issuance session found for incoming credential request for issuer ${issuer.issuerId} and access token data`, + { + verifyAccessTokenResult, + } ) - return sendErrorResponse(response, agentContext.config.logger, 404, 'invalid_request', null) + return sendErrorResponse(response, next, agentContext.config.logger, 404, 'invalid_request', null) } - if ( - (verifyAccessTokenResult.preAuthorizedCode && - issuanceSession.preAuthorizedCode !== verifyAccessTokenResult.preAuthorizedCode) || - (verifyAccessTokenResult.issuerState && issuanceSession.issuerState !== verifyAccessTokenResult.issuerState) - ) { - agentContext.config.logger.warn( - `Credential request used access token with different a pre-authorized code or issuerState code than was used for the issuance session ${issuanceSession?.id}` - ) - return sendErrorResponse( - response, - agentContext.config.logger, - 401, - 'unauthorized', - 'Access token is not valid for this credential request' + try { + const cNonce = getCNonceFromCredentialRequest(credentialRequest) + + if (!issuanceSession.cNonce || cNonce !== issuanceSession.cNonce) { + throw new Error('Invalid c_nonce') + } + } catch (error) { + // If no c_nonce could be extracted we generate a new one and send that in the error response + const expiresAtDate = new Date( + Date.now() + issuanceModuleConfig.accessTokenEndpoint.cNonceExpiresInSeconds * 1000 ) + + issuanceSession.cNonce = utils.uuid() + issuanceSession.cNonceExpiresAt = expiresAtDate + issuanceSessionRepository.update(agentContext, issuanceSession) + + return sendErrorResponse(response, next, agentContext.config.logger, 404, 'invalid_proof', null, { + c_nonce: issuanceSession.cNonce, + c_nonce_expires_in: issuanceModuleConfig.accessTokenEndpoint.cNonceExpiresInSeconds, + }) } + // TODO: invalidate nonce if this method fails const { credentialResponse } = await openId4VcIssuerService.createCredentialResponse(agentContext, { issuanceSession, credentialRequest, }) - response.json(credentialResponse) + return sendJsonResponse(response, next, credentialResponse) } catch (error) { - sendErrorResponse(response, agentContext.config.logger, 500, 'invalid_request', error) + return sendErrorResponse(response, next, agentContext.config.logger, 500, 'invalid_request', 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/credentialOfferEndpoint.ts b/packages/openid4vc/src/openid4vc-issuer/router/credentialOfferEndpoint.ts index f1e316d1f3..6442171e14 100644 --- a/packages/openid4vc/src/openid4vc-issuer/router/credentialOfferEndpoint.ts +++ b/packages/openid4vc/src/openid4vc-issuer/router/credentialOfferEndpoint.ts @@ -4,7 +4,7 @@ import type { Router, Response } from 'express' import { joinUriParts, EventEmitter } from '@credo-ts/core' -import { getRequestContext, sendErrorResponse } from '../../shared/router' +import { getRequestContext, sendErrorResponse, sendJsonResponse } from '../../shared/router' import { OpenId4VcIssuanceSessionState } from '../OpenId4VcIssuanceSessionState' import { OpenId4VcIssuerEvents } from '../OpenId4VcIssuerEvents' import { OpenId4VcIssuerModuleConfig } from '../OpenId4VcIssuerModuleConfig' @@ -30,6 +30,7 @@ export function configureCredentialOfferEndpoint(router: Router, config: OpenId4 if (!request.params.credentialOfferId || typeof request.params.credentialOfferId !== 'string') { return sendErrorResponse( response, + next, agentContext.config.logger, 400, 'invalid_request', @@ -56,7 +57,14 @@ export function configureCredentialOfferEndpoint(router: Router, config: OpenId4 }) if (!openId4VcIssuanceSession || !openId4VcIssuanceSession.credentialOfferPayload) { - return sendErrorResponse(response, agentContext.config.logger, 404, 'not_found', 'Credential offer not found') + return sendErrorResponse( + response, + next, + agentContext.config.logger, + 404, + 'not_found', + 'Credential offer not found' + ) } if ( @@ -66,6 +74,7 @@ export function configureCredentialOfferEndpoint(router: Router, config: OpenId4 ) { return sendErrorResponse( response, + next, agentContext.config.logger, 400, 'invalid_request', @@ -91,13 +100,10 @@ export function configureCredentialOfferEndpoint(router: Router, config: OpenId4 }) } - response.json(openId4VcIssuanceSession.credentialOfferPayload) + return sendJsonResponse(response, next, openId4VcIssuanceSession.credentialOfferPayload) } catch (error) { - sendErrorResponse(response, agentContext.config.logger, 500, 'invalid_request', error) + return sendErrorResponse(response, next, agentContext.config.logger, 500, 'invalid_request', 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 06a73f16c2..5dab29f903 100644 --- a/packages/openid4vc/src/openid4vc-issuer/router/index.ts +++ b/packages/openid4vc/src/openid4vc-issuer/router/index.ts @@ -1,6 +1,6 @@ export { configureAccessTokenEndpoint, OpenId4VciAccessTokenEndpointConfig } from './accessTokenEndpoint' export { configureCredentialEndpoint, OpenId4VciCredentialEndpointConfig } from './credentialEndpoint' -export { configureIssuerMetadataEndpoint } from './metadataEndpoint' +export { configureIssuerMetadataEndpoint } from './issuerMetadataEndpoint' export { configureOAuthAuthorizationServerMetadataEndpoint } from './authorizationServerMetadataEndpoint' export { configureCredentialOfferEndpoint, OpenId4VciCredentialOfferEndpointConfig } from './credentialOfferEndpoint' export { OpenId4VcIssuanceRequest } from './requestContext' diff --git a/packages/openid4vc/src/openid4vc-issuer/router/issuerMetadataEndpoint.ts b/packages/openid4vc/src/openid4vc-issuer/router/issuerMetadataEndpoint.ts index 0a267f55b7..aad1ad0ab3 100644 --- a/packages/openid4vc/src/openid4vc-issuer/router/issuerMetadataEndpoint.ts +++ b/packages/openid4vc/src/openid4vc-issuer/router/issuerMetadataEndpoint.ts @@ -2,7 +2,7 @@ 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 { getRequestContext, sendErrorResponse, sendJsonResponse } from '../../shared/router' import { OpenId4VcIssuerService } from '../OpenId4VcIssuerService' export function configureIssuerMetadataEndpoint(router: Router) { @@ -33,13 +33,10 @@ export function configureIssuerMetadataEndpoint(router: Router) { dpop_signing_alg_values_supported: issuerMetadata.dpopSigningAlgValuesSupported, } satisfies CredentialIssuerMetadata - response.status(200).json(transformedMetadata) + return sendJsonResponse(response, next, transformedMetadata) } catch (e) { - sendErrorResponse(response, agentContext.config.logger, 500, 'invalid_request', e) + return sendErrorResponse(response, next, 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-verifier/router/authorizationEndpoint.ts b/packages/openid4vc/src/openid4vc-verifier/router/authorizationEndpoint.ts index 926104b419..6d3f6126e4 100644 --- a/packages/openid4vc/src/openid4vc-verifier/router/authorizationEndpoint.ts +++ b/packages/openid4vc/src/openid4vc-verifier/router/authorizationEndpoint.ts @@ -7,7 +7,7 @@ import type { Response, Router } from 'express' import { CredoError, Key, TypedArrayEncoder } from '@credo-ts/core' import { AuthorizationRequest, RP } from '@sphereon/did-auth-siop' -import { getRequestContext, sendErrorResponse } from '../../shared/router' +import { getRequestContext, sendErrorResponse, sendJsonResponse } from '../../shared/router' import { OpenId4VcSiopVerifierService } from '../OpenId4VcSiopVerifierService' export interface OpenId4VcSiopAuthorizationEndpointConfig { @@ -123,12 +123,9 @@ export function configureAuthorizationEndpoint(router: Router, config: OpenId4Vc authorizationResponse: authorizationResponsePayload, verificationSession, }) - response.status(200).send() + return sendJsonResponse(response, next, {}) } catch (error) { - sendErrorResponse(response, agentContext.config.logger, 500, 'invalid_request', error) + return sendErrorResponse(response, next, agentContext.config.logger, 500, 'invalid_request', error) } - - // NOTE: if we don't call next, the agentContext session handler will NOT be called - next() }) } diff --git a/packages/openid4vc/src/openid4vc-verifier/router/authorizationRequestEndpoint.ts b/packages/openid4vc/src/openid4vc-verifier/router/authorizationRequestEndpoint.ts index 01c4736dd8..9798506670 100644 --- a/packages/openid4vc/src/openid4vc-verifier/router/authorizationRequestEndpoint.ts +++ b/packages/openid4vc/src/openid4vc-verifier/router/authorizationRequestEndpoint.ts @@ -4,7 +4,7 @@ import type { Router, Response } from 'express' import { EventEmitter, joinUriParts } from '@credo-ts/core' -import { getRequestContext, sendErrorResponse } from '../../shared/router' +import { getRequestContext, sendErrorResponse, sendJsonResponse } from '../../shared/router' import { OpenId4VcSiopVerifierService } from '../OpenId4VcSiopVerifierService' import { OpenId4VcVerificationSessionState } from '../OpenId4VcVerificationSessionState' import { OpenId4VcVerifierEvents } from '../OpenId4VcVerifierEvents' @@ -33,6 +33,7 @@ export function configureAuthorizationRequestEndpoint( if (!request.params.authorizationRequestId || typeof request.params.authorizationRequestId !== 'string') { return sendErrorResponse( response, + next, agentContext.config.logger, 400, 'invalid_request', @@ -62,6 +63,7 @@ export function configureAuthorizationRequestEndpoint( if (!verificationSession) { return sendErrorResponse( response, + next, agentContext.config.logger, 404, 'not_found', @@ -77,6 +79,7 @@ export function configureAuthorizationRequestEndpoint( ) { return sendErrorResponse( response, + next, agentContext.config.logger, 400, 'invalid_request', @@ -102,13 +105,11 @@ export function configureAuthorizationRequestEndpoint( }) } - response.status(200).send(verificationSession.authorizationRequestJwt) + response.type('application/oauth-authz-req+jwt').status(200).send(verificationSession.authorizationRequestJwt) + next() } catch (error) { - sendErrorResponse(response, agentContext.config.logger, 500, 'invalid_request', error) + return sendErrorResponse(response, next, agentContext.config.logger, 500, 'invalid_request', error) } - - // NOTE: if we don't call next, the agentContext session handler will NOT be called - next() } ) } diff --git a/packages/openid4vc/src/shared/issuerMetadataUtils.ts b/packages/openid4vc/src/shared/issuerMetadataUtils.ts index 09622cd404..f9d2fb0a84 100644 --- a/packages/openid4vc/src/shared/issuerMetadataUtils.ts +++ b/packages/openid4vc/src/shared/issuerMetadataUtils.ts @@ -10,6 +10,7 @@ import type { CredentialOfferFormatV1_0_11 } from '@sphereon/oid4vci-common' import { CredoError } from '@credo-ts/core' import { getSupportedJwaSignatureAlgorithms } from './utils' +import { CredentialConfigurationSupported } from '@animo-id/oid4vci' /** * Get all `types` from a `CredentialSupported` object. @@ -185,55 +186,30 @@ export function credentialsSupportedV11ToV13( } /** - * 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. + * Returns all entries from the credential offer with the associated metadata resolved. */ export function getOfferedCredentials( - agentContext: AgentContext, - offeredCredentials: Array, - credentialsSupportedOrConfigurations: OpenId4VciCredentialConfigurationsSupported | OpenId4VciCredentialSupported[] + offeredCredentialConfigurationIds: Array, + credentialConfigurationsSupported: Record ): { - credentialsSupported: OpenId4VciCredentialSupportedWithId[] - credentialConfigurationsSupported: OpenId4VciCredentialConfigurationsSupported + credentialConfigurationsSupported: Record } { - 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 offeredCredentialConfigurations: Record = {} - const foundCredentialConfiguration = credentialConfigurationsSupported[offeredCredential] - const foundCredentialSupported = credentialsSupported.find((supported) => supported.id === offeredCredential) + for (const offeredCredentialConfigurationId of offeredCredentialConfigurationIds) { + const foundCredentialConfiguration = credentialConfigurationsSupported[offeredCredentialConfigurationId] // Make sure the issuer metadata includes the offered credential. - if (!foundCredentialConfiguration || !foundCredentialSupported) { + if (!foundCredentialConfiguration) { throw new Error( - `Offered credential '${offeredCredential}' is not part of credentials_supported/credential_configurations_supported of the issuer metadata.` + `Offered credential configuration id '${offeredCredentialConfigurationId}' is not part of credential_configurations_supported of the issuer metadata.` ) } - offeredCredentialConfigurations[offeredCredential] = foundCredentialConfiguration - offeredCredentialsSupported.push(foundCredentialSupported) + offeredCredentialConfigurations[offeredCredentialConfigurationId] = foundCredentialConfiguration } return { credentialConfigurationsSupported: offeredCredentialConfigurations, - credentialsSupported: offeredCredentialsSupported, } } diff --git a/packages/openid4vc/src/shared/models/AuthorizationServer.ts b/packages/openid4vc/src/shared/models/AuthorizationServer.ts index 5225a4a561..69c8e90171 100644 --- a/packages/openid4vc/src/shared/models/AuthorizationServer.ts +++ b/packages/openid4vc/src/shared/models/AuthorizationServer.ts @@ -1,4 +1,10 @@ export interface OpenId4VciAuthorizationServerConfig { // The base Url of your OAuth Server - baseUrl: string + issuer: string + + serverType: 'oidc' | 'oauth2' + + // client id and client secret are needed when introspection is performed + clientId?: string + clientSecret?: string } diff --git a/packages/openid4vc/src/shared/models/OpenId4VciCredentialFormatProfile.ts b/packages/openid4vc/src/shared/models/OpenId4VciCredentialFormatProfile.ts index 628e65c12e..01d65ba458 100644 --- a/packages/openid4vc/src/shared/models/OpenId4VciCredentialFormatProfile.ts +++ b/packages/openid4vc/src/shared/models/OpenId4VciCredentialFormatProfile.ts @@ -3,4 +3,5 @@ export enum OpenId4VciCredentialFormatProfile { JwtVcJsonLd = 'jwt_vc_json-ld', LdpVc = 'ldp_vc', SdJwtVc = 'vc+sd-jwt', + MsoMdoc = 'mso_mdoc' } diff --git a/packages/openid4vc/src/shared/models/index.ts b/packages/openid4vc/src/shared/models/index.ts index 3ce2cd4954..7df4df89eb 100644 --- a/packages/openid4vc/src/shared/models/index.ts +++ b/packages/openid4vc/src/shared/models/index.ts @@ -1,3 +1,4 @@ +import { CredentialConfigurationSupported, CredentialIssuerMetadata } from '@animo-id/oid4vci'; import type { VerifiedAuthorizationRequest, AuthorizationRequestPayload, @@ -25,14 +26,11 @@ import type { export type OpenId4VciCredentialSupported = CredentialsSupportedLegacy & { id?: string; scope?: string } export type OpenId4VciCredentialSupportedWithId = CredentialsSupportedLegacy & { id: string; scope?: string } export type OpenId4VciCredentialSupportedWithIdAndScope = OpenId4VciCredentialSupportedWithId & { scope: string } -export type OpenId4VciCredentialConfigurationSupported = CredentialConfigurationSupportedV1_0_13 +export type OpenId4VciCredentialConfigurationSupported = CredentialConfigurationSupported export type OpenId4VciCredentialConfigurationsSupported = Record export type OpenId4VciTxCode = TxCode -export type OpenId4VciIssuerMetadataV1Draft11 = CredentialIssuerMetadataV1_0_11 -export type OpenId4VciIssuerMetadataV1Draft13 = CredentialIssuerMetadataV1_0_13 -export type OpenId4VciIssuerMetadata = OpenId4VciIssuerMetadataV1Draft11 | OpenId4VciIssuerMetadataV1Draft13 - +export type OpenId4VciIssuerMetadata = CredentialIssuerMetadata export type OpenId4VciIssuerMetadataDisplay = MetadataDisplay export type OpenId4VciCredentialRequest = UniformCredentialRequest diff --git a/packages/openid4vc/src/shared/router/context.ts b/packages/openid4vc/src/shared/router/context.ts index 0bf538a69d..521f2a2585 100644 --- a/packages/openid4vc/src/shared/router/context.ts +++ b/packages/openid4vc/src/shared/router/context.ts @@ -1,5 +1,5 @@ import type { AgentContext, Logger } from '@credo-ts/core' -import type { Response, Request } from 'express' +import type { Response, Request, NextFunction } from 'express' import { CredoError } from '@credo-ts/core' @@ -11,16 +11,34 @@ export interface OpenId4VcRequestContext { agentContext: AgentContext } -export function sendErrorResponse(response: Response, logger: Logger, code: number, message: string, error: unknown) { +export function sendErrorResponse( + response: Response, + next: NextFunction, + logger: Logger, + code: number, + message: string, + error: unknown, + additionalPayload?: Record +) { const error_description = error instanceof Error ? error.message : typeof error === 'string' ? error : 'An unknown error occurred.' - const body = { error: message, error_description } - logger.warn(`[OID4VCI] Sending error response: ${JSON.stringify(body)}`, { + const body = { error: message, error_description, ...additionalPayload } + logger.warn(`[OID4VC] Sending error response: ${JSON.stringify(body)}`, { error, }) - return response.status(code).json(body) + response.status(code).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, body: any) { + response.status(200).json(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..cb1abcb8d5 100644 --- a/packages/openid4vc/src/shared/utils.ts +++ b/packages/openid4vc/src/shared/utils.ts @@ -141,11 +141,19 @@ export async function openIdTokenIssuerToJwtIssuer( } 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)` ) } diff --git a/packages/openid4vc/tests/openid4vc.e2e.test.ts b/packages/openid4vc/tests/openid4vc.e2e.test.ts index 1409395523..872f4cdcd0 100644 --- a/packages/openid4vc/tests/openid4vc.e2e.test.ts +++ b/packages/openid4vc/tests/openid4vc.e2e.test.ts @@ -2,6 +2,7 @@ import type { AgentType, TenantType } from './utils' import type { OpenId4VciCredentialBindingResolver } from '../src/openid4vc-holder' import type { DifPresentationExchangeDefinitionV2, SdJwtVc } from '@credo-ts/core' import type { Server } from 'http' +import { createHttpsProxy, disableSslVerification } from '../../../tests/https-proxy' import { CredoError, @@ -48,14 +49,17 @@ import { } from './utilsVci' import { openBadgePresentationDefinition, universityDegreePresentationDefinition } from './utilsVp' +const proxyPort = 3443 const serverPort = 1234 -const baseUrl = `http://localhost:${serverPort}` +const baseUrl = `https://localhost:${proxyPort}` const issuanceBaseUrl = `${baseUrl}/oid4vci` const verificationBaseUrl = `${baseUrl}/oid4vp` describe('OpenId4Vc', () => { let expressApp: Express let expressServer: Server + let proxyServer: Server + let resetSslVerification: () => void let issuer: AgentType<{ openId4VcIssuer: OpenId4VcIssuerModule @@ -78,6 +82,14 @@ describe('OpenId4Vc', () => { let verifier1: TenantType let verifier2: TenantType + beforeAll(() => { + resetSslVerification = disableSslVerification() + }) + + afterAll(() => { + resetSslVerification() + }) + beforeEach(async () => { expressApp = express() @@ -125,7 +137,7 @@ describe('OpenId4Vc', () => { tenants: new TenantsModule(), }, '96213c3d7fc8d4d6754c7a0fd969598g' - )) as unknown as typeof issuer + )) as unknown as typeof issuer issuer1 = await createTenantForAgent(issuer.agent, 'iTenant1') issuer2 = await createTenantForAgent(issuer.agent, 'iTenant2') @@ -160,10 +172,15 @@ describe('OpenId4Vc', () => { expressApp.use('/oid4vp', verifier.agent.modules.openId4VcVerifier.config.router) expressServer = expressApp.listen(serverPort) + proxyServer = createHttpsProxy({ + port: proxyPort, + target: `http://localhost:${serverPort}`, + }) }) afterEach(async () => { expressServer?.close() + proxyServer?.close() await issuer.agent.shutdown() await issuer.agent.wallet.delete() @@ -269,26 +286,28 @@ 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'], + proof_types_supported: { + jwt: { + proof_signing_alg_values_supported: ['EdDSA'], + }, + }, 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` ) @@ -345,13 +364,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` ) @@ -397,6 +416,90 @@ describe('OpenId4Vc', () => { await holderTenant1.endSession() }) + it.skip('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 openIdIssuerTenant = await issuerTenant.modules.openId4VcIssuer.createIssuer({ + dpopSigningAlgValuesSupported: [JwaSignatureAlgorithm.EdDSA], + credentialConfigurationsSupported: { + universityDegree: universityDegreeCredentialConfigurationSupported, + }, + authorizationServerConfigs: [ + { + issuer: 'https://localhost:3042', + serverType: 'oidc', + clientId: 'issuer-server', + clientSecret: 'issuer-server', + }, + ], + }) + + const { issuanceSession, credentialOffer } = await issuerTenant.modules.openId4VcIssuer.createCredentialOffer({ + issuerId: openIdIssuerTenant.issuerId, + offeredCredentials: ['universityDegree'], + authorizationCodeFlowConfig: { + authorizationServerUrl: 'https://localhost:3042', + }, + version: 'v1.draft13', + }) + + await issuerTenant.endSession() + + const resolvedCredentialOffer = await holderTenant.modules.openId4VcHolder.resolveCredentialOffer(credentialOffer) + + console.log(JSON.stringify(resolvedCredentialOffer, null, 2)) + + let code = new Promise((resolve) => { + expressApp.get('/redirect', (req, res) => { + console.log('incoming request', req.query) + if (req.query.code) { + resolve(req.query.code as string) + } + res.send('') + }) + }) + + const auth = await holderTenant.modules.openId4VcHolder.resolveIssuanceAuthorizationRequest( + resolvedCredentialOffer, + { + clientId: 'foo', + redirectUri: 'https://localhost:3443/redirect', + scope: ['UniversityDegreeCredential'], + } + ) + + console.log(JSON.stringify(auth)) + + // Bind to JWK + const tokenResponseTenant = await holderTenant.modules.openId4VcHolder.requestToken({ + resolvedCredentialOffer, + code: await code, + resolvedAuthorizationRequest: auth, + }) + + console.log(JSON.stringify(tokenResponseTenant)) + + const credentials = await holderTenant.modules.openId4VcHolder.requestCredentials({ + resolvedCredentialOffer, + ...tokenResponseTenant, + credentialBindingResolver, + }) + + await waitForCredentialIssuanceSessionRecordSubject(issuer.replaySubject, { + state: OpenId4VcIssuanceSessionState.Completed, + issuanceSessionId: issuanceSession.id, + contextCorrelationId: issuer1.tenantId, + }) + + expect(credentials).toHaveLength(1) + const compactSdJwtVcTenant1 = (credentials[0].credential as SdJwtVc).compact + const sdJwtVcTenant1 = holderTenant.sdJwtVc.fromCompact(compactSdJwtVcTenant1) + expect(sdJwtVcTenant1.payload.vct).toEqual('UniversityDegreeCredential') + + await holderTenant.endSession() + }) + 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 }) @@ -731,7 +834,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:${proxyPort}` }]], }) const rawCertificate = certificate.toString('base64') @@ -769,12 +872,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, @@ -789,28 +886,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( - `openid4vp://?client_id=localhost%3A1234&request_uri=${encodeURIComponent( + expect(authorizationRequest).toEqual( + `openid4vp://?client_id=localhost%3A3443&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') @@ -863,107 +946,108 @@ describe('OpenId4Vc', () => { 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, - presentationExchange: { - credentials: selectedCredentials, + try { + const { serverResponse, submittedResponse } = + await holder.agent.modules.openId4VcHolder.acceptSiopAuthorizationRequest({ + authorizationRequest: resolvedAuthorizationRequest.authorizationRequest, + presentationExchange: { + credentials: selectedCredentials, + }, + }) + + // path_nested should not be used for sd-jwt + expect(submittedResponse.presentation_submission?.descriptor_map[0].path_nested).toBeUndefined() + expect(submittedResponse).toEqual({ + presentation_submission: { + definition_id: 'OpenBadgeCredential', + descriptor_map: [ + { + format: 'vc+sd-jwt', + id: 'OpenBadgeCredentialDescriptor', + path: '$', + }, + ], + id: expect.any(String), }, + state: expect.any(String), + vp_token: expect.any(String), + }) + expect(serverResponse).toMatchObject({ + status: 200, }) - // path_nested should not be used for sd-jwt - expect(submittedResponse.presentation_submission?.descriptor_map[0].path_nested).toBeUndefined() - expect(submittedResponse).toEqual({ - presentation_submission: { - definition_id: 'OpenBadgeCredential', - descriptor_map: [ - { - format: 'vc+sd-jwt', - id: 'OpenBadgeCredentialDescriptor', - path: '$', - }, - ], - id: expect.any(String), - }, - state: expect.any(String), - vp_token: expect.any(String), - }) - expect(serverResponse).toMatchObject({ - status: 200, - }) - - // The RP MUST validate that the aud (audience) Claim contains the value of the client_id - // that the RP sent in the Authorization Request as an audience. - // When the request has been signed, the value might be an HTTPS URL, or a Decentralized Identifier. - await waitForVerificationSessionRecordSubject(verifier.replaySubject, { - contextCorrelationId: verifier.agent.context.contextCorrelationId, - state: OpenId4VcVerificationSessionState.ResponseVerified, - verificationSessionId: verificationSession.id, - }) - const { idToken, presentationExchange } = - await verifier.agent.modules.openId4VcVerifier.getVerifiedAuthorizationResponse(verificationSession.id) + // The RP MUST validate that the aud (audience) Claim contains the value of the client_id + // that the RP sent in the Authorization Request as an audience. + // When the request has been signed, the value might be an HTTPS URL, or a Decentralized Identifier. + await waitForVerificationSessionRecordSubject(verifier.replaySubject, { + contextCorrelationId: verifier.agent.context.contextCorrelationId, + state: OpenId4VcVerificationSessionState.ResponseVerified, + verificationSessionId: verificationSession.id, + }) + const { idToken, presentationExchange } = + await verifier.agent.modules.openId4VcVerifier.getVerifiedAuthorizationResponse(verificationSession.id) - expect(idToken).toBeUndefined() + expect(idToken).toBeUndefined() - const presentation = presentationExchange?.presentations[0] as SdJwtVc + const presentation = presentationExchange?.presentations[0] as SdJwtVc - // name SHOULD NOT be disclosed - expect(presentation.prettyClaims).not.toHaveProperty('name') + // name SHOULD NOT be disclosed + expect(presentation.prettyClaims).not.toHaveProperty('name') - // university and name SHOULD NOT be in the signed payload - expect(presentation.payload).not.toHaveProperty('university') - expect(presentation.payload).not.toHaveProperty('name') + // university and name SHOULD NOT be in the signed payload + expect(presentation.payload).not.toHaveProperty('university') + expect(presentation.payload).not.toHaveProperty('name') - expect(presentationExchange).toEqual({ - definition: presentationDefinition, - submission: { - definition_id: 'OpenBadgeCredential', - descriptor_map: [ + expect(presentationExchange).toEqual({ + definition: presentationDefinition, + submission: { + definition_id: 'OpenBadgeCredential', + descriptor_map: [ + { + format: 'vc+sd-jwt', + id: 'OpenBadgeCredentialDescriptor', + path: '$', + }, + ], + id: expect.any(String), + }, + presentations: [ { - format: 'vc+sd-jwt', - id: 'OpenBadgeCredentialDescriptor', - path: '$', - }, - ], - id: expect.any(String), - }, - presentations: [ - { - compact: expect.any(String), - header: { - alg: 'EdDSA', - kid: '#z6MkrzQPBr4pyqC776KKtrz13SchM5ePPbssuPuQZb5t4uKQ', - typ: 'vc+sd-jwt', - }, - payload: { - _sd: [expect.any(String), expect.any(String)], - _sd_alg: 'sha-256', - cnf: { - kid: 'did:key:z6MkpGR4gs4Rc3Zph4vj8wRnjnAxgAPSxcR8MAVKutWspQzc#z6MkpGR4gs4Rc3Zph4vj8wRnjnAxgAPSxcR8MAVKutWspQzc', + compact: expect.any(String), + header: { + alg: 'EdDSA', + kid: '#z6MkrzQPBr4pyqC776KKtrz13SchM5ePPbssuPuQZb5t4uKQ', + typ: 'vc+sd-jwt', }, - iat: expect.any(Number), - iss: 'did:key:z6MkrzQPBr4pyqC776KKtrz13SchM5ePPbssuPuQZb5t4uKQ', - vct: 'OpenBadgeCredential', - degree: 'bachelor', - }, - // university SHOULD be disclosed - prettyClaims: { - cnf: { - kid: 'did:key:z6MkpGR4gs4Rc3Zph4vj8wRnjnAxgAPSxcR8MAVKutWspQzc#z6MkpGR4gs4Rc3Zph4vj8wRnjnAxgAPSxcR8MAVKutWspQzc', + payload: { + _sd: [expect.any(String), expect.any(String)], + _sd_alg: 'sha-256', + cnf: { + kid: 'did:key:z6MkpGR4gs4Rc3Zph4vj8wRnjnAxgAPSxcR8MAVKutWspQzc#z6MkpGR4gs4Rc3Zph4vj8wRnjnAxgAPSxcR8MAVKutWspQzc', + }, + iat: expect.any(Number), + iss: 'did:key:z6MkrzQPBr4pyqC776KKtrz13SchM5ePPbssuPuQZb5t4uKQ', + vct: 'OpenBadgeCredential', + degree: 'bachelor', + }, + // university SHOULD be disclosed + prettyClaims: { + cnf: { + kid: 'did:key:z6MkpGR4gs4Rc3Zph4vj8wRnjnAxgAPSxcR8MAVKutWspQzc#z6MkpGR4gs4Rc3Zph4vj8wRnjnAxgAPSxcR8MAVKutWspQzc', + }, + iat: expect.any(Number), + iss: 'did:key:z6MkrzQPBr4pyqC776KKtrz13SchM5ePPbssuPuQZb5t4uKQ', + vct: 'OpenBadgeCredential', + degree: 'bachelor', + university: 'innsbruck', }, - iat: expect.any(Number), - iss: 'did:key:z6MkrzQPBr4pyqC776KKtrz13SchM5ePPbssuPuQZb5t4uKQ', - vct: 'OpenBadgeCredential', - degree: 'bachelor', - university: 'innsbruck', }, - }, - ], - }) + ], + }) + } catch (error) { + console.error('error', error) + throw error + } }) it('e2e flow with verifier endpoints verifying a sd-jwt-vc with selective disclosure', async () => { @@ -988,7 +1072,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:${proxyPort}` }]], }) const rawCertificate = certificate.toString('base64') @@ -1026,12 +1110,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, @@ -1045,28 +1123,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( - `openid4vp://?client_id=localhost%3A1234&request_uri=${encodeURIComponent( + expect(authorizationRequest).toEqual( + `openid4vp://?client_id=localhost%3A3443&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({ @@ -1118,10 +1182,6 @@ describe('OpenId4Vc', () => { 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, @@ -1260,7 +1320,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:${proxyPort}` }]], }) const rawCertificate = certificate.toString('base64') @@ -1322,11 +1382,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, @@ -1340,28 +1395,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( - `openid4vp://?client_id=localhost%3A1234&request_uri=${encodeURIComponent( + expect(authorizationRequest).toEqual( + `openid4vp://?client_id=localhost%3A3443&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({ @@ -1443,10 +1484,6 @@ describe('OpenId4Vc', () => { 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, diff --git a/packages/openid4vc/tests/utils.ts b/packages/openid4vc/tests/utils.ts index d076e4b65c..c11c752a63 100644 --- a/packages/openid4vc/tests/utils.ts +++ b/packages/openid4vc/tests/utils.ts @@ -10,7 +10,6 @@ import type { Observable } from 'rxjs' import { Agent, LogLevel, utils } from '@credo-ts/core' import { ReplaySubject, lastValueFrom, filter, timeout, catchError, take, map } from 'rxjs' - import { TestLogger, agentDependencies, @@ -19,10 +18,13 @@ import { } from '../../core/tests' import { OpenId4VcVerifierEvents, OpenId4VcIssuerEvents } 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, + dependencies: { + ...agentDependencies, + fetch: customFetch ?? agentDependencies.fetch + }, modules: modulesMap, }) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a623a35224..bf31285cae 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 @@ -53,6 +60,9 @@ importers: '@typescript-eslint/parser': specifier: ^7.14.1 version: 7.18.0(eslint@8.57.0)(typescript@5.5.4) + babel-jest: + specifier: ^29.7.0 + version: 29.7.0(@babel/core@7.25.8) bn.js: specifier: ^5.2.1 version: 5.2.1 @@ -80,6 +90,9 @@ importers: express: specifier: ^4.17.1 version: 4.19.2 + http-proxy-middleware: + specifier: ^3.0.3 + version: 3.0.3 jest: specifier: ^29.7.0 version: 29.7.0(@types/node@18.18.8)(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4)) @@ -89,9 +102,12 @@ importers: rxjs: specifier: ^7.8.0 version: 7.8.1 + selfsigned: + specifier: ^2.4.1 + version: 2.4.1 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(@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(@types/node@18.18.8)(typescript@5.5.4) @@ -101,6 +117,9 @@ importers: 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 @@ -312,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 @@ -342,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 @@ -419,13 +438,13 @@ importers: dependencies: '@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 @@ -695,6 +714,9 @@ importers: packages/openid4vc: dependencies: + '@animo-id/oid4vci': + specifier: 0.0.2-alpha-20241024080134 + version: 0.0.2-alpha-20241024080134(typescript@5.5.4) '@credo-ts/core': specifier: workspace:* version: link:../core @@ -719,9 +741,15 @@ importers: class-transformer: specifier: ^0.5.1 version: 0.5.1 + oauth4webapi: + specifier: ^3.0.1 + version: 3.0.1 rxjs: specifier: ^7.8.0 version: 7.8.1 + zod: + specifier: ^3.23.8 + version: 3.23.8 devDependencies: '@credo-ts/tenants': specifier: workspace:* @@ -784,13 +812,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 @@ -906,6 +934,9 @@ packages: react: '*' react-native: '*' + '@animo-id/oid4vci@0.0.2-alpha-20241024080134': + resolution: {integrity: sha512-0+t/xQhQbaFKa8huQBrDWT87LqZ6E/7zecLYj+WOZVV/vrNnk6RvmTMy3zDh0NBXRx28cw12qI+OwdoVtGegCQ==} + '@animo-id/react-native-bbs-signatures@0.1.0': resolution: {integrity: sha512-7qvsiWhGfUev8ngE8YzF6ON9PtCID5LiYVYM4EC5eyj80gCdhx3R46CI7K1qbqIlGsoTYQ/Xx5Ubo5Ji9eaUEA==} peerDependencies: @@ -926,14 +957,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==} @@ -941,30 +984,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: @@ -978,99 +1045,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 @@ -1205,8 +1343,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 @@ -1217,6 +1355,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: @@ -1293,8 +1437,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 @@ -1305,26 +1455,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 @@ -1335,50 +1503,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 @@ -1395,14 +1581,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 @@ -1413,8 +1611,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 @@ -1425,8 +1629,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 @@ -1437,14 +1647,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 @@ -1455,26 +1671,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 @@ -1485,14 +1707,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 @@ -1503,14 +1731,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 @@ -1521,6 +1755,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'} @@ -1557,14 +1797,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 @@ -1581,26 +1821,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 @@ -1611,14 +1875,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 @@ -1629,14 +1893,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 @@ -1681,14 +1951,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==} @@ -2141,10 +2423,6 @@ 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: @@ -2851,6 +3129,9 @@ packages: '@types/http-errors@2.0.4': resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==} + '@types/http-proxy@1.17.15': + resolution: {integrity: sha512-25g5atgiVNTIv0LBDTg1H74Hvayx0ajtJPLLcYE3whFv75J0pWNtOBzaXJQgDTmrX1bx5U9YC2w/n65BN1HwRQ==} + '@types/inquirer@8.2.10': resolution: {integrity: sha512-IdD5NmHyVjWM8SHWo/kPBgtzXatwPkfwzyP3fN1jF2g9BWt5WO+8hL2F4o2GKIYsU40PpqeevuUWvkS/roXJkA==} @@ -3460,6 +3741,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'} @@ -3554,6 +3840,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==} @@ -4066,6 +4355,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==} @@ -4313,6 +4605,9 @@ packages: resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} engines: {node: '>=6'} + eventemitter3@4.0.7: + resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + events@3.3.0: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} @@ -4827,6 +5122,14 @@ packages: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} engines: {node: '>= 0.8'} + http-proxy-middleware@3.0.3: + resolution: {integrity: sha512-usY0HG5nyDUwtqpiZdETNbmKtw3QQ1jwYFZ9wi5iHzX2BcILwQKtYDJPo7XHTsu5Z0B2Hj3W9NNnbd+AjFWjqg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + http-proxy@1.18.1: + resolution: {integrity: sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==} + engines: {node: '>=8.0.0'} + http2-wrapper@2.2.1: resolution: {integrity: sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==} engines: {node: '>=10.19.0'} @@ -5042,6 +5345,10 @@ packages: resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==} engines: {node: '>=0.10.0'} + is-plain-object@5.0.0: + resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} + engines: {node: '>=0.10.0'} + is-regex@1.1.4: resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} engines: {node: '>= 0.4'} @@ -5827,6 +6134,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'} @@ -6098,6 +6409,9 @@ packages: nullthrows@1.1.1: resolution: {integrity: sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==} + oauth4webapi@3.0.1: + resolution: {integrity: sha512-72NtHQg8vCvm62NE7ckW9brybI3yBaFka5BeEs7Z4jws/k3W1e5UAt61icij/dL1cMnVpxul5jfkMcCTHLXqUQ==} + ob1@0.73.10: resolution: {integrity: sha512-aO6EYC+QRRCkZxVJhCWhLKgVjhNuD6Gu1riGjxrIm89CqLsmKgxzYDDEsktmKsoDeRdWGQM5EdMzXDl5xcVfsw==} @@ -6628,6 +6942,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==} @@ -6648,6 +6966,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 @@ -7448,9 +7777,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==} @@ -7848,19 +8177,26 @@ 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/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/oid4vci@0.0.2-alpha-20241024080134(typescript@5.5.4)': + dependencies: + buffer: 6.0.3 + 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: @@ -7870,15 +8206,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 @@ -7899,9 +8242,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 @@ -7914,14 +8277,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 @@ -7933,6 +8307,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 @@ -7946,6 +8328,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 @@ -7953,6 +8374,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 @@ -7964,17 +8406,35 @@ 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/types': 7.25.2 - - '@babel/helper-member-expression-to-functions@7.24.8': + '@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 + + '@babel/helper-member-expression-to-functions@7.24.8': dependencies: '@babel/traverse': 7.25.3 '@babel/types': 7.25.2 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 @@ -7982,6 +8442,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 @@ -7992,12 +8459,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 @@ -8007,6 +8510,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 @@ -8016,6 +8546,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 @@ -8023,6 +8580,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 @@ -8030,12 +8594,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 @@ -8044,11 +8621,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 @@ -8056,42 +8646,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-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.0(@babel/core@7.25.2)': + '@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-id-destructuring-collision-in-function-expression@7.25.0(@babel/core@7.25.2)': + '@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.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-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.24.7(@babel/core@7.25.2)': + '@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-static-class-fields-redefine-readonly@7.25.0(@babel/core@7.25.2)': + '@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.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 @@ -8105,6 +8741,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 @@ -8113,14 +8759,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: @@ -8128,6 +8792,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 @@ -8137,8 +8807,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: @@ -8146,11 +8823,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: @@ -8161,12 +8851,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 @@ -8176,18 +8881,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)': @@ -8195,26 +8918,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 @@ -8225,24 +8969,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)': @@ -8250,44 +9014,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)': @@ -8295,24 +9096,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 @@ -8325,30 +9160,102 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/plugin-transform-async-to-generator@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/helper-remap-async-to-generator': 7.25.0(@babel/core@7.25.8) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-async-to-generator@7.25.7(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@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-async-to-generator@7.25.7(@babel/core@7.25.8)': + dependencies: + '@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-block-scoped-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-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-class-properties@7.24.7(@babel/core@7.25.2)': + '@babel/plugin-transform-block-scoping@7.25.0(@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-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-static-block@7.24.7(@babel/core@7.25.2)': + '@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.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) + '@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 @@ -8364,53 +9271,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-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-duplicate-named-capturing-groups-regex@7.25.0(@babel/core@7.25.2)': + '@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-export-namespace-from@7.24.7(@babel/core@7.25.2)': + '@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.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: @@ -8418,6 +9427,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 @@ -8426,6 +9441,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 @@ -8435,33 +9474,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 @@ -8474,21 +9586,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 @@ -8498,30 +9655,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-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-object-rest-spread@7.24.7(@babel/core@7.25.2)': + '@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: @@ -8531,18 +9725,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 @@ -8551,21 +9780,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.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-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-create-class-features-plugin': 7.25.0(@babel/core@7.25.2) - '@babel/helper-plugin-utils': 7.24.8 + '@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.24.7(@babel/core@7.25.2)': + '@babel/plugin-transform-private-property-in-object@7.25.8(@babel/core@7.25.8)': 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/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 @@ -8574,28 +9834,66 @@ 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/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 @@ -8607,22 +9905,51 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-transform-react-pure-annotations@7.24.7(@babel/core@7.25.2)': + '@babel/plugin-transform-react-jsx@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-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-regenerator@7.24.7(@babel/core@7.25.2)': + '@babel/plugin-transform-react-pure-annotations@7.24.7(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.24.8 + '@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-reserved-words@7.24.7(@babel/core@7.25.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.24.8 + '@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: @@ -8636,11 +9963,38 @@ snapshots: 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 @@ -8649,21 +10003,80 @@ snapshots: 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-spread@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-sticky-regex@7.24.7(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 + '@babel/plugin-transform-sticky-regex@7.24.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.24.8 + + '@babel/plugin-transform-sticky-regex@7.25.7(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-sticky-regex@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + '@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-typeof-symbol@7.24.8(@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/plugin-transform-template-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-template-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-typeof-symbol@7.25.7(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@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: '@babel/core': 7.25.2 @@ -8675,16 +10088,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: @@ -8692,92 +10127,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) @@ -8787,6 +10231,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 @@ -8797,21 +10315,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: @@ -8824,6 +10362,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 @@ -8845,6 +10394,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 @@ -8857,12 +10412,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': @@ -9164,7 +10737,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 @@ -9200,11 +10773,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: @@ -9228,12 +10801,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 @@ -9241,10 +10814,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: @@ -9253,10 +10826,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: @@ -9267,19 +10840,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 @@ -9287,10 +10860,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 @@ -9298,14 +10871,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: @@ -9667,10 +11240,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 @@ -9702,7 +11275,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 @@ -9761,8 +11334,6 @@ 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 @@ -9991,7 +11562,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 @@ -10407,6 +11978,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 @@ -10468,16 +12059,50 @@ snapshots: - 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.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 + 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/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) @@ -10493,47 +12118,111 @@ snapshots: '@babel/plugin-syntax-export-default-from': 7.24.7(@babel/core@7.25.2) '@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-syntax-optional-chaining': 7.8.3(@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: @@ -11247,6 +12936,10 @@ snapshots: '@types/http-errors@2.0.4': {} + '@types/http-proxy@1.17.15': + dependencies: + '@types/node': 18.18.8 + '@types/inquirer@8.2.10': dependencies: '@types/through': 0.0.33 @@ -11770,7 +13463,7 @@ snapshots: axios@0.21.4: dependencies: - follow-redirects: 1.15.6 + follow-redirects: 1.15.6(debug@4.3.6) transitivePeerDependencies: - debug @@ -11786,13 +13479,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 @@ -11825,6 +13518,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 @@ -11833,6 +13535,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 @@ -11840,10 +13550,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 @@ -11859,35 +13576,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 @@ -11929,11 +13671,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: {} @@ -12048,6 +13823,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 @@ -12151,6 +13933,8 @@ snapshots: caniuse-lite@1.0.30001651: {} + caniuse-lite@1.0.30001668: {} + canonicalize@1.0.8: {} canonicalize@2.0.0: {} @@ -12674,6 +14458,8 @@ snapshots: electron-to-chromium@1.5.13: {} + electron-to-chromium@1.5.38: {} + elliptic@6.5.4: dependencies: bn.js: 4.12.0 @@ -13045,6 +14831,8 @@ snapshots: event-target-shim@5.0.1: {} + eventemitter3@4.0.7: {} + events@3.3.0: {} exec-async@2.2.0: {} @@ -13081,35 +14869,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.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-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.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: @@ -13134,13 +14957,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) @@ -13148,11 +14997,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 @@ -13364,7 +15213,7 @@ snapshots: find-yarn-workspace-root@2.0.0: dependencies: - micromatch: 4.0.7 + micromatch: 4.0.8 fix-esm@1.0.1: dependencies: @@ -13384,7 +15233,9 @@ snapshots: flow-parser@0.185.2: {} - follow-redirects@1.15.6: {} + follow-redirects@1.15.6(debug@4.3.6): + optionalDependencies: + debug: 4.3.6 fontfaceobserver@2.3.0: {} @@ -13713,6 +15564,25 @@ snapshots: statuses: 2.0.1 toidentifier: 1.0.1 + http-proxy-middleware@3.0.3: + dependencies: + '@types/http-proxy': 1.17.15 + debug: 4.3.6 + http-proxy: 1.18.1(debug@4.3.6) + is-glob: 4.0.3 + is-plain-object: 5.0.0 + micromatch: 4.0.8 + transitivePeerDependencies: + - supports-color + + http-proxy@1.18.1(debug@4.3.6): + dependencies: + eventemitter3: 4.0.7 + follow-redirects: 1.15.6(debug@4.3.6) + requires-port: 1.0.0 + transitivePeerDependencies: + - debug + http2-wrapper@2.2.1: dependencies: quick-lru: 5.1.1 @@ -13915,6 +15785,8 @@ snapshots: dependencies: isobject: 3.0.1 + is-plain-object@5.0.0: {} + is-regex@1.1.4: dependencies: call-bind: 1.0.7 @@ -13972,7 +15844,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 @@ -13984,8 +15856,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 @@ -13998,7 +15870,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 @@ -14008,7 +15880,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 @@ -14101,10 +15973,10 @@ snapshots: 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 @@ -14295,15 +16167,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 @@ -14500,7 +16372,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 @@ -14508,7 +16405,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) @@ -15009,6 +16906,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 @@ -15021,6 +16961,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 @@ -15149,6 +17101,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: {} @@ -15377,6 +17334,8 @@ snapshots: nullthrows@1.1.1: {} + oauth4webapi@3.0.1: {} + ob1@0.73.10: {} object-assign@4.1.1: {} @@ -15845,36 +17804,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) @@ -15902,7 +17871,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) @@ -15996,6 +18011,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: {} @@ -16022,6 +18041,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 @@ -16599,7 +18633,7 @@ 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(@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 @@ -16613,10 +18647,10 @@ 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(@types/node@18.18.8)(typescript@5.5.4): dependencies: @@ -16792,9 +18826,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: {} @@ -16837,6 +18869,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 diff --git a/tests/https-proxy.ts b/tests/https-proxy.ts new file mode 100644 index 0000000000..bf398125c6 --- /dev/null +++ b/tests/https-proxy.ts @@ -0,0 +1,71 @@ +import express from 'express' +import { createProxyMiddleware } from 'http-proxy-middleware' +import selfsigned from 'selfsigned' +import https, { globalAgent } from 'https' +import { setGlobalDispatcher, Agent, getGlobalDispatcher } from 'undici' +import { agentDependencies } from '@credo-ts/node' + +export function disableSslVerification() { + + const originalDispatcher = getGlobalDispatcher() + // Works for global fetch (undici) + const dispatcher = new Agent({ + connect: { + rejectUnauthorized: false, + }, + }) + const originalFetch = fetch + global.fetch = (url, options) => + // @ts-ignore + originalFetch(url, { + ...options, + dispatcher, + }) + agentDependencies.fetch = global.fetch + setGlobalDispatcher(dispatcher) + + // We use a self-signed certificate and so we need to disable invalid ssl certificates + // Works for node-fetch (cross-fetch) + globalAgent.options.rejectUnauthorized = false + + return () => { + global.fetch = originalFetch + agentDependencies.fetch = originalFetch + setGlobalDispatcher(originalDispatcher) + globalAgent.options.rejectUnauthorized = true + } +} + +export function createHttpsProxy({ target, port }: { target: string; port: number }) { + // Generate certificates on the fly + const attrs = [{ name: 'commonName', value: `localhost:${port}` }] + const pems = selfsigned.generate(attrs, { + algorithm: 'sha256', + days: 30, + keySize: 2048, + }) + + const app = express() + + // Configure proxy middleware + const proxy = createProxyMiddleware({ + target, + changeOrigin: true, + secure: false, + }) + + // Use proxy for all routes + app.use(proxy) + + const server = https + .createServer( + { + key: pems.private, + cert: pems.cert, + }, + app + ) + .listen(port) + + return server +} From c0b49dcb8c83e187cb86853501e13f7f5cc774b9 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Wed, 30 Oct 2024 23:57:38 +0530 Subject: [PATCH 6/9] temp Signed-off-by: Timo Glastra --- .../OpenId4VciHolderServiceOptions.ts | 2 +- .../router/credentialEndpoint.ts | 2 +- .../router/issuerMetadataEndpoint.ts | 4 +- .../src/shared/issuerMetadataUtils.ts | 32 --- packages/openid4vc/src/shared/models/index.ts | 8 +- pnpm-lock.yaml | 207 +++++++++++++++++- 6 files changed, 208 insertions(+), 47 deletions(-) diff --git a/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderServiceOptions.ts b/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderServiceOptions.ts index c2f19d994f..24e92d0a48 100644 --- a/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderServiceOptions.ts +++ b/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderServiceOptions.ts @@ -8,7 +8,7 @@ import type { } from '@sphereon/oid4vci-common' import { OpenId4VciCredentialFormatProfile } from '../shared/models/OpenId4VciCredentialFormatProfile' -import { CredentialConfigurationSupported, CredentialIssuerMetadata, CredentialOfferObject, IssuerMetadataResult } from '@animo-id/oid4vci' +import { CredentialConfigurationSupported, CredentialOfferObject, IssuerMetadataResult } from '@animo-id/oid4vci' export type OpenId4VciSupportedCredentialFormats = | OpenId4VciCredentialFormatProfile.JwtVcJson diff --git a/packages/openid4vc/src/openid4vc-issuer/router/credentialEndpoint.ts b/packages/openid4vc/src/openid4vc-issuer/router/credentialEndpoint.ts index f97c2fb58d..95fdd4ca61 100644 --- a/packages/openid4vc/src/openid4vc-issuer/router/credentialEndpoint.ts +++ b/packages/openid4vc/src/openid4vc-issuer/router/credentialEndpoint.ts @@ -40,7 +40,7 @@ export function configureCredentialEndpoint(router: Router, config: OpenId4VciCr if (!verifyAccessTokenResult) return try { - const credentialRequest = request.body as OpenId4VciCredentialRequest + const credentialRequest = request.body const issuanceSessionRepository = agentContext.dependencyManager.resolve(OpenId4VcIssuanceSessionRepository) let issuanceSession: OpenId4VcIssuanceSessionRecord | null diff --git a/packages/openid4vc/src/openid4vc-issuer/router/issuerMetadataEndpoint.ts b/packages/openid4vc/src/openid4vc-issuer/router/issuerMetadataEndpoint.ts index aad1ad0ab3..2d0cc37a89 100644 --- a/packages/openid4vc/src/openid4vc-issuer/router/issuerMetadataEndpoint.ts +++ b/packages/openid4vc/src/openid4vc-issuer/router/issuerMetadataEndpoint.ts @@ -1,5 +1,5 @@ import type { OpenId4VcIssuanceRequest } from './requestContext' -import type { CredentialIssuerMetadata } from '@sphereon/oid4vci-common' +import type { OpenId4VciIssuerMetadata } from '../../shared' import type { Router, Response } from 'express' import { getRequestContext, sendErrorResponse, sendJsonResponse } from '../../shared/router' @@ -31,7 +31,7 @@ export function configureIssuerMetadataEndpoint(router: Router) { // metadata. For backwards compatiblity we will keep them in now. token_endpoint: issuerMetadata.tokenEndpoint, dpop_signing_alg_values_supported: issuerMetadata.dpopSigningAlgValuesSupported, - } satisfies CredentialIssuerMetadata + } satisfies OpenId4VciIssuerMetadata return sendJsonResponse(response, next, transformedMetadata) } catch (e) { diff --git a/packages/openid4vc/src/shared/issuerMetadataUtils.ts b/packages/openid4vc/src/shared/issuerMetadataUtils.ts index f9d2fb0a84..06156f162f 100644 --- a/packages/openid4vc/src/shared/issuerMetadataUtils.ts +++ b/packages/openid4vc/src/shared/issuerMetadataUtils.ts @@ -5,45 +5,13 @@ import type { OpenId4VciCredentialSupportedWithId, } 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 { CredentialConfigurationSupported } 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 - */ -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 - } - throw Error(`Unable to extract types from credentials supported. Unknown format ${credentialSupported.format}`) -} export function credentialConfigurationSupportedToCredentialSupported( id: string, diff --git a/packages/openid4vc/src/shared/models/index.ts b/packages/openid4vc/src/shared/models/index.ts index 7df4df89eb..60ef033798 100644 --- a/packages/openid4vc/src/shared/models/index.ts +++ b/packages/openid4vc/src/shared/models/index.ts @@ -1,4 +1,4 @@ -import { CredentialConfigurationSupported, CredentialIssuerMetadata } from '@animo-id/oid4vci'; +import { CredentialConfigurationSupported, CredentialIssuerMetadata, CredentialRequest } from '@animo-id/oid4vci'; import type { VerifiedAuthorizationRequest, AuthorizationRequestPayload, @@ -7,9 +7,6 @@ import type { } from '@sphereon/did-auth-siop' import type { AssertedUniformCredentialOffer, - CredentialConfigurationSupportedV1_0_13, - CredentialIssuerMetadataV1_0_11, - CredentialIssuerMetadataV1_0_13, CredentialOfferPayloadV1_0_11, CredentialOfferPayloadV1_0_13, CredentialRequestJwtVcJson, @@ -20,7 +17,6 @@ import type { CredentialsSupportedLegacy, MetadataDisplay, TxCode, - UniformCredentialRequest, } from '@sphereon/oid4vci-common' export type OpenId4VciCredentialSupported = CredentialsSupportedLegacy & { id?: string; scope?: string } @@ -33,7 +29,7 @@ export type OpenId4VciTxCode = TxCode export type OpenId4VciIssuerMetadata = CredentialIssuerMetadata export type OpenId4VciIssuerMetadataDisplay = MetadataDisplay -export type OpenId4VciCredentialRequest = UniformCredentialRequest +export type OpenId4VciCredentialRequest = CredentialRequest export type OpenId4VciCredentialRequestJwtVcJson = CredentialRequestJwtVcJson | CredentialRequestJwtVcJsonV1_0_13 diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bf31285cae..fb1425a95f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -488,8 +488,8 @@ importers: specifier: ^2.3.1 version: 2.3.1 '@sphereon/ssi-types': - specifier: 0.29.1-unstable.121 - version: 0.29.1-unstable.121(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4)) + specifier: ^0.30.1 + version: 0.30.1(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4)) '@stablelib/ed25519': specifier: ^1.0.2 version: 1.0.3 @@ -736,8 +736,8 @@ importers: specifier: 0.16.1-next.66 version: 0.16.1-next.66 '@sphereon/ssi-types': - specifier: 0.29.1-unstable.121 - version: 0.29.1-unstable.121(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4)) + specifier: ^0.30.1 + version: 0.30.1(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4)) class-transformer: specifier: ^0.5.1 version: 0.5.1 @@ -2915,7 +2915,6 @@ packages: '@sphereon/kmp-mdl-mdoc@0.2.0-SNAPSHOT.22': resolution: {integrity: sha512-uAZZExVy+ug9JLircejWa5eLtAZ7bnBP6xb7DO2+86LRsHNLh2k2jMWJYxp+iWtGHTsh6RYsZl14ScQLvjiQ/A==} - bundledDependencies: [] '@sphereon/oid4vc-common@0.16.1-next.66': resolution: {integrity: sha512-PqMFDMfWHn9jiUxa5k018huQTYxHfYJJstQNyRrle/pV37gv6Lh+yjwML6LE8eUQksw05tfXeq1hk8M2Gl1HWA==} @@ -2948,21 +2947,39 @@ packages: '@sphereon/ssi-sdk-ext.did-utils@0.24.1-unstable.112': resolution: {integrity: sha512-nc0jFPOWg0H20S8m83aQUpNym0Wx0rJCGkgpH6GdK8gBtgza8Y9DvAap1AYZug18WbqPcF6rBjvtIJqAKsSvlQ==} + '@sphereon/ssi-sdk-ext.did-utils@0.24.1-unstable.130': + resolution: {integrity: sha512-I+0VjitRjisABWm8RtTPQG57tFwfUS13Wud30OvBoADRxnaA0guUrkS82AYtV6YD0TBHdrd0D6a0RCJwK9SvDg==} + '@sphereon/ssi-sdk-ext.identifier-resolution@0.24.1-unstable.112': resolution: {integrity: sha512-VBkJjHokFNsQ0wsHUbyCysMuShTOEuK6yrvyW64uOFcB2hzq1J/wi9CewI+YRHv7mnejBlu46uYNycvOKKRcsQ==} + '@sphereon/ssi-sdk-ext.identifier-resolution@0.24.1-unstable.130': + resolution: {integrity: sha512-9mY+qgXmbZCC8aic99R7B3vKBHBakDiC6Sktgd7Q9AknR8cCmvdrmTgnOETrLng9L43uNOJnNTMG/4T6LqmtsA==} + '@sphereon/ssi-sdk-ext.jwt-service@0.24.1-unstable.112': resolution: {integrity: sha512-OrBaSg5wLSehkJ4MyuyDWKD4CRIBERnJqRT0o/y5DbaCF3k02+/lN/rWP+4qwk2w192fIEAExG4L2GwZM/5PLQ==} + '@sphereon/ssi-sdk-ext.jwt-service@0.24.1-unstable.130': + resolution: {integrity: sha512-MHLGRmJODEYJyFoXKwlKMYzf48vS5JcUkGk0W4sqmrY1wwcw+ro3l8adIprG37mNuknXBs9Mv0x/tvibE9wwCQ==} + '@sphereon/ssi-sdk-ext.key-manager@0.24.1-unstable.112': resolution: {integrity: sha512-XdXV4qj+BYTZWyGHduWQxl0mxCYt5CF0Q93p4Thbm2/hjfaAC6aJi2WAXFGTIri95QVbKW1Uscob0CjNCVkWdg==} + '@sphereon/ssi-sdk-ext.key-manager@0.24.1-unstable.130': + resolution: {integrity: sha512-O/6NlKmlYRnEyP/mAI2Diu0qptMSqZfVwqog8KAOG/G8JUmktfSQmclBW8RoJ6AD9uY65BGzNk1oAVuuMv4Dog==} + '@sphereon/ssi-sdk-ext.key-utils@0.24.1-unstable.112': resolution: {integrity: sha512-er6TwGUWxlao2lSP97r1DTFlUXcPSMsIToULOWQJp6wKbvCuvV6pN5luS0qKB/W0/TOUE5kXzFwNx086BPnPRA==} + '@sphereon/ssi-sdk-ext.key-utils@0.24.1-unstable.130': + resolution: {integrity: sha512-DCyXW18g1OAuZ+aFHzQGrbZSx793DX94LSFnrWlOTMnYeILmrizuFksUlWSb3lTqQGAqWBC48NoR3I1H6lSMEQ==} + '@sphereon/ssi-sdk-ext.x509-utils@0.24.1-unstable.112': resolution: {integrity: sha512-bbx2jFoqWhW/xYABVwg3HiUo15yztPt3s+9bJtdB8n4PCjin4Nq3+vFvaHsmu70yAGkbWfsBcBVW6Y3oFtvpAA==} + '@sphereon/ssi-sdk-ext.x509-utils@0.24.1-unstable.130': + resolution: {integrity: sha512-JDX8i0WrwONaOivZXB+OxJQGkln7vuSLS61tOYl7M1RyPGixdBYuEuACsdvWf6egYOpaWmhmXZzaAOj18eDddw==} + '@sphereon/ssi-sdk.agent-config@0.29.1-unstable.161': resolution: {integrity: sha512-ZP/TjapF/Gv/AwnNr9e1U3rjyRwdLtAj4un9j1csnKcgYe9ff2fhYbe06y9mU4tfQilH69mAW4Tz1t6N5U7XbA==} @@ -2978,6 +2995,9 @@ packages: '@sphereon/ssi-types@0.29.1-unstable.208': resolution: {integrity: sha512-3YAFzy//BojsYN+RYoEjndWP3w5a8a3qRZi5dS0Gh6s4yMCiykqTJM1agJVeoaLce8JxFFaCWSpkzwbmJYGTaQ==} + '@sphereon/ssi-types@0.30.1': + resolution: {integrity: sha512-vbYaxQXb71sOPwDj7TRDlUGfIHKVVs8PiHfImPBgSBshrD7VpEHOrB+EwwavMm5MAQvWK/yblGmzk7FHds7SHA==} + '@sphereon/ssi-types@0.9.0': resolution: {integrity: sha512-umCr/syNcmvMMbQ+i/r/mwjI1Qw2aFPp9AwBTvTo1ailAVaaJjJGPkkVz1K9/2NZATNdDiQ3A8yGzdVJoKh9pA==} @@ -3343,6 +3363,7 @@ packages: '@xmldom/xmldom@0.7.13': resolution: {integrity: sha512-lm2GW5PkosIzccsaZIz7tp8cPADSIlIHWDFTR1N0SzfinhhYgeIQjFMz4rYzanCScr3DqQLeomUDArp6MWKm+g==} engines: {node: '>=10.0.0'} + deprecated: this version is no longer supported, please update to at least 0.8.* '@xmldom/xmldom@0.8.10': resolution: {integrity: sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==} @@ -4669,6 +4690,7 @@ packages: expo-random@14.0.1: resolution: {integrity: sha512-gX2mtR9o+WelX21YizXUCD/y+a4ZL+RDthDmFkHxaYbdzjSYTn8u/igoje/l3WEO+/RYspmqUFa8w/ckNbt6Vg==} + deprecated: This package is now deprecated in favor of expo-crypto, which provides the same functionality. To migrate, replace all imports from expo-random with imports from expo-crypto. peerDependencies: expo: '*' @@ -12533,6 +12555,44 @@ snapshots: - ts-node - typeorm-aurora-data-api-driver + '@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(@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 + '@veramo/core': 4.2.0 + '@veramo/utils': 4.2.0 + did-jwt: 6.11.6 + did-resolver: 4.1.0 + elliptic: 6.5.7 + uint8arrays: 3.1.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/ssi-sdk-ext.identifier-resolution@0.24.1-unstable.112(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.112(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4)) @@ -12566,6 +12626,39 @@ snapshots: - ts-node - typeorm-aurora-data-api-driver + '@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(@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(@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 + debug: 4.3.6 + pkijs: 3.2.4 + uint8arrays: 3.1.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/ssi-sdk-ext.jwt-service@0.24.1-unstable.112(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.112(ts-node@10.9.2(@types/node@18.18.8)(typescript@5.5.4)) @@ -12601,6 +12694,41 @@ snapshots: - ts-node - typeorm-aurora-data-api-driver + '@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(@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(@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 + debug: 4.3.6 + jwt-decode: 4.0.0 + uint8arrays: 3.1.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/ssi-sdk-ext.key-manager@0.24.1-unstable.112': dependencies: '@veramo/core': 4.2.0 @@ -12609,6 +12737,14 @@ snapshots: transitivePeerDependencies: - supports-color + '@sphereon/ssi-sdk-ext.key-manager@0.24.1-unstable.130': + dependencies: + '@veramo/core': 4.2.0 + '@veramo/key-manager': 4.2.0 + uint8arrays: 3.1.1 + transitivePeerDependencies: + - supports-color + '@sphereon/ssi-sdk-ext.key-utils@0.24.1-unstable.112': dependencies: '@ethersproject/random': 5.7.0 @@ -12631,6 +12767,28 @@ snapshots: transitivePeerDependencies: - supports-color + '@sphereon/ssi-sdk-ext.key-utils@0.24.1-unstable.130': + dependencies: + '@ethersproject/random': 5.7.0 + '@sphereon/ssi-sdk-ext.x509-utils': 0.24.1-unstable.130 + '@sphereon/ssi-types': 0.29.1-unstable.161 + '@stablelib/ed25519': 1.0.3 + '@stablelib/sha256': 1.0.1 + '@stablelib/sha512': 1.0.1 + '@trust/keyto': 1.0.1 + '@veramo/core': 4.2.0 + base64url: 3.0.1 + debug: 4.3.6 + did-resolver: 4.1.0 + elliptic: 6.5.7 + lodash.isplainobject: 4.0.6 + multiformats: 9.9.0 + uint8arrays: 3.1.1 + varint: 6.0.0 + web-encoding: 1.1.5 + transitivePeerDependencies: + - supports-color + '@sphereon/ssi-sdk-ext.x509-utils@0.24.1-unstable.112': dependencies: '@trust/keyto': 1.0.1 @@ -12641,6 +12799,16 @@ snapshots: transitivePeerDependencies: - supports-color + '@sphereon/ssi-sdk-ext.x509-utils@0.24.1-unstable.130': + dependencies: + '@trust/keyto': 1.0.1 + debug: 4.3.6 + js-x509-utils: 1.0.7 + pkijs: 3.2.4 + uint8arrays: 3.1.1 + transitivePeerDependencies: + - supports-color + '@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 @@ -12729,6 +12897,35 @@ snapshots: transitivePeerDependencies: - supports-color + '@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(@types/node@18.18.8)(typescript@5.5.4)) + debug: 4.3.6 + events: 3.3.0 + jwt-decode: 3.1.2 + 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/ssi-types@0.9.0': dependencies: jwt-decode: 3.1.2 From be9876b1a737b88da501275e802759ca37f78458 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Tue, 5 Nov 2024 13:13:31 +0530 Subject: [PATCH 7/9] some updates Signed-off-by: Timo Glastra --- packages/openid4vc/package.json | 3 +- .../OpenId4VcIssuerService.ts | 3 +- .../OpenId4VcIssuerServiceOptions.ts | 22 +++----- packages/openid4vc/src/shared/models/index.ts | 55 +++++++++---------- pnpm-lock.yaml | 35 ++++++++++-- 5 files changed, 69 insertions(+), 49 deletions(-) diff --git a/packages/openid4vc/package.json b/packages/openid4vc/package.json index 492c68286e..a080145591 100644 --- a/packages/openid4vc/package.json +++ b/packages/openid4vc/package.json @@ -37,7 +37,8 @@ "oauth4webapi": "^3.0.1", "rxjs": "^7.8.0", "zod": "^3.23.8", - "@animo-id/oid4vci": "0.0.2-alpha-20241024080134" + "@animo-id/oid4vci": "0.0.2-alpha-20241104215631", + "@animo-id/oauth2": "0.0.2-alpha-20241104215631" }, "devDependencies": { "@credo-ts/tenants": "workspace:*", diff --git a/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerService.ts b/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerService.ts index 5cbd9c004e..1ef8f2ac32 100644 --- a/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerService.ts +++ b/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerService.ts @@ -16,6 +16,7 @@ import type { OpenId4VciCredentialRequest, } from '../shared' import type { AgentContext, DidDocument, Query, QueryOptions } from '@credo-ts/core' +import {preAuthorizedCodeGrantIdentifier } from '@animo-id/oauth2' import { PRE_AUTH_GRANT_LITERAL } from '@sphereon/oid4vci-common' import type { CredentialOfferPayloadV1_0_11, @@ -167,7 +168,7 @@ export class OpenId4VcIssuerService { credentialOfferUri: hostedCredentialOfferUri, baseUri: options.baseUri, credentialDataSupplierInput: options.issuanceMetadata, - pinLength: grants[PRE_AUTH_GRANT_LITERAL]?.tx_code?.length, + pinLength: grants[preAuthorizedCodeGrantIdentifier]?.tx_code?.length, }) // FIXME: https://github.com/Sphereon-Opensource/OID4VCI/issues/102 diff --git a/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerServiceOptions.ts b/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerServiceOptions.ts index c0286293ac..a7fc465989 100644 --- a/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerServiceOptions.ts +++ b/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerServiceOptions.ts @@ -7,10 +7,11 @@ import type { import type { OpenId4VcCredentialHolderBinding, OpenId4VciCredentialConfigurationsSupported, - OpenId4VciCredentialOffer, + OpenId4VciCredentialConfigurationsSupportedWithFormats, + OpenId4VciCredentialConfigurationSupportedWithFormats, + OpenId4VciCredentialOfferPayload, OpenId4VciCredentialRequest, - OpenId4VciCredentialSupported, - OpenId4VciCredentialSupportedWithId, + OpenId4VciCredentialRequestWithFormats, OpenId4VciIssuerMetadataDisplay, OpenId4VciTxCode, } from '../shared' @@ -63,7 +64,6 @@ export type OpenId4VcIssuerMetadata = { authorizationServers?: string[] issuerDisplay?: OpenId4VciIssuerMetadataDisplay[] - credentialsSupported: OpenId4VciCredentialSupportedWithId[] credentialConfigurationsSupported: OpenId4VciCredentialConfigurationsSupported dpopSigningAlgValuesSupported?: [JwaSignatureAlgorithm, ...JwaSignatureAlgorithm[]] } @@ -127,12 +127,12 @@ export type OpenId4VciCredentialRequestToCredentialMapper = (options: { /** * The credential request received from the wallet */ - credentialRequest: OpenId4VciCredentialRequest + credentialRequest: OpenId4VciCredentialRequestWithFormats /** * The offer associated with the credential request */ - credentialOffer: OpenId4VciCredentialOffer + credentialOffer: OpenId4VciCredentialOfferPayload /** * Verified key binding material that should be included in the credential @@ -142,14 +142,10 @@ export type OpenId4VciCredentialRequestToCredentialMapper = (options: { holderBinding: OpenId4VcCredentialHolderBinding /** - * @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 diff --git a/packages/openid4vc/src/shared/models/index.ts b/packages/openid4vc/src/shared/models/index.ts index 60ef033798..5f98079cb8 100644 --- a/packages/openid4vc/src/shared/models/index.ts +++ b/packages/openid4vc/src/shared/models/index.ts @@ -1,45 +1,42 @@ -import { CredentialConfigurationSupported, CredentialIssuerMetadata, CredentialRequest } from '@animo-id/oid4vci'; +import { + CredentialConfigurationSupported, + CredentialConfigurationSupportedWithFormats, + CredentialIssuerMetadata, + CredentialRequest, + CredentialRequestWithFormats, +} from '@animo-id/oid4vci' import type { VerifiedAuthorizationRequest, AuthorizationRequestPayload, AuthorizationResponsePayload, IDTokenPayload, } from '@sphereon/did-auth-siop' -import type { - AssertedUniformCredentialOffer, - CredentialOfferPayloadV1_0_11, - CredentialOfferPayloadV1_0_13, - CredentialRequestJwtVcJson, - CredentialRequestJwtVcJsonLdAndLdpVc, - CredentialRequestJwtVcJsonLdAndLdpVcV1_0_13, - CredentialRequestJwtVcJsonV1_0_13, - CredentialRequestSdJwtVc, - CredentialsSupportedLegacy, - MetadataDisplay, - TxCode, -} from '@sphereon/oid4vci-common' - -export type OpenId4VciCredentialSupported = CredentialsSupportedLegacy & { id?: string; scope?: string } -export type OpenId4VciCredentialSupportedWithId = CredentialsSupportedLegacy & { id: string; scope?: string } -export type OpenId4VciCredentialSupportedWithIdAndScope = OpenId4VciCredentialSupportedWithId & { scope: string } +import { CredentialOfferObject } from '@animo-id/oid4vci' +import { PreAuthorizedCodeGrantIdentifier } 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, + OpenId4VciCredentialConfigurationSupported +> -export type OpenId4VciIssuerMetadata = CredentialIssuerMetadata -export type OpenId4VciIssuerMetadataDisplay = MetadataDisplay +// TODO: export in @animo-id/oid4vc +export type OpenId4VciTxCode = NonNullable< + NonNullable[PreAuthorizedCodeGrantIdentifier]>['tx_code'] +> -export type OpenId4VciCredentialRequest = CredentialRequest +export type OpenId4VciIssuerMetadata = CredentialIssuerMetadata -export type OpenId4VciCredentialRequestJwtVcJson = CredentialRequestJwtVcJson | CredentialRequestJwtVcJsonV1_0_13 +// TODO: export in @animo-id/oid4vc +export type OpenId4VciIssuerMetadataDisplay = NonNullable[number] -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 diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fb1425a95f..ba28619267 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -714,9 +714,12 @@ importers: packages/openid4vc: dependencies: + '@animo-id/oauth2': + specifier: 0.0.2-alpha-20241104215631 + version: 0.0.2-alpha-20241104215631(typescript@5.5.4) '@animo-id/oid4vci': - specifier: 0.0.2-alpha-20241024080134 - version: 0.0.2-alpha-20241024080134(typescript@5.5.4) + specifier: 0.0.2-alpha-20241104215631 + version: 0.0.2-alpha-20241104215631(typescript@5.5.4) '@credo-ts/core': specifier: workspace:* version: link:../core @@ -934,8 +937,14 @@ packages: react: '*' react-native: '*' - '@animo-id/oid4vci@0.0.2-alpha-20241024080134': - resolution: {integrity: sha512-0+t/xQhQbaFKa8huQBrDWT87LqZ6E/7zecLYj+WOZVV/vrNnk6RvmTMy3zDh0NBXRx28cw12qI+OwdoVtGegCQ==} + '@animo-id/oauth2-utils@0.0.2-alpha-20241104215631': + resolution: {integrity: sha512-HRDkLU+hJBRiFG1P1ZOXcjCeE4dv8Novbvc7/VcHAl5eUv4exFCwvUgei3J3EM4E/vsO595PVpUsSXpVhXvLxg==} + + '@animo-id/oauth2@0.0.2-alpha-20241104215631': + resolution: {integrity: sha512-EZr1i5DD9va8uFBVv0b0wtO/cifpfgbD+NOHhuZP7Eo266S2Nka+NmVWOmB6KBlz+hI8fi1aEvo2zGKAxZ1VYw==} + + '@animo-id/oid4vci@0.0.2-alpha-20241104215631': + resolution: {integrity: sha512-khS+7h3A43kvo0cjO97TijsbGVdwFE2VDkFE47ptJpJAfHtvgZlQ4CuffK3PA8iVF0FsT9PAUbZveKQKNxtDVw==} '@animo-id/react-native-bbs-signatures@0.1.0': resolution: {integrity: sha512-7qvsiWhGfUev8ngE8YzF6ON9PtCID5LiYVYM4EC5eyj80gCdhx3R46CI7K1qbqIlGsoTYQ/Xx5Ubo5Ji9eaUEA==} @@ -2915,6 +2924,7 @@ packages: '@sphereon/kmp-mdl-mdoc@0.2.0-SNAPSHOT.22': resolution: {integrity: sha512-uAZZExVy+ug9JLircejWa5eLtAZ7bnBP6xb7DO2+86LRsHNLh2k2jMWJYxp+iWtGHTsh6RYsZl14ScQLvjiQ/A==} + bundledDependencies: [] '@sphereon/oid4vc-common@0.16.1-next.66': resolution: {integrity: sha512-PqMFDMfWHn9jiUxa5k018huQTYxHfYJJstQNyRrle/pV37gv6Lh+yjwML6LE8eUQksw05tfXeq1hk8M2Gl1HWA==} @@ -8208,13 +8218,28 @@ snapshots: 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/oid4vci@0.0.2-alpha-20241024080134(typescript@5.5.4)': + '@animo-id/oauth2-utils@0.0.2-alpha-20241104215631(typescript@5.5.4)': dependencies: buffer: 6.0.3 valibot: 0.42.1(typescript@5.5.4) transitivePeerDependencies: - typescript + '@animo-id/oauth2@0.0.2-alpha-20241104215631(typescript@5.5.4)': + dependencies: + '@animo-id/oauth2-utils': 0.0.2-alpha-20241104215631(typescript@5.5.4) + valibot: 0.42.1(typescript@5.5.4) + transitivePeerDependencies: + - typescript + + '@animo-id/oid4vci@0.0.2-alpha-20241104215631(typescript@5.5.4)': + dependencies: + '@animo-id/oauth2': 0.0.2-alpha-20241104215631(typescript@5.5.4) + '@animo-id/oauth2-utils': 0.0.2-alpha-20241104215631(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 From c7f9eb30101fdce577e9547553969e99abbaca0d Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Fri, 8 Nov 2024 16:01:03 +0530 Subject: [PATCH 8/9] feat: working and tests passing Signed-off-by: Timo Glastra --- demo-openid/README.md | 20 +- demo-openid/src/BaseAgent.ts | 12 +- demo-openid/src/BaseInquirer.ts | 86 +- demo-openid/src/Holder.ts | 122 +- demo-openid/src/HolderInquirer.ts | 129 +- demo-openid/src/Issuer.ts | 179 ++- demo-openid/src/IssuerInquirer.ts | 39 +- demo-openid/src/Provider.ts | 81 +- demo-openid/src/VerifierInquirer.ts | 33 +- jest.config.base.ts | 4 - packages/core/package.json | 2 +- packages/core/src/agent/AgentConfig.ts | 4 + packages/core/src/agent/AgentDependencies.ts | 1 + .../src/modules/sd-jwt-vc/SdJwtVcService.ts | 6 +- packages/core/src/types.ts | 13 + packages/node/package.json | 2 +- .../src/transport/HttpInboundTransport.ts | 1 - packages/openid4vc/package.json | 7 +- .../openid4vc-holder/OpenId4VcHolderApi.ts | 87 +- .../openid4vc-holder/OpenId4VcHolderModule.ts | 17 +- .../OpenId4VciHolderService.ts | 686 ++++++---- .../OpenId4VciHolderServiceOptions.ts | 126 +- .../openid4vc-holder/__tests__/fixtures.ts | 10 +- .../__tests__/openid4vci-holder.test.ts | 53 +- .../__tests__/openid4vp-holder.e2e.test.ts | 2 +- .../openid4vc-issuer/OpenId4VcIssuerApi.ts | 28 +- .../openid4vc-issuer/OpenId4VcIssuerModule.ts | 26 +- .../OpenId4VcIssuerModuleConfig.ts | 21 +- .../OpenId4VcIssuerService.ts | 1160 ++++++++--------- .../OpenId4VcIssuerServiceOptions.ts | 106 +- .../__tests__/openid4vc-issuer.test.ts | 358 +++-- .../authorization/clientAuth.ts | 82 -- .../authorization/discover.ts | 18 - .../openid4vc-issuer/authorization/dpop.ts | 30 - .../authorization/externalAuthorization.ts | 131 -- .../authorization/internalAuthorization.ts | 70 - .../authorization/introspect.ts | 29 - .../authorization/verifyResourceRequest.ts | 50 - .../openid4vc-issuer/oauth4webapi.native.ts | 5 - .../src/openid4vc-issuer/oauth4webapi.ts | 12 - .../repository/OpenId4VcCNonceStateManager.ts | 130 -- ...Id4VcCredentialOfferSessionStateManager.ts | 253 ---- ...OpenId4VcCredentialOfferUriStateManager.ts | 82 -- .../OpenId4VcIssuanceSessionRecord.ts | 105 +- .../repository/OpenId4VcIssuerRecord.ts | 34 +- .../router/accessTokenEndpoint.ts | 321 +++-- .../authorizationServerMetadataEndpoint.ts | 24 +- .../router/credentialEndpoint.ts | 125 +- .../router/credentialOfferEndpoint.ts | 63 +- .../src/openid4vc-issuer/router/index.ts | 2 + .../router/issuerMetadataEndpoint.ts | 36 +- .../openid4vc-issuer/router/jwksEndpoint.ts | 22 + .../openid4vc-issuer/router/nonceEndpoint.ts | 50 + .../util/credentialRequest.ts | 14 - .../src/openid4vc-issuer/util/txCode.ts | 19 + .../OpenId4VcSiopVerifierService.ts | 13 +- .../OpenId4VcSiopVerifierServiceOptions.ts | 17 +- .../OpenId4VcVerifierApi.ts | 11 + .../OpenId4VcVerifierModule.ts | 16 +- .../repository/OpenId4VcVerifierRecord.ts | 5 + .../router/authorizationRequestEndpoint.ts | 2 +- .../__tests__/issuerMetadataUtils.test.ts | 77 -- packages/openid4vc/src/shared/callbacks.ts | 107 ++ .../src/shared/issuerMetadataUtils.ts | 5 +- .../src/shared/models/AuthorizationServer.ts | 10 - .../shared/models/CredentialHolderBinding.ts | 3 +- .../OpenId4VciAuthorizationServerConfig.ts | 12 + packages/openid4vc/src/shared/models/index.ts | 30 +- .../openid4vc/src/shared/router/context.ts | 75 +- packages/openid4vc/src/shared/utils.ts | 26 +- .../openid4vc/tests/openid4vc.e2e.test.ts | 563 ++++---- packages/openid4vc/tests/utils.ts | 17 +- packages/openid4vc/tests/utilsVci.ts | 48 +- pnpm-lock.yaml | 111 +- tests/https-proxy.ts | 71 - 75 files changed, 2910 insertions(+), 3437 deletions(-) delete mode 100644 packages/openid4vc/src/openid4vc-issuer/authorization/clientAuth.ts delete mode 100644 packages/openid4vc/src/openid4vc-issuer/authorization/discover.ts delete mode 100644 packages/openid4vc/src/openid4vc-issuer/authorization/dpop.ts delete mode 100644 packages/openid4vc/src/openid4vc-issuer/authorization/externalAuthorization.ts delete mode 100644 packages/openid4vc/src/openid4vc-issuer/authorization/internalAuthorization.ts delete mode 100644 packages/openid4vc/src/openid4vc-issuer/authorization/introspect.ts delete mode 100644 packages/openid4vc/src/openid4vc-issuer/authorization/verifyResourceRequest.ts delete mode 100644 packages/openid4vc/src/openid4vc-issuer/oauth4webapi.native.ts delete mode 100644 packages/openid4vc/src/openid4vc-issuer/oauth4webapi.ts delete mode 100644 packages/openid4vc/src/openid4vc-issuer/repository/OpenId4VcCNonceStateManager.ts delete mode 100644 packages/openid4vc/src/openid4vc-issuer/repository/OpenId4VcCredentialOfferSessionStateManager.ts delete mode 100644 packages/openid4vc/src/openid4vc-issuer/repository/OpenId4VcCredentialOfferUriStateManager.ts create mode 100644 packages/openid4vc/src/openid4vc-issuer/router/jwksEndpoint.ts create mode 100644 packages/openid4vc/src/openid4vc-issuer/router/nonceEndpoint.ts delete mode 100644 packages/openid4vc/src/openid4vc-issuer/util/credentialRequest.ts create mode 100644 packages/openid4vc/src/openid4vc-issuer/util/txCode.ts delete mode 100644 packages/openid4vc/src/shared/__tests__/issuerMetadataUtils.test.ts create mode 100644 packages/openid4vc/src/shared/callbacks.ts delete mode 100644 packages/openid4vc/src/shared/models/AuthorizationServer.ts create mode 100644 packages/openid4vc/src/shared/models/OpenId4VciAuthorizationServerConfig.ts delete mode 100644 tests/https-proxy.ts diff --git a/demo-openid/README.md b/demo-openid/README.md index f8ad0777c3..0867a195ef 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). +- ✅ Issuing a credenital with external authorization server - ✅ 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: 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..94874bfb55 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[]): Promise { + const result = await prompt([ + { + ...this.optionsInquirer, + message: 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[]): Promise { + const result = await prompt([ + { + ...this.optionsInquirer, + message: 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 b7cf2c5488..51b8e4abcd 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' @@ -28,11 +36,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,41 +47,102 @@ export class Holder extends BaseAgent> return await this.agent.modules.openId4VcHolder.resolveCredentialOffer(credentialOffer) } - public async requestAndStoreCredentials( + public async initiateAuthorization( resolvedCredentialOffer: OpenId4VciResolvedCredentialOffer, credentialsToRequest: string[] ) { - const resolvedAuthorizationRequest = await this.agent.modules.openId4VcHolder.resolveIssuanceAuthorizationRequest( - resolvedCredentialOffer, - { - clientId: 'foo', - redirectUri: 'http://example.com', - scope: ['openid', 'UniversityDegree'], + 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: 'foo', + redirectUri: 'http://localhost:3000/redirect', + 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 } - ) + } - let code = 'not a valid code!' - code = 'MU_MtTZjjhjmzuzGZdsLSam2GcC-7c4g_k5ukV2XO3i' + throw new Error('Unsupported grant type') + } - const tokenResponse = await this.agent.modules.openId4VcHolder.requestToken({ - resolvedAuthorizationRequest, - code, - resolvedCredentialOffer, - }) + public async requestAndStoreCredentials( + resolvedCredentialOffer: OpenId4VciResolvedCredentialOffer, + options: { clientId?: string; codeVerifier?: string; credentialsToRequest: string[]; code?: string } + ) { + const tokenResponse = await this.agent.modules.openId4VcHolder.requestToken( + options.code && options.clientId + ? { + resolvedCredentialOffer, + clientId: options.clientId, + codeVerifier: options.codeVerifier, + code: options.code, + } + : { + resolvedCredentialOffer, + } + ) const credentialResponse = await this.agent.modules.openId4VcHolder.requestCredentials({ resolvedCredentialOffer, - credentialsToRequest, - credentialBindingResolver: async () => ({ - method: 'did', - didUrl: this.verificationMethod.id, - }), + credentialConfigurationIds: options.credentialsToRequest, + credentialBindingResolver: async ({ keyTypes, supportsJwk, 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`, + } + } + if (supportsJwk) { + return { + method: 'jwk', + jwk: getJwkFromKey(key), + } + } + + throw new Error('unable to determine holder binding') + }, ...tokenResponse, }) 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..fbcc2d68ed 100644 --- a/demo-openid/src/HolderInquirer.ts +++ b/demo-openid/src/HolderInquirer.ts @@ -4,9 +4,8 @@ import type { OpenId4VcSiopResolvedAuthorizationRequest, OpenId4VciResolvedCrede import { DifPresentationExchangeService, Mdoc } from '@credo-ts/core' 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' @@ -22,6 +21,7 @@ enum PromptOptions { RequestCredential = 'Accept the credential offer.', ResolveProofRequest = 'Resolve a proof request.', AcceptPresentationRequest = 'Accept the presentation request.', + AddTrustedCertificate = 'Add trusted certificate', Exit = 'Exit', Restart = 'Restart', } @@ -42,18 +42,22 @@ export class HolderInquirer extends BaseInquirer { } private async getPromptChoice() { - const promptOptions = [PromptOptions.ResolveCredentialOffer, PromptOptions.ResolveProofRequest] + const promptOptions = [ + PromptOptions.ResolveCredentialOffer, + 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 @@ -66,6 +70,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 +84,23 @@ 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 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 +108,70 @@ 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 + + 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 + } - 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 ? 'foo' : undefined, + codeVerifier, + code: authorizationCode, + }) + + 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 +206,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 c41950bd93..27ed34b325 100644 --- a/demo-openid/src/Issuer.ts +++ b/demo-openid/src/Issuer.ts @@ -4,7 +4,9 @@ import type { OpenId4VcCredentialHolderDidBinding, OpenId4VciCredentialConfigurationsSupportedWithFormats, OpenId4VciCredentialRequestToCredentialMapper, - OpenId4VciSignMdocCredential, + OpenId4VciSignMdocCredentials, + OpenId4VciSignSdJwtCredentials, + OpenId4VciSignW3cCredentials, OpenId4VcIssuerRecord, } from '@credo-ts/openid4vc' @@ -20,6 +22,8 @@ import { X509Service, KeyType, X509ModuleConfig, + utils, + TypedArrayEncoder, } from '@credo-ts/core' import { OpenId4VcIssuerModule, OpenId4VciCredentialFormatProfile } from '@credo-ts/openid4vc' import { ariesAskar } from '@hyperledger/aries-askar-nodejs' @@ -28,17 +32,13 @@ import { Router } from 'express' import { BaseAgent } from './BaseAgent' import { Output } from './OutputClass' -// TODO: this should error -const credentialConfigurationsSupported = { - UniversityDegreeCredential: { +export const credentialConfigurationsSupported = { + 'UniversityDegreeCredential-jwtvcjson': { format: OpenId4VciCredentialFormatProfile.JwtVcJson, - types: ['VerifiableCredential', 'UniversityDegreeCredential'], - scope: 'openid4vc:credential:UniversityDegreeCredential', - }, - OpenBadgeCredential: { - format: OpenId4VciCredentialFormatProfile.JwtVcJson, - types: ['VerifiableCredential', 'OpenBadgeCredential'], - scope: 'openid4vc:credential:OpenBadgeCredential', + scope: 'openid4vc:credential:UniversityDegreeCredential-jwtvcjson', + credential_definition: { + type: ['VerifiableCredential', 'UniversityDegreeCredential'], + }, }, 'UniversityDegreeCredential-sdjwt': { format: OpenId4VciCredentialFormatProfile.SdJwtVc, @@ -58,7 +58,7 @@ function getCredentialRequestToCredentialMapper({ issuerDidKey: DidKey }): OpenId4VciCredentialRequestToCredentialMapper { return async ({ - holderBinding, + holderBindings, credentialConfigurationIds, credentialConfigurationsSupported: supported, agentContext, @@ -68,82 +68,65 @@ function getCredentialRequestToCredentialMapper({ throw new Error(`Expected exactly one trusted certificate. Received ${trustedCertificates?.length}.`) } - // FIXME: correct type inference const credentialConfigurationId = credentialConfigurationIds[0] const credentialConfiguration = supported[credentialConfigurationId] if (credentialConfiguration.format === OpenId4VciCredentialFormatProfile.JwtVcJson) { - assertDidBasedHolderBinding(holderBinding) - const configuration = credentialConfigurationsSupported.UniversityDegreeCredential - - return { - credentialSupportedId: credentialConfigurationId, - format: ClaimFormat.JwtVc, - credential: new W3cCredential({ - type: configuration.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}`, - } - } - - if (credentialConfiguration.scope === credentialConfigurationsSupported.OpenBadgeCredential.scope) { - assertDidBasedHolderBinding(holderBinding) - const configuration = credentialConfigurationsSupported.OpenBadgeCredential + holderBindings.forEach((holderBinding) => assertDidBasedHolderBinding(holderBinding)) return { + credentialConfigurationId, format: ClaimFormat.JwtVc, - credentialSupportedId: credentialConfigurationId, - credential: new W3cCredential({ - type: configuration.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: new W3cCredentialSubject({ + id: parseDid(holderBinding.didUrl).did, + }), + issuanceDate: w3cDate(Date.now()), + }), + verificationMethod: `${issuerDidKey.did}#${issuerDidKey.key.fingerprint}`, + } }), - verificationMethod: `${issuerDidKey.did}#${issuerDidKey.key.fingerprint}`, - } + } satisfies OpenId4VciSignW3cCredentials } - if (credentialConfiguration.scope === credentialConfigurationsSupported['UniversityDegreeCredential-sdjwt'].scope) { - const configuration = credentialConfigurationsSupported['UniversityDegreeCredential-sdjwt'] - + if (credentialConfiguration.format === OpenId4VciCredentialFormatProfile.SdJwtVc) { return { - credentialSupportedId: credentialConfigurationId, + credentialConfigurationId, format: ClaimFormat.SdJwtVc, - payload: { vct: configuration.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' }, + holder: holderBinding, + issuer: { + method: 'did', + didUrl: `${issuerDidKey.did}#${issuerDidKey.key.fingerprint}`, + }, + disclosureFrame: { _sd: ['university', 'degree'] }, + })), + } satisfies OpenId4VciSignSdJwtCredentials } - if (credentialConfiguration.scope === credentialConfigurationsSupported['UniversityDegreeCredential-mdoc'].scope) { - const configuration = credentialConfigurationsSupported['UniversityDegreeCredential-mdoc'] + if (credentialConfiguration.format === OpenId4VciCredentialFormatProfile.MsoMdoc) { return { - credentialSupportedId: credentialConfigurationId, + credentialConfigurationId, format: ClaimFormat.MsoMdoc, - docType: configuration.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', + }, }, - }, - } satisfies OpenId4VciSignMdocCredential + docType: credentialConfiguration.doctype, + })), + } satisfies OpenId4VciSignMdocCredentials } throw new Error('Invalid request') @@ -184,20 +167,18 @@ export class Issuer extends BaseAgent<{ const issuer = new Issuer(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, + 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: [], 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) @@ -205,13 +186,13 @@ export class Issuer extends BaseAgent<{ issuer.issuerRecord = await issuer.agent.modules.openId4VcIssuer.createIssuer({ issuerId: '726222ad-7624-4f12-b15b-e08aa7042ffa', credentialConfigurationsSupported, - // FIXME: should be extraAuthorizationServerConfigs. authorizationServerConfigs: [ { issuer: 'http://localhost:3042', - serverType: 'oidc', - clientId: 'issuer-server', - clientSecret: 'issuer-server', + clientAuthentication: { + clientId: 'issuer-server', + clientSecret: 'issuer-server', + }, }, ], }) @@ -219,22 +200,26 @@ export class Issuer extends BaseAgent<{ return issuer } - public async createCredentialOffer(offeredCredentials: string[]) { - // const issuerMetadata = await this.agent.modules.openId4VcIssuer.getIssuerMetadata(this.issuerRecord.issuerId) + public async createCredentialOffer(options: { credentialConfigurationIds: string[]; requireAuthorization: boolean }) { + const issuerMetadata = await this.agent.modules.openId4VcIssuer.getIssuerMetadata(this.issuerRecord.issuerId) - const { credentialOffer, issuanceSession } = await this.agent.modules.openId4VcIssuer.createCredentialOffer({ + const { credentialOffer } = await this.agent.modules.openId4VcIssuer.createCredentialOffer({ issuerId: this.issuerRecord.issuerId, - offeredCredentials, - // FIXME: wait for PR in OID4VCI repo - // // Pre-auth using our own server - // preAuthorizedCodeFlowConfig: { - // authorizationServerUrl: issuerMetadata.issuerUrl, - // }, + offeredCredentials: options.credentialConfigurationIds, + // Pre-auth using our own server + preAuthorizedCodeFlowConfig: !options.requireAuthorization + ? { + authorizationServerUrl: issuerMetadata.credentialIssuer.credential_issuer, + } + : undefined, // Auth using external authorization server - authorizationCodeFlowConfig: { - authorizationServerUrl: 'http://localhost:3042', - issuerState: 'f498b73c-144f-4eea-bd6b-7be89b35936e', - }, + authorizationCodeFlowConfig: options.requireAuthorization + ? { + authorizationServerUrl: 'http://localhost:3042', + // TODO: should be generated by us, if we're going to use for matching + issuerState: utils.uuid(), + } + : undefined, }) return credentialOffer diff --git a/demo-openid/src/IssuerInquirer.ts b/demo-openid/src/IssuerInquirer.ts index dca38ed86a..e730d3b868 100644 --- a/demo-openid/src/IssuerInquirer.ts +++ b/demo-openid/src/IssuerInquirer.ts @@ -1,9 +1,8 @@ import { clear } from 'console' import { textSync } from 'figlet' -import { prompt } from 'inquirer' -import { BaseInquirer, ConfirmOptions } from './BaseInquirer' -import { Issuer, credentialsSupported } from './Issuer' +import { BaseInquirer } from './BaseInquirer' +import { credentialConfigurationsSupported, Issuer } from './Issuer' import { Title, purpleText } from './OutputClass' export const runIssuer = async () => { @@ -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,29 @@ 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]) + const credentialConfigurationIds = await this.pickMultiple(Object.keys(credentialConfigurationsSupported)) + const requireAuthorization = await this.inquireConfirmation('Require authorization?') + const offerRequest = await this.issuer.createCredentialOffer({ + credentialConfigurationIds, + requireAuthorization, + }) console.log(purpleText(`credential offer: '${offerRequest}'`)) } 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 index 105ea044e5..3b0da1a8dc 100644 --- a/demo-openid/src/Provider.ts +++ b/demo-openid/src/Provider.ts @@ -1,65 +1,87 @@ import { bodyParser } from '@koa/bodyparser' -import Provider from 'oidc-provider' +import { Provider } from 'oidc-provider' const oidc = new Provider('http://localhost:3042', { clients: [ { client_id: 'foo', - // client_secret: 'bar', - redirect_uris: ['http://localhost:1234/redirect'], + client_secret: 'bar', + redirect_uris: ['http://localhost:3000/redirect'], grant_types: ['authorization_code'], + id_token_signed_response_alg: 'ES256', }, { client_id: 'issuer-server', client_secret: 'issuer-server', }, ], - // scopes: ['UniversityDegreeCredential'], + 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: [ + 'openid4vc:credential:UniversityDegreeCredential-jwtvcjson', + 'openid4vc:credential:OpenBadgeCredential-ldpvc', + 'openid4vc:credential:OpenBadgeCredential-sdjwt', + 'openid4vc:credential:OpenBadgeCredential-mdoc', + ], pkce: { methods: ['S256'], - required: () => { - console.log('checking pkce') - return true - }, + required: () => true, }, extraTokenClaims: async (context, token) => { if (token.kind === 'AccessToken') { - console.log(context.body) return { - issuer_state: (context.body as Record).issuer_state, + issuer_state: context.request.body.issuer_state, } } return undefined }, features: { - dPoP: { enabled: false }, + dPoP: { enabled: true }, pushedAuthorizationRequests: { enabled: true, - //requirePushedAuthorizationRequests: true, + requirePushedAuthorizationRequests: true, + }, + introspection: { + enabled: true, }, resourceIndicators: { - defaultResource: () => 'http://localhost:1234', + defaultResource: () => 'http://localhost:2000/oid4vci/726222ad-7624-4f12-b15b-e08aa7042ffa', enabled: true, - getResourceServerInfo: (ctx, resourceIndicator, client) => { + getResourceServerInfo: () => { return { - scope: 'UniversityDegreeCredential', + scope: 'openid4vc:credential:OpenBadgeCredential-sdjwt', accessTokenTTL: 5 * 60, // 5 minutes - accessTokenFormat: 'jwt', + accessTokenFormat: 'opaque', + audience: 'http://localhost:2000/oid4vci/726222ad-7624-4f12-b15b-e08aa7042ffa', + jwt: { + sign: { + kid: 'first-key', + alg: 'ES256', + }, + }, } }, - useGrantedResource: (ctx, model) => { - // @param ctx - koa request context - // @param model - depending on the request's grant_type this can be either an AuthorizationCode, BackchannelAuthenticationRequest, - // RefreshToken, or DeviceCode model instance. + useGrantedResource: () => { return true }, }, }, - async findAccount(ctx, id) { + async findAccount(_, id) { return { accountId: id, - async claims(use, scope) { + async claims() { return { sub: id } }, } @@ -68,15 +90,11 @@ const oidc = new Provider('http://localhost:3042', { oidc.use(bodyParser()) oidc.use(async (ctx, next) => { - /** pre-processing - * you may target a specific action here by matching `ctx.path` - */ - console.log('pre middleware', ctx.method, ctx.path) + // We hack the client secret (to allow public client) if (ctx.path.includes('request')) { - console.log(ctx.request.body) - // ctx.request.body.client_secret = 'bar' + ctx.request.body.client_secret = 'bar' } if (ctx.path.includes('auth')) { @@ -87,7 +105,9 @@ oidc.use(async (ctx, next) => { if (ctx.path.includes('token')) { console.log('token endpoint') - // ctx.request.body.client_secret = 'bar' + console.log(ctx.request.body) + ctx.request.body.client_id = 'foo' + ctx.request.body.client_secret = 'bar' } await next() @@ -99,8 +119,7 @@ oidc.use(async (ctx, next) => { console.log('post middleware', ctx.method, ctx.oidc?.route) if (ctx.path.includes('token')) { - console.log('token endpoint') - ctx.response.body + console.log('token endpoint', ctx.response.body) } }) 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/jest.config.base.ts b/jest.config.base.ts index 8eab845e74..43b6b40302 100644 --- a/jest.config.base.ts +++ b/jest.config.base.ts @@ -1,5 +1,4 @@ import type { Config } from '@jest/types' -import path from 'path' const config: Config.InitialOptions = { preset: 'ts-jest', @@ -10,8 +9,6 @@ const config: Config.InitialOptions = { moduleNameMapper: { '@credo-ts/(.+)': ['/../../packages/$1/src', '/../packages/$1/src', '/packages/$1/src'], }, - // oauth4webapi is an esm module - // transformIgnorePatterns: ['node_modules/.pnpm/(?!oauth4webapi)'], transform: { '\\.tsx?$': [ 'ts-jest', @@ -19,7 +16,6 @@ const config: Config.InitialOptions = { isolatedModules: true, }, ], - // '\\.jsx?$': ['babel-jest', { extends: path.join(__dirname, '.babelrc') }], }, } diff --git a/packages/core/package.json b/packages/core/package.json index 90e7452f2e..2c57561cb5 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -75,7 +75,7 @@ "@types/object-inspect": "^1.8.0", "@types/uuid": "^9.0.1", "@types/varint": "^6.0.0", - "nock": "^13.3.0", + "nock": "^14.0.0-beta.15", "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/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/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/node/package.json b/packages/node/package.json index 1a0027a4b6..82b34a448d 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.15", "rimraf": "^4.4.0", "typescript": "~5.5.2" } diff --git a/packages/node/src/transport/HttpInboundTransport.ts b/packages/node/src/transport/HttpInboundTransport.ts index dc39be8bdb..01a3e5dfee 100644 --- a/packages/node/src/transport/HttpInboundTransport.ts +++ b/packages/node/src/transport/HttpInboundTransport.ts @@ -9,7 +9,6 @@ import type { } from '@credo-ts/core' import type { Express, Request, Response } from 'express' import type { Server } from 'http' -import https from 'https' import { DidCommMimeType, CredoError, TransportService, utils, AgentEventTypes } from '@credo-ts/core' import express, { text } from 'express' diff --git a/packages/openid4vc/package.json b/packages/openid4vc/package.json index ad52b6dfb6..b35d753f32 100644 --- a/packages/openid4vc/package.json +++ b/packages/openid4vc/package.json @@ -31,17 +31,16 @@ "@sphereon/oid4vc-common": "0.16.1-fix.173", "@sphereon/ssi-types": "0.30.2-next.135", "class-transformer": "^0.5.1", - "oauth4webapi": "^3.0.1", "rxjs": "^7.8.0", "zod": "^3.23.8", - "@animo-id/oid4vci": "0.0.2-alpha-20241104215631", - "@animo-id/oauth2": "0.0.2-alpha-20241104215631" + "@animo-id/oid4vci": "0.0.2-alpha-20241108100449", + "@animo-id/oauth2": "0.0.2-alpha-20241108100449" }, "devDependencies": { "@credo-ts/tenants": "workspace:*", "@types/express": "^4.17.21", "express": "^4.18.2", - "nock": "^13.3.0", + "nock": "^14.0.0-beta.15", "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 269534b6a8..1685656ba0 100644 --- a/packages/openid4vc/src/openid4vc-holder/OpenId4VcHolderApi.ts +++ b/packages/openid4vc/src/openid4vc-holder/OpenId4VcHolderApi.ts @@ -1,12 +1,11 @@ import type { OpenId4VciResolvedCredentialOffer, - OpenId4VciResolvedAuthorizationRequest, OpenId4VciAuthCodeFlowOptions, - OpenId4VciAcceptCredentialOfferOptions, OpenId4VciTokenRequestOptions as OpenId4VciRequestTokenOptions, OpenId4VciCredentialRequestOptions as OpenId4VciRequestCredentialOptions, OpenId4VciSendNotificationOptions, OpenId4VciRequestTokenResponse, + OpenId4VciRetrieveAuthorizationCodeUsingPresentationOptions, } from './OpenId4VciHolderServiceOptions' import type { OpenId4VcSiopAcceptAuthorizationRequestOptions } from './OpenId4vcSiopHolderServiceOptions' @@ -72,8 +71,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,69 +100,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 { - accessTokenResponse: { 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 @@ -174,16 +141,8 @@ export class OpenId4VcHolderApi { /** * Send a notification event to the credential issuer - * - * @param options OpenId4VciSendNotificationOptions */ public async sendNotification(options: OpenId4VciSendNotificationOptions) { - return this.openId4VciHolderService.sendNotification(this.agentContext, { - accessToken: options.accessToken, - metadata: await this.openId4VciHolderService.resolveIssuerMetadata(this.agentContext, {}), - notificationEvent: options.notificationEvent, - notificationId: options.notificationMetadata.notificationId, - dpop: options.dpop - }) + 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 ae6afd72e6..d812ab3c81 100644 --- a/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderService.ts +++ b/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderService.ts @@ -3,23 +3,36 @@ import type { OpenId4VciAuthCodeFlowOptions, OpenId4VciCredentialBindingResolver, OpenId4VciCredentialResponse, + OpenId4VciDpopRequestOptions, OpenId4VciNotificationEvent, OpenId4VciProofOfPossessionRequirements, OpenId4VciResolvedAuthorizationRequest, - OpenId4VciResolvedAuthorizationRequestWithCode, OpenId4VciResolvedCredentialOffer, + OpenId4VciRetrieveAuthorizationCodeUsingPresentationOptions, OpenId4VciSupportedCredentialFormats, OpenId4VciTokenRequestOptions, } from './OpenId4VciHolderServiceOptions' -import type { OpenId4VciCredentialConfigurationSupported, OpenId4VciIssuerMetadata } from '../shared' -import type { AgentContext, JwaSignatureAlgorithm, Key } from '@credo-ts/core' +import type { OpenId4VciCredentialConfigurationSupported, OpenId4VciCredentialIssuerMetadata } from '../shared' +import type { AgentContext, JwaSignatureAlgorithm, KeyType } from '@credo-ts/core' +import { + getAuthorizationServerMetadataFromList, + JwtSigner, + Oauth2Client, + preAuthorizedCodeGrantIdentifier, + RequestDpopOptions, +} from '@animo-id/oauth2' +import { + AuthorizationFlow, + CredentialConfigurationsSupportedWithFormats, + CredentialResponse, + IssuerMetadataResult, + Oid4vciClient, + Oid4vciRetrieveCredentialsError, +} from '@animo-id/oid4vci' import { CredoError, - DidsApi, - Hasher, InjectionSymbols, - JsonEncoder, Jwk, JwsService, Logger, @@ -33,7 +46,6 @@ import { getJwkClassFromJwaSignatureAlgorithm, getJwkFromJson, getJwkFromKey, - getKeyFromVerificationMethod, getSupportedVerificationMethodTypesFromKeyType, inject, injectable, @@ -41,11 +53,11 @@ import { } from '@credo-ts/core' import { OpenId4VciCredentialFormatProfile } from '../shared' +import { getOid4vciCallbacks } from '../shared/callbacks' import { getOfferedCredentials } from '../shared/issuerMetadataUtils' -import { getSupportedJwaSignatureAlgorithms } from '../shared/utils' +import { getKeyFromDid, getSupportedJwaSignatureAlgorithms } from '../shared/utils' import { openId4VciSupportedCredentialFormats } from './OpenId4VciHolderServiceOptions' -import { CredentialResponse, IssuerMetadataResult, Oid4vciClient } from '@animo-id/oid4vci' @injectable() export class OpenId4VciHolderService { @@ -63,17 +75,6 @@ export class OpenId4VciHolderService { this.logger = logger } - private getClient(agentContext: AgentContext) { - return new Oid4vciClient({ - callbacks: { - generateRandom: (length) => agentContext.wallet.getRandomValues(length), - hash: (data, alg) => Hasher.hash(data, alg.toLowerCase()), - signJwt: this.jwtSignerCallback(agentContext), - fetch: agentContext.config.agentDependencies.fetch, - }, - }) - } - public async resolveCredentialOffer( agentContext: AgentContext, credentialOffer: string @@ -84,11 +85,10 @@ export class OpenId4VciHolderService { const metadata = await client.resolveIssuerMetadata(credentialOfferObject.credential_issuer) this.logger.debug('fetched credential offer and issuer metadata', { metadata, credentialOfferObject }) - // TODO: only extract known offers const credentialConfigurationsSupported = getOfferedCredentials( credentialOfferObject.credential_configuration_ids, - metadata.credentialIssuer.credential_configurations_supported - ) + client.getKnownCredentialConfigurationsSupported(metadata.credentialIssuer) + ) as CredentialConfigurationsSupportedWithFormats return { metadata, @@ -108,7 +108,8 @@ export class OpenId4VciHolderService { const client = this.getClient(agentContext) - const { authorizationRequestUrl, pkce, authorizationServer } = await client.initiateAuthorization({ + // TODO: we should already support DPoP at the PAR endpoint. + const authorizationResult = await client.initiateAuthorization({ clientId, issuerMetadata: metadata, credentialOffer: credentialOfferPayload, @@ -116,11 +117,18 @@ export class OpenId4VciHolderService { redirectUri, }) + if (authorizationResult.authorizationFlow === AuthorizationFlow.PresentationDuringIssuance) { + return { + authorizationFlow: AuthorizationFlow.PresentationDuringIssuance, + oid4vpRequestUrl: authorizationResult.oid4vpRequestUrl, + } + } + + // Normal Oauth2Redirect flow return { - ...authCodeFlowOptions, - authorizationServer, - codeVerifier: pkce?.codeVerifier, - authorizationRequestUri: authorizationRequestUrl, + authorizationFlow: AuthorizationFlow.Oauth2Redirect, + codeVerifier: authorizationResult.pkce?.codeVerifier, + authorizationRequestUrl: authorizationResult.authorizationRequestUrl, } } @@ -208,20 +216,51 @@ export class OpenId4VciHolderService { } } + 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 + // TODO: this should not return pkce (it's only needed if we fallback from auth challenge to PAR) + const { authorizationChallengeResponse } = await client.retrieveAuthorizationCodeUsingPresentation({ + authSession: options.authSession, + presentationDuringIssuanceSession: options.presentationDuringIssuanceSession, + credentialOffer: options.resolvedCredentialOffer.credentialOfferPayload, + issuerMetadata: options.resolvedCredentialOffer.metadata, + // dpop + }) + + return { + authorizationCode: authorizationChallengeResponse.authorization_code, + } + } + public async requestAccessToken(agentContext: AgentContext, options: OpenId4VciTokenRequestOptions) { - const { resolvedCredentialOffer, txCode, resolvedAuthorizationRequest, code } = options - const { metadata, credentialOfferPayload } = resolvedCredentialOffer + 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 + ) - const authorizationServerMetadata = determineAuthorizationServerForOffer({ - credentialOffer: credentialOfferPayload, - grantType: resolvedAuthorizationRequest ? authorizationCodeGrantIdentifier : preAuthorizedCodeGrantIdentifier, - issuerMetadata: metadata, - }) - const isDpopSupported = client.isDpopSupported({ - authorizationServer: authorizationServerMetadata.issuer, - issuerMetadata: metadata, + // 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, { @@ -229,45 +268,35 @@ export class OpenId4VciHolderService { }) : undefined - if (resolvedAuthorizationRequest) { - const { codeVerifier, redirectUri } = resolvedAuthorizationRequest - const result = await client.retrieveAuthorizationCodeAccessToken({ - issuerMetadata: metadata, - authorizationCode: code, - authorizationServer: authorizationServerMetadata.issuer, - dpop, - pkceCodeVerifier: codeVerifier, - redirectUri, - }) - - return { - ...result, - dpop: dpop - ? { - ...result.dpop, - alg: dpop.signer.alg as JwaSignatureAlgorithm, - jwk: getJwkFromJson(dpop.signer.publicJwk), - } - : undefined, - } - } else { - const result = await client.retrievePreAuthorizedCodeAccessToken({ - credentialOffer: resolvedCredentialOffer.credentialOfferPayload, - issuerMetadata: metadata, - dpop, - txCode, - }) + const result = options.code + ? await client.retrieveAuthorizationCodeAccessTokenFromOffer({ + issuerMetadata: metadata, + credentialOffer: credentialOfferPayload, + authorizationCode: options.code, + dpop, + pkceCodeVerifier: options.codeVerifier, + redirectUri: options.redirectUri, + additionalRequestPayload: { + // TODO: remove after new release of oid4vci-ts + issuer_state: credentialOfferPayload.grants?.authorization_code?.issuer_state, + }, + }) + : await client.retrievePreAuthorizedCodeAccessTokenFromOffer({ + credentialOffer: credentialOfferPayload, + issuerMetadata: metadata, + dpop, + txCode: options.txCode, + }) - return { - ...result, - dpop: dpop - ? { - ...result.dpop, - alg: dpop.signer.alg as JwaSignatureAlgorithm, - jwk: getJwkFromJson(dpop.signer.publicJwk), - } - : undefined, - } + return { + ...result, + dpop: dpop + ? { + ...result.dpop, + alg: dpop.signer.alg as JwaSignatureAlgorithm, + jwk: getJwkFromJson(dpop.signer.publicJwk), + } + : undefined, } } @@ -276,28 +305,22 @@ export class OpenId4VciHolderService { options: { resolvedCredentialOffer: OpenId4VciResolvedCredentialOffer acceptCredentialOfferOptions: OpenId4VciAcceptCredentialOfferOptions - resolvedAuthorizationRequestWithCode?: OpenId4VciResolvedAuthorizationRequestWithCode - accessToken?: string + accessToken: string cNonce?: string - dpop?: { jwk: Jwk; alg: JwaSignatureAlgorithm; nonce?: string } + dpop?: OpenId4VciDpopRequestOptions clientId?: string } ) { const { resolvedCredentialOffer, acceptCredentialOfferOptions } = options const { metadata, offeredCredentialConfigurations } = resolvedCredentialOffer - const { credentialsToRequest, credentialBindingResolver, verifyCredentialStatus } = acceptCredentialOfferOptions - + const { credentialConfigurationIds, credentialBindingResolver, verifyCredentialStatus } = + acceptCredentialOfferOptions const client = this.getClient(agentContext) - 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 @@ -315,23 +338,12 @@ export class OpenId4VciHolderService { ) } - const tokenRequestOptions = { - resolvedCredentialOffer, - resolvedAuthorizationRequest: options.resolvedAuthorizationRequestWithCode, - code: options.resolvedAuthorizationRequestWithCode?.code, - txCode: acceptCredentialOfferOptions.userPin, - } as OpenId4VciTokenRequestOptions - - const tokenResponse = options.accessToken - ? { accessTokenResponse: { access_token: options.accessToken, c_nonce: options.cNonce }, dpop: options.dpop } - : await this.requestAccessToken(agentContext, tokenRequestOptions) - const receivedCredentials: Array = [] - let cNonce = tokenResponse.accessTokenResponse.c_nonce - let dpopNonce = tokenResponse.dpop?.nonce + 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( @@ -341,9 +353,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) { + cNonce = ( + await client.requestNonce({ + issuerMetadata: metadata, + }) + ).c_nonce + } + + // Otherwise we will send a dummy request + await client + .retrieveCredentials({ + issuerMetadata: metadata, + accessToken: options.accessToken, + credentialConfigurationId: credentialConfigurationsToRequest[0][0], + // TODO: do we already catch the dpop from error response? + 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') + } + + // TODO: batch issuance should be optional + const batchSize = metadata.credentialIssuer.batch_credential_issuance?.batch_size ?? 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, @@ -352,44 +403,38 @@ export class OpenId4VciHolderService { credentialBindingResolver, }) - let jwtSigner: JwtSigner = - credentialBinding.method === 'did' - ? { - method: credentialBinding.method, - didUrl: credentialBinding.didUrl, - alg: signatureAlgorithm, - } - : { - method: 'jwk', - publicJwk: credentialBinding.jwk.toJson(), - alg: signatureAlgorithm, - } - - 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 }) + 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) + } const { credentialResponse, dpop } = await client.retrieveCredentials({ issuerMetadata: metadata, - accessToken: tokenResponse.accessTokenResponse.access_token, + accessToken: options.accessToken, credentialConfigurationId: offeredCredentialId, - dpop: tokenResponse.dpop + dpop: options.dpop ? await this.getDpopOptions(agentContext, { - ...tokenResponse.dpop, + ...options.dpop, nonce: dpopNonce, - dpopSigningAlgValuesSupported: [tokenResponse.dpop.alg], + dpopSigningAlgValuesSupported: [options.dpop.alg], }) : undefined, - proof: { - proof_type: 'jwt', - jwt, - }, + proofs: batchSize > 1 ? { jwt: jwts } : undefined, + proof: + batchSize === 1 + ? { + proof_type: 'jwt', + jwt: jwts[0], + } + : undefined, }) // Set new nonce values @@ -401,13 +446,28 @@ export class OpenId4VciHolderService { verifyCredentialStatus: verifyCredentialStatus ?? false, credentialIssuerMetadata: metadata.credentialIssuer, format: offeredCredentialConfiguration.format as OpenId4VciCredentialFormatProfile, + credentialConfigurationId: offeredCredentialId, }) - this.logger.debug('received 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, + } } /** @@ -427,63 +487,105 @@ 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 // Now we need to determine how the credential will be bound to us const credentialBinding = await options.credentialBindingResolver({ 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)) + ) { + 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) { + 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 determien 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 } } /** @@ -521,7 +623,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) { @@ -539,19 +641,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 @@ -566,9 +668,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(', ')}'` ) } @@ -581,7 +687,7 @@ export class OpenId4VciHolderService { const supportsJwk = issuerSupportedBindingMethods?.includes('jwk') || supportsCoseKey return { - signatureAlgorithm, + signatureAlgorithms, supportedDidMethods, supportsAllDidMethods, supportsJwk, @@ -593,152 +699,164 @@ export class OpenId4VciHolderService { credentialResponse: CredentialResponse, options: { verifyCredentialStatus: boolean - credentialIssuerMetadata: OpenId4VciIssuerMetadata + credentialIssuerMetadata: OpenId4VciCredentialIssuerMetadata format: OpenId4VciCredentialFormatProfile + credentialConfigurationId: string } ): Promise { - const { verifyCredentialStatus, credentialIssuerMetadata } = options - - this.logger.debug('Credential request response', credentialResponse) + const { verifyCredentialStatus, credentialConfigurationId } = options + this.logger.debug('Credential response', credentialResponse) - if (credentialResponse.credentials) { - throw new CredoError( - `Credential response returned 'credentials' parameter in credential response which is not supported yet.` - ) + const credentials = + credentialResponse.credentials ?? (credentialResponse.credential ? [credentialResponse.credential] : undefined) + if (!credentials) { + throw new CredoError(`Credential response returned neither 'credentials' nor 'credential' parameter.`) } - if (!credentialResponse.credential) { - throw new CredoError( - `Did not receive a successful credential response. Missing 'credential' parameter in credential response.` - ) - } - - const notificationMetadata = - credentialIssuerMetadata.notification_endpoint && credentialResponse.notification_id - ? { - notificationEndpoint: credentialIssuerMetadata.notification_endpoint, - notificationId: credentialResponse.notification_id, - } - : undefined + const notificationId = credentialResponse.notification_id const format = options.format if (format === OpenId4VciCredentialFormatProfile.SdJwtVc) { - if (typeof credentialResponse.credential !== 'string') + if (!credentials.every((c) => typeof c === 'string')) { throw new CredoError( - `Received a credential of format ${format}, but the credential is not a string. ${JSON.stringify( - credentialResponse.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.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 ) { - if (typeof credentialResponse.credential !== 'string') + if (!credentials.every((c) => typeof c === 'string')) { throw new CredoError( - `Received a credential of format ${format}, but the credential is not a string. ${JSON.stringify( - credentialResponse.credential + `Received credential(s) of format ${format}, but not all credential(s) are a string. ${JSON.stringify( + credentials )}` ) + } - const credential = W3cJwtVerifiableCredential.fromSerializedJwt(credentialResponse.credential) - 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'}`) + 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 } + }) + ) + + 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(', ')}` + ) } - return { credential, notificationMetadata } + return { credentials: result.map((r) => r.credential), notificationId, credentialConfigurationId } } else if (format === OpenId4VciCredentialFormatProfile.LdpVc) { - if (typeof credentialResponse.credential === 'string') + if (!credentials.every((c) => typeof c === 'object')) { throw new CredoError( - `Received a credential of format ${format}, but the credential is not an object. ${JSON.stringify( - credentialResponse.credential + `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, + }) - const credential = W3cJsonLdVerifiableCredential.fromJson(credentialResponse.credential) - 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, result } + }) + ) + + 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(', ')}` + ) } - return { credential, notificationMetadata } + return { credentials: result.map((r) => r.credential), notificationId, credentialConfigurationId } } else if (format === OpenId4VciCredentialFormatProfile.MsoMdoc) { - if (typeof credentialResponse.successBody.credential !== 'string') + if (!credentials.every((c) => typeof c === 'string')) { throw new CredoError( - `Received a credential of format ${ - OpenId4VciCredentialFormatProfile.MsoMdoc - }, 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 mdocApi = agentContext.dependencyManager.resolve(MdocApi) - const mdoc = Mdoc.fromBase64Url(credentialResponse.successBody.credential) - const verificationResult = await mdocApi.verify(mdoc, {}) + const result = await Promise.all( + credentials.map(async (credential) => { + const mdoc = Mdoc.fromBase64Url(credential) + const result = await mdocApi.verify(mdoc, {}) + return { + result, + mdoc, + } + }) + ) - if (!verificationResult.isValid) { - agentContext.config.logger.error('Failed to validate credential', { verificationResult }) - throw new CredoError(`Failed to validate mdoc credential. Results = ${verificationResult.error}`) + if (!result.every((r) => r.result.isValid)) { + agentContext.config.logger.error('Failed to validate credentials', { result }) + throw new CredoError( + `Failed to validate mdoc credential. Results = ${result + .map((r) => (r.result.isValid ? undefined : r.result.error)) + .filter(Boolean) + .join(', ')}` + ) } - return { credential: mdoc, notificationMetadata } + return { credentials: result.map((c) => c.mdoc), notificationId, credentialConfigurationId } } - throw new CredoError(`Unsupported credential format`) + throw new CredoError(`Unsupported credential format ${options.format}`) } - private jwtSignerCallback(agentContext: AgentContext) { - const callback: SignJwtCallback = async (signer, { header, payload }) => { - if (signer.method === 'custom' || signer.method === 'x5c') { - throw new CredoError(`Jwt signer method 'custom' and 'x5c' are not supported for jwt signer.`) - } - - let key: Key - - if (signer.method === 'did') { - const didsApi = agentContext.dependencyManager.resolve(DidsApi) - const didDocument = await didsApi.resolveDidDocument(signer.didUrl) - const verificationMethod = didDocument.dereferenceKey(signer.didUrl, ['authentication']) - key = getKeyFromVerificationMethod(verificationMethod) - } else { - key = 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 jws = await this.jwsService.createJwsCompact(agentContext, { - key, - payload: JsonEncoder.toBuffer(payload), - protectedHeaderOptions: { - ...header, - // only pass jwk if signer method is jwk - jwk: signer.method === 'jwk' ? jwk : undefined, - }, - }) + private getClient(agentContext: AgentContext) { + return new Oid4vciClient({ + callbacks: getOid4vciCallbacks(agentContext), + }) + } - return jws - } - return callback + 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 4305341077..45e55b6f8c 100644 --- a/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderServiceOptions.ts +++ b/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderServiceOptions.ts @@ -1,14 +1,13 @@ -import type { - OpenId4VcCredentialHolderBinding, -} from '../shared' +import type { OpenId4VcCredentialHolderBinding } from '../shared' +import type { CredentialConfigurationSupported, CredentialOfferObject, IssuerMetadataResult } from '@animo-id/oid4vci' 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, -} from '@sphereon/oid4vci-common' + +import { AuthorizationFlow as OpenId4VciAuthorizationFlow } from '@animo-id/oid4vci' import { OpenId4VciCredentialFormatProfile } from '../shared/models/OpenId4VciCredentialFormatProfile' -import { CredentialConfigurationSupported, CredentialOfferObject, IssuerMetadataResult } from '@animo-id/oid4vci' + +export { OpenId4VciAuthorizationFlow } export type OpenId4VciSupportedCredentialFormats = | OpenId4VciCredentialFormatProfile.JwtVcJson @@ -25,9 +24,10 @@ export const openId4VciSupportedCredentialFormats: OpenId4VciSupportedCredential OpenId4VciCredentialFormatProfile.MsoMdoc, ] -export interface OpenId4VciNotificationMetadata { - notificationId: string - notificationEndpoint: string +export interface OpenId4VciDpopRequestOptions { + jwk: Jwk + alg: JwaSignatureAlgorithm + nonce?: string } /** @@ -37,17 +37,19 @@ 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; alg: JwaSignatureAlgorithm; 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 { @@ -61,25 +63,21 @@ export interface OpenId4VciResolvedCredentialOffer { offeredCredentialConfigurations: Record } -export interface OpenId4VciResolvedAuthorizationRequest extends OpenId4VciAuthCodeFlowOptions { - /** - * The authorization server used for creating the authorization request url - */ - authorizationServer: string - - codeVerifier?: string - authorizationRequestUri: string -} - -export interface OpenId4VciResolvedAuthorizationRequestWithCode extends OpenId4VciResolvedAuthorizationRequest { - code: string -} +export type OpenId4VciResolvedAuthorizationRequest = + | { + oid4vpRequestUrl: string + authorizationFlow: OpenId4VciAuthorizationFlow.PresentationDuringIssuance + } + | { + 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,33 +93,50 @@ export interface OpenId4VciSendNotificationOptions { */ notificationEvent: OpenId4VciNotificationEvent - dpop?: { jwk: Jwk; alg: JwaSignatureAlgorithm; nonce?: string } + dpop?: OpenId4VciDpopRequestOptions } -interface OpenId4VcTokenRequestBaseOptions { +export interface OpenId4VcAuthorizationCodeTokenRequestOptions { resolvedCredentialOffer: OpenId4VciResolvedCredentialOffer - txCode?: string -} - -export interface OpenId4VcAuthorizationCodeTokenRequestOptions extends OpenId4VcTokenRequestBaseOptions { - resolvedAuthorizationRequest: OpenId4VciResolvedAuthorizationRequest 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; alg: JwaSignatureAlgorithm; nonce?: string } + dpop?: OpenId4VciDpopRequestOptions /** * The client id used for authorization. Only required if authorization_code flow was used. @@ -135,17 +150,11 @@ export interface OpenId4VciCredentialRequestOptions extends Omit { .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/OpenId4VcIssuerApi.ts b/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerApi.ts index bae4c0d3b9..5cc2460091 100644 --- a/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerApi.ts +++ b/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerApi.ts @@ -7,8 +7,6 @@ import type { import { AgentContext, injectable } from '@credo-ts/core' -import { credentialsSupportedV13ToV11, type OpenId4VciCredentialRequest } from '../shared' - import { OpenId4VcIssuerModuleConfig } from './OpenId4VcIssuerModuleConfig' import { OpenId4VcIssuerService } from './OpenId4VcIssuerService' @@ -59,19 +57,13 @@ export class OpenId4VcIssuerApi { } public async updateIssuerMetadata(options: OpenId4VcUpdateIssuerRecordOptions) { - const { issuerId, credentialConfigurationsSupported, credentialsSupported, ...issuerOptions } = options + const { issuerId, credentialConfigurationsSupported, display, dpopSigningAlgValuesSupported } = 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 return this.openId4VcIssuerService.updateIssuer(this.agentContext, issuer) } @@ -102,18 +94,6 @@ 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) diff --git a/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerModule.ts b/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerModule.ts index 82334e8a34..c39d1b9a4a 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 { Response } from 'express' +import { setGlobalConfig } from '@animo-id/oauth2' import { AgentConfig } from '@credo-ts/core' import { getAgentContextForActorId, getRequestContext, importExpress } from '../shared/router' @@ -17,8 +19,9 @@ import { configureCredentialEndpoint, configureIssuerMetadataEndpoint, configureOAuthAuthorizationServerMetadataEndpoint, + configureJwksEndpoint, } from './router' -import { Response } from 'express' +import { configureNonceEndpoint } from './router/nonceEndpoint' /** * @public @@ -32,16 +35,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) @@ -118,6 +126,8 @@ export class OpenId4VcIssuerModule implements Module { // Configure endpoints configureIssuerMetadataEndpoint(endpointRouter) + configureJwksEndpoint(endpointRouter) + configureNonceEndpoint(endpointRouter, this.config.nonceEndpoint) configureOAuthAuthorizationServerMetadataEndpoint(endpointRouter) configureCredentialOfferEndpoint(endpointRouter, this.config.credentialOfferEndpoint) configureAccessTokenEndpoint(endpointRouter, this.config.accessTokenEndpoint) diff --git a/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerModuleConfig.ts b/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerModuleConfig.ts index 71eaa43c9a..369d0722e3 100644 --- a/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerModuleConfig.ts +++ b/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerModuleConfig.ts @@ -2,13 +2,14 @@ import type { OpenId4VciAccessTokenEndpointConfig, OpenId4VciCredentialEndpointConfig, OpenId4VciCredentialOfferEndpointConfig, + OpenId4VciNonceEndpointConfig, } from './router' import type { Optional } from '@credo-ts/core' 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_TOKEN_EXPIRES_IN = 3 * 60 // 3 minutes const DEFAULT_PRE_AUTH_CODE_EXPIRES_IN = 3 * 60 // 3 minutes @@ -29,11 +30,12 @@ export interface OpenId4VcIssuerModuleConfigOptions { router?: Router endpoints: { + nonce?: Optional credentialOffer?: Optional credential: Optional accessToken?: Optional< OpenId4VciAccessTokenEndpointConfig, - 'cNonceExpiresInSeconds' | 'endpointPath' | 'preAuthorizedCodeExpirationInSeconds' | 'tokenExpiresInSeconds' + 'endpointPath' | 'preAuthorizedCodeExpirationInSeconds' | 'tokenExpiresInSeconds' > } } @@ -75,7 +77,6 @@ export class OpenId4VcIssuerModuleConfig { 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, @@ -94,4 +95,18 @@ export class OpenId4VcIssuerModuleConfig { endpointPath: userOptions.endpointPath ?? '/offers', } } + + /** + * Get the nonce endpoint config, with default values set + */ + public get nonceEndpoint(): OpenId4VciNonceEndpointConfig { + // Use user supplied options, or return defaults. + const userOptions = this.options.endpoints.nonce ?? {} + + return { + ...userOptions, + cNonceExpiresInSeconds: userOptions.cNonceExpiresInSeconds ?? DEFAULT_C_NONCE_EXPIRES_IN, + endpointPath: userOptions.endpointPath ?? '/offers', + } + } } diff --git a/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerService.ts b/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerService.ts index 9cc4c2875f..56f49c4af1 100644 --- a/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerService.ts +++ b/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerService.ts @@ -3,79 +3,65 @@ import type { OpenId4VciCreateCredentialOfferOptions, OpenId4VciCreateIssuerOptions, OpenId4VciPreAuthorizedCodeFlowConfig, - OpenId4VcIssuerMetadata, - OpenId4VciSignSdJwtCredential, - OpenId4VciSignW3cCredential, + OpenId4VciSignW3cCredentials, OpenId4VciAuthorizationCodeFlowConfig, - OpenId4VciSignMdocCredential, } from './OpenId4VcIssuerServiceOptions' -import type { OpenId4VcIssuanceSessionRecord } from './repository' -import type { - OpenId4VcCredentialHolderBinding, - OpenId4VciCredentialConfigurationsSupported, - OpenId4VciCredentialOfferPayload, - OpenId4VciCredentialRequest, -} from '../shared' -import { preAuthorizedCodeGrantIdentifier } from '@animo-id/oauth2' -import { PRE_AUTH_GRANT_LITERAL } from '@sphereon/oid4vci-common' -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 { OpenId4VcCredentialHolderBindingWithKey, OpenId4VciMetadata } from '../shared' +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, + 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, } from '@credo-ts/core' -import { VcIssuerBuilder } from '@sphereon/oid4vci-issuer' -import { credentialsSupportedV11ToV13, OpenId4VciCredentialFormatProfile } from '../shared' -import { credentialsSupportedV13ToV11, getOfferedCredentials } from '../shared/issuerMetadataUtils' +import { OpenId4VciCredentialFormatProfile } from '../shared' +import { dynamicOid4vciClientAuthentication, getOid4vciCallbacks } from '../shared/callbacks' +import { 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 @@ -83,20 +69,17 @@ 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 @@ -106,185 +89,198 @@ export class OpenId4VcIssuerService { agentContext: AgentContext, options: OpenId4VciCreateCredentialOfferOptions & { issuer: OpenId4VcIssuerRecord } ) { - const { preAuthorizedCodeFlowConfig, authorizationCodeFlowConfig, issuer, offeredCredentials } = options - + 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.') } - const vcIssuer = this.getVcIssuer(agentContext, issuer) + const vcIssuer = this.getIssuer(agentContext, issuer) + const issuerMetadata = await this.getIssuerMetadata(agentContext, issuer) - // 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(options.offeredCredentials, vcIssuer.issuerMetadata.credential_configurations_supported) const uniqueOfferedCredentials = Array.from(new Set(options.offeredCredentials)) if (uniqueOfferedCredentials.length !== offeredCredentials.length) { throw new CredoError('All offered credentials must have unique ids.') } // We always use shortened URIs currently - const hostedCredentialOfferUri = joinUriParts(vcIssuer.issuerMetadata.credential_issuer, [ + const hostedCredentialOfferUri = joinUriParts(issuerMetadata.credentialIssuer.credential_issuer, [ this.openId4VcIssuerConfig.credentialOfferEndpoint.endpointPath, // It doesn't really matter what the url is, as long as it's unique utils.uuid(), ]) - // TODO: HAIP - // TODO: for grant type authorization_code, the issuer must include a scope value in order to allow the wallet to identify the desired credential type. - // TODO: The wallet MUST use that value in the scope Authorization parameter. - // TODO: add support for scope in the credential offer in sphereon-oid4vci - const issuerMetadata = this.getIssuerMetadata(agentContext, issuer) - this.verifyGrantAuthorizationServers(issuerMetadata, preAuthorizedCodeFlowConfig, authorizationCodeFlowConfig) - - const hasMultipleAuthorizationServers = issuerMetadata.authorizationServers?.length ?? 0 > 1 const grants = await this.getGrantsFromConfig(agentContext, { - preAuthorizedCodeFlowConfig: preAuthorizedCodeFlowConfig - ? { - ...preAuthorizedCodeFlowConfig, - - // FIXME: this is removed from the offer. Need to wait for - // https://github.com/Sphereon-Opensource/OID4VC/pull/159 - // Must only be used if multiple authorization servers are present - authorizationServerUrl: hasMultipleAuthorizationServers - ? preAuthorizedCodeFlowConfig?.authorizationServerUrl - : undefined, - } - : undefined, - authorizationCodeFlowConfig: authorizationCodeFlowConfig - ? { - ...authorizationCodeFlowConfig, - - // Must only be used if multiple authorization servers are present - authorizationServerUrl: hasMultipleAuthorizationServers - ? authorizationCodeFlowConfig?.authorizationServerUrl - : undefined, - } - : undefined, + 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[preAuthorizedCodeGrantIdentifier]?.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: { + issuerState: credentialOfferObject.grants?.authorization_code?.issuer_state, + }, + preAuthorizedCode: credentialOfferObject.grants?.[preAuthorizedCodeGrantIdentifier]?.['pre-authorized_code'], + userPin: preAuthorizedCodeFlowConfig?.txCode + ? generateTxCode(agentContext, preAuthorizedCodeFlowConfig.txCode) + : undefined, + issuanceMetadata: options.issuanceMetadata, }) - - // TODO: is isuer_state stored? - // issuanceSession.issuerState = grants.authorization_code?.issuer_state - - if (options.version !== 'v1.draft13') { - const v13CredentialOfferPayload = issuanceSession.credentialOfferPayload as CredentialOfferPayloadV1_0_13 - const v11CredentialOfferPayload: CredentialOfferPayloadV1_0_11 = { - ...v13CredentialOfferPayload, - credentials: v13CredentialOfferPayload.credential_configuration_ids, - } - - 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, issuer) + 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, + 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 crednetial 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, } } @@ -317,66 +313,33 @@ export class OpenId4VcIssuerService { // - createdAt // - purpose const accessTokenSignerKey = await agentContext.wallet.createKey({ - keyType: KeyType.Ed25519, + keyType: options.accessTokenSignerKeyType ?? KeyType.Ed25519, }) - const openId4VcIssuerBase = { + // this is required for HAIP + // TODO: do we also need to provide some way to let the wallet know which authorization server + // TODO: can issue which credentials? + const openId4VcIssuer = new OpenId4VcIssuerRecord({ issuerId: options.issuerId ?? utils.uuid(), display: options.display, dpopSigningAlgValuesSupported: options.dpopSigningAlgValuesSupported, accessTokenPublicKeyFingerprint: accessTokenSignerKey.fingerprint, authorizationServerConfigs: options.authorizationServerConfigs, - } as const - - const credentialsSupported = - options.credentialsSupported?.map((credentialSupported) => { - return { - ...credentialSupported, - scope: - credentialSupported.scope ?? options.authorizationServerConfigs?.length ?? 0 > 0 - ? credentialSupported.id - : undefined, - } - }) ?? [] - - const credentialConfigurationsSupported = - options.credentialConfigurationsSupported && - Object.fromEntries( - Object.entries(options.credentialConfigurationsSupported).map(([id, credentialConfiguration]) => { - return [ - id, - { - ...credentialConfiguration, - scope: - credentialConfiguration.scope ?? (options.authorizationServerConfigs?.length ?? 0 > 0 ? id : undefined), - }, - ] - }) - ) - - // If we have an authorization server, we also want to publish a scope to request each credential - // this is required for HAIP - // TODO: do we also need to provide some way to let the wallet know which authorization server - // TODO: can issue which credentials? - const openId4VcIssuer = options.credentialsSupported - ? new OpenId4VcIssuerRecord({ - ...openId4VcIssuerBase, - credentialsSupported: credentialsSupported, - }) - : new OpenId4VcIssuerRecord({ - ...openId4VcIssuerBase, - credentialConfigurationsSupported: - credentialConfigurationsSupported as OpenId4VciCredentialConfigurationsSupported, - }) + credentialConfigurationsSupported: options.credentialConfigurationsSupported, + }) 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 @@ -384,9 +347,29 @@ 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 @@ -397,143 +380,168 @@ export class OpenId4VcIssuerService { ] : undefined - 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, - authorizationServers, - dpopSigningAlgValuesSupported: issuerRecord.dpopSigningAlgValuesSupported, - } satisfies OpenId4VcIssuerMetadata - - return issuerMetadata - } + const credentialIssuerMetadata = { + credential_issuer: issuerUrl, + credential_endpoint: joinUriParts(issuerUrl, [config.credentialEndpoint.endpointPath]), + credential_configurations_supported: issuerRecord.credentialConfigurationsSupported ?? {}, + authorization_servers: authorizationServers, + display: issuerRecord.display, + nonce_endpoint: joinUriParts(issuerUrl, [config.nonceEndpoint.endpointPath]), + } satisfies CredentialIssuerMetadata - 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) - }, - }) + const issuerAuthorizationServer = { + issuer: issuerUrl, + token_endpoint: joinUriParts(issuerUrl, [config.accessTokenEndpoint.endpointPath]), + 'pre-authorized_grant_anonymous_access_supported': true, - if (!isValid) throw new CredoError('Could not verify JWT signature.') + jwks_uri: joinUriParts(issuerUrl, ['jwks.json']), - // 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, - } - } - } + // TODO: presentation during issuance + // authorization_challenge_endpoint: '' - private getVcIssuer(agentContext: AgentContext, issuer: OpenId4VcIssuerRecord) { - const issuerMetadata = this.getIssuerMetadata(agentContext, issuer) + // 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 - const builder = new VcIssuerBuilder() - .withCredentialIssuer(issuerMetadata.issuerUrl) - .withCredentialEndpoint(issuerMetadata.credentialEndpoint) - .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') - }) + code_challenge_methods_supported: [PkceCodeChallengeMethod.S256], + dpop_signing_alg_values_supported: issuerRecord.dpopSigningAlgValuesSupported, + } satisfies AuthorizationServerMetadata - if (issuerMetadata.authorizationServers) { - builder.withAuthorizationServers(issuerMetadata.authorizationServers) - } else { - builder.withTokenEndpoint(issuerMetadata.tokenEndpoint) + return { + credentialIssuer: credentialIssuerMetadata, + authorizationServers: [issuerAuthorizationServer, ...extraAuthorizationServers], } + } - if (issuerMetadata.issuerDisplay) { - builder.withIssuerDisplay(issuerMetadata.issuerDisplay) - } + public async createNonce(agentContext: AgentContext, issuer: OpenId4VcIssuerRecord) { + const issuerMetadata = await this.getIssuerMetadata(agentContext, issuer) + const jwsService = agentContext.dependencyManager.resolve(JwsService) + + const cNonceExpiresInSeconds = this.openId4VcIssuerConfig.nonceEndpoint.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], + }, + }) - return builder.build() + return { + cNonce, + cNonceExpiresAt, + cNonceExpiresInSeconds, + } } - private verifyGrantAuthorizationServers( - issuerMetadata: OpenId4VcIssuerMetadata, - preAuthorizedCodeFlowConfig?: OpenId4VciPreAuthorizedCodeFlowConfig, - authorizationCodeFlowConfig?: OpenId4VciAuthorizationCodeFlowConfig - ) { - const hasMultipleAuthorizationServers = issuerMetadata.authorizationServers?.length ?? 0 > 1 - if ( - authorizationCodeFlowConfig && - hasMultipleAuthorizationServers && - !authorizationCodeFlowConfig.authorizationServerUrl - ) { - throw new CredoError( - 'The authorization code flow requires an explicit authorization server url if multiple authorization servers are present in the credential issuer metadata.' - ) - } + /** + * @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) - if ( - issuerMetadata.authorizationServers && - authorizationCodeFlowConfig?.authorizationServerUrl && - !issuerMetadata.authorizationServers.includes(authorizationCodeFlowConfig.authorizationServerUrl) - ) { - throw new CredoError( - `The authorizationServerlUrl '${ - authorizationCodeFlowConfig.authorizationServerUrl - }' in authorization code flow config is not present in issuer metadata authorization_servers. Allowed values are ${issuerMetadata.authorizationServers.join( - ', ' - )}` - ) - } + const key = Key.fromFingerprint(issuer.accessTokenPublicKeyFingerprint) + const jwk = getJwkFromKey(key) - if ( - issuerMetadata.authorizationServers && - preAuthorizedCodeFlowConfig?.authorizationServerUrl && - !issuerMetadata.authorizationServers.includes(preAuthorizedCodeFlowConfig.authorizationServerUrl) - ) { - throw new CredoError( - `The authorizationServerlUrl '${ - preAuthorizedCodeFlowConfig.authorizationServerUrl - }' in pre authorized code flow config is not present in issuer metadata authorization_servers. Allowed values are ${issuerMetadata.authorizationServers.join( - ', ' - )}` - ) + const jwt = Jwt.fromSerializedJwt(cNonce) + jwt.payload.validate() + + 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`) } + const verification = await jwsService.verifyJws(agentContext, { + jws: cNonce, + jwkResolver: () => jwk, + }) + if ( - preAuthorizedCodeFlowConfig && - hasMultipleAuthorizationServers && - !preAuthorizedCodeFlowConfig.authorizationServerUrl + !verification.signerKeys + .map((singerKey) => singerKey.fingerprint) + .includes(issuer.accessTokenPublicKeyFingerprint) ) { - throw new CredoError( - 'The pre authorized code flow requires an explicit authorization server url if multiple authorization servers are present in the credential issuer metadata.' - ) + throw new CredoError('Invalid nonce') } } + public getIssuer(agentContext: AgentContext, issuerRecord: OpenId4VcIssuerRecord) { + return new Oid4vciIssuer({ + callbacks: { + ...getOid4vciCallbacks(agentContext), + clientAuthentication: dynamicOid4vciClientAuthentication(agentContext, issuerRecord), + }, + }) + } + + public getOauth2Client(agentContext: AgentContext) { + return new Oauth2Client({ + callbacks: getOid4vciCallbacks(agentContext), + }) + } + + public getOauth2AuthorizationServer(agentContext: AgentContext) { + return new Oauth2AuthorizationServer({ + callbacks: getOid4vciCallbacks(agentContext), + }) + } + + public getResourceServer(agentContext: AgentContext) { + return new Oauth2ResourceServer({ + callbacks: getOid4vciCallbacks(agentContext), + }) + } + + /** + * 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})` + ) + + const previousState = issuanceSession.state + issuanceSession.state = newState + await this.openId4VcIssuanceSessionRepository.update(agentContext, issuanceSession) + + this.emitStateChangedEvent(agentContext, issuanceSession, previousState) + } + + private emitStateChangedEvent( + agentContext: AgentContext, + issuanceSession: OpenId4VcIssuanceSessionRecord, + previousState: OpenId4VcIssuanceSessionState | null + ) { + const eventEmitter = agentContext.dependencyManager.resolve(EventEmitter) + + eventEmitter.emit(agentContext, { + type: OpenId4VcIssuerEvents.IssuanceSessionStateChanged, + payload: { + issuanceSession: issuanceSession.clone(), + previousState: previousState, + }, + }) + } + private async getGrantsFromConfig( agentContext: AgentContext, config: { @@ -543,22 +551,17 @@ export class OpenId4VcIssuerService { ) { const { preAuthorizedCodeFlowConfig, authorizationCodeFlowConfig } = config - const grants: Grant = {} + // TODO: export form @animo-id/oid4vci + const grants: Parameters[0]['grants'] = {} // Pre auth if (preAuthorizedCodeFlowConfig) { - const { userPinRequired, txCode, authorizationServerUrl, preAuthorizedCode } = preAuthorizedCodeFlowConfig + const { txCode, authorizationServerUrl, preAuthorizedCode } = preAuthorizedCodeFlowConfig - if (userPinRequired === false && txCode) { - throw new CredoError('The userPinRequired option must be set to true when using txCode.') - } - - grants[PRE_AUTH_GRANT_LITERAL] = { + grants[preAuthorizedCodeGrantIdentifier] = { 'pre-authorized_code': preAuthorizedCode ?? (await agentContext.wallet.generateNonce()), - tx_code: txCode ?? (userPinRequired ? {} : undefined), + tx_code: txCode, authorization_server: authorizationServerUrl, - // v11 only - user_pin_required: userPinRequired ?? txCode !== undefined, } } @@ -573,342 +576,215 @@ export class OpenId4VcIssuerService { return grants } - private findOfferedCredentialsMatchingRequest( - agentContext: AgentContext, - credentialOffer: OpenId4VciCredentialOfferPayload, - credentialRequest: OpenId4VciCredentialRequest, - allCredentialConfigurationsSupported: OpenId4VciCredentialConfigurationsSupported, - issuanceSession: OpenId4VcIssuanceSessionRecord - ): OpenId4VciCredentialConfigurationsSupported { - const offeredCredentialsData = isCredentialOfferV1Draft13(credentialOffer) - ? credentialOffer.credential_configuration_ids - : credentialOffer.credentials - - const { credentialConfigurationsSupported: offeredCredentialConfigurations } = getOfferedCredentials( - offeredCredentialsData as string[], - allCredentialConfigurationsSupported - ) + 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`) + } - 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.` - ) + if (signer.method === 'jwk') { + const jwk = getJwkFromJson(signer.publicJwk) + credentialHolderBindings.push({ + method: 'jwk', + jwk, + key: jwk.key, + }) } - return { - [credentialRequest.credential_identifier]: offeredCredential, + if (signer.method === 'did') { + const key = await getKeyFromDid(agentContext, signer.didUrl) + credentialHolderBindings.push({ + method: 'did', + didUrl: signer.didUrl, + key, + }) } } - 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 - } - - return false - }) - ) + return credentialHolderBindings } - private getSdJwtVcCredentialSigningCallback = ( + private async getSignedCredentials( agentContext: AgentContext, - options: OpenId4VciSignSdJwtCredential - ): CredentialSignerCallback => { - return async () => { - const sdJwtVcApi = agentContext.dependencyManager.resolve(SdJwtVcApi) - - const sdJwtVc = await sdJwtVcApi.sign(options) - return getSphereonVerifiableCredential(sdJwtVc) + options: OpenId4VciCreateCredentialResponseOptions & { + issuer: OpenId4VcIssuerRecord + issuanceSession: OpenId4VcIssuanceSessionRecord + requestFormat: CredentialRequestFormatSpecific + proofSigners: JwtSigner[] } - } + ): Promise<{ + credentials: string[] | Record[] + format: `${OpenId4VciCredentialFormatProfile}` + credentialConfigurationId: string + }> { + const { issuanceSession, issuer, requestFormat } = options + const issuerMetadata = await this.getIssuerMetadata(agentContext, issuer) + + const notIssuedCredentialConfigurationIds = + options.issuanceSession.credentialOfferPayload.credential_configuration_ids.filter( + (id) => !issuanceSession.issuedCredentials.includes(id) + ) - private getMsoMdocCredentialSigningCallback = ( - agentContext: AgentContext, - options: OpenId4VciSignMdocCredential - ): CredentialSignerCallback => { - return async () => { - const mdocApi = agentContext.dependencyManager.resolve(MdocApi) + // TODO: this + next validation should be handeld by oid4vci lib + const offeredCredentialsMatchingRequest = getCredentialConfigurationsMatchingRequestFormat({ + requestFormat, + credentialConfigurations: getOfferedCredentials( + notIssuedCredentialConfigurationIds, + issuerMetadata.credentialIssuer.credential_configurations_supported + ), + }) - const mdoc = await mdocApi.sign(options) - return getSphereonVerifiableCredential(mdoc) + const matchingCredentialConfigurationIds = Object.keys(offeredCredentialsMatchingRequest) as [string, ...string[]] + if (matchingCredentialConfigurationIds.length === 0) { + throw new Oauth2ServerErrorResponseError({ + error: Oauth2ErrorCodes.CredentialRequestDenied, + error_description: 'No offered credentials matching the credential request', + }) } - } - - private getW3cCredentialSigningCallback = ( - agentContext: AgentContext, - options: OpenId4VciSignW3cCredential - ): CredentialSignerCallback => { - return async (opts) => { - const { jwtVerifyResult, format } = opts - const { kid, didDocument: holderDidDocument } = jwtVerifyResult - 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.') + const mapper = + options.credentialRequestToCredentialMapper ?? + this.openId4VcIssuerConfig.credentialEndpoint.credentialRequestToCredentialMapper - 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 - } + const holderBindings = await this.getHolderBindingFromRequestProofs(agentContext, options.proofSigners) + const signOptions = await mapper({ + agentContext, + issuanceSession, + holderBindings, + credentialOffer: issuanceSession.credentialOfferPayload, - const didsApi = agentContext.dependencyManager.resolve(DidsApi) - const issuerDidDocument = await didsApi.resolveDidDocument(options.verificationMethod) - const verificationMethod = issuerDidDocument.dereferenceVerificationMethod(options.verificationMethod) - - 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}`) - } - const alg = supportedSignatureAlgorithms[0] - - if (!alg) { - throw new CredoError(`No supported JWA signature algorithms for key type ${key.keyType}`) - } - - const signed = await this.w3cCredentialService.signCredential(agentContext, { - format: w3cServiceFormat, - credential: options.credential, - verificationMethod: options.verificationMethod, - alg, - }) + credentialRequest: options.credentialRequest, + credentialRequestFormat: options.requestFormat, - return getSphereonVerifiableCredential(signed) - } else { - const key = getKeyFromVerificationMethod(verificationMethod) - const proofType = getProofTypeFromKey(agentContext, key) + // Macthing credential configuration ids + credentialConfigurationsSupported: offeredCredentialsMatchingRequest, + credentialConfigurationIds: matchingCredentialConfigurationIds, + }) - const signed = await this.w3cCredentialService.signCredential(agentContext, { - format: w3cServiceFormat, - credential: options.credential, - verificationMethod: options.verificationMethod, - proofType: proofType, - }) + const credentialHasAlreadyBeenIssued = issuanceSession.issuedCredentials.includes( + signOptions.credentialConfigurationId + ) - return getSphereonVerifiableCredential(signed) - } + if (credentialHasAlreadyBeenIssued) { + 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` + ) } - } - private async getHolderBindingFromRequest( - agentContext: AgentContext, - credentialRequest: OpenId4VciCredentialRequest - ) { - if (!credentialRequest.proof?.jwt) throw new CredoError('Received a credential request without a proof') + // NOTE: we may want to allow a mismatch between this (as with new attestations not every key + // needs a separate proof), but for 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` + ) + } - const jwt = Jwt.fromSerializedJwt(credentialRequest.proof.jwt) + if (signOptions.format === ClaimFormat.JwtVc || signOptions.format === ClaimFormat.LdpVc) { + const oid4vciFormatMap: Record = { + [OpenId4VciCredentialFormatProfile.JwtVcJson]: ClaimFormat.JwtVc, + [OpenId4VciCredentialFormatProfile.JwtVcJsonLd]: ClaimFormat.JwtVc, + [OpenId4VciCredentialFormatProfile.LdpVc]: ClaimFormat.LdpVc, + } - 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('#')) { + const expectedClaimFormat = oid4vciFormatMap[options.requestFormat.format] + if (signOptions.format !== expectedClaimFormat) { throw new CredoError( - `kid containing did MUST point to a specific key within the did document: ${jwt.header.kid}` + `Invalid credential format returned by sign options. Expected '${expectedClaimFormat}', received '${signOptions.format}'.` ) } - 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.`) + 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}'.` + ) } - const didResolver = agentContext.dependencyManager.resolve(DidResolverService) - const didDocument = await didResolver.resolveDidDocument(agentContext, parsedDid.didUrl) - const key = getKeyFromVerificationMethod(didDocument.dereferenceKey(parsedDid.didUrl, ['assertionMethod'])) + 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}'.` + ) + } + const sdJwtVcApi = agentContext.dependencyManager.resolve(SdJwtVcApi) return { - method: 'did', - didUrl: jwt.header.kid, - key, - } satisfies OpenId4VcCredentialHolderBinding & { key: Key } - } else if (jwt.header.jwk) { - const jwk = getJwkFromJson(jwt.header.jwk) + 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}'.` + ) + } + + const mdocApi = agentContext.dependencyManager.resolve(MdocApi) return { - method: 'jwk', - jwk: jwk, - key: jwk.key, - } satisfies OpenId4VcCredentialHolderBinding & { key: Key } + 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('Either kid or jwk must be present in credential request proof header') + throw new CredoError(`Unsupported credential format ${signOptions.format}`) } } - private getCredentialDataSupplier = ( + private async signW3cCredential( agentContext: AgentContext, - options: OpenId4VciCreateCredentialResponseOptions & { - issuer: OpenId4VcIssuerRecord - issuanceSession: OpenId4VcIssuanceSessionRecord - } - ): CredentialDataSupplier => { - return async (args: CredentialDataSupplierArgs) => { - const { issuanceSession, issuer } = options - - const credentialRequest = args.credentialRequest as OpenId4VciCredentialRequest - - const issuerMetadata = this.getIssuerMetadata(agentContext, issuer) - - const offeredCredentialsMatchingRequest = this.findOfferedCredentialsMatchingRequest( - agentContext, - options.issuanceSession.credentialOfferPayload, - credentialRequest, - issuerMetadata.credentialConfigurationsSupported, - issuanceSession - ) - - const numOfferedCredentialsMatchingRequest = Object.keys(offeredCredentialsMatchingRequest).length - if (numOfferedCredentialsMatchingRequest === 0) { - throw new CredoError('No offered credentials match the credential request.') + 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}`) } - if (numOfferedCredentialsMatchingRequest > 1) { - agentContext.config.logger.debug( - 'Multiple credentials from credentials supported matching request, picking first one.' - ) + const alg = supportedSignatureAlgorithms[0] + if (!alg) { + throw new CredoError(`No supported JWA signature algorithms for key type ${key.keyType}`) } - 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, + return await this.w3cCredentialService.signCredential(agentContext, { + format: ClaimFormat.JwtVc, + credential: options.credential, + verificationMethod: options.verificationMethod, + alg, }) + } else { + const proofType = getProofTypeFromKey(agentContext, key) - const credentialHasAlreadyBeenIssued = issuanceSession.issuedCredentials.includes( - signOptions.credentialSupportedId - ) - if (credentialHasAlreadyBeenIssued) { - throw new CredoError( - `The requested credential with id '${signOptions.credentialSupportedId}' has already been issued.` - ) - } - - 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 { - 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}'.` - ) - } - - 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}'.` - ) - } - - 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}'.` - ) - } - - 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}`) - } + 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 15b9ad0829..f3b18c964d 100644 --- a/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerServiceOptions.ts +++ b/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerServiceOptions.ts @@ -1,21 +1,14 @@ +import type { OpenId4VcIssuanceSessionRecord, OpenId4VcIssuerRecordProps } from './repository' import type { - OpenId4VcIssuanceSessionRecord, - OpenId4VcIssuerRecordCredentialConfigurationsSupportedProps, - OpenId4VcIssuerRecordCredentialSupportedProps, - OpenId4VcIssuerRecordProps, -} from './repository' -import type { - OpenId4VcCredentialHolderBinding, - OpenId4VciCredentialConfigurationsSupported, + OpenId4VcCredentialHolderBindingWithKey, OpenId4VciCredentialConfigurationsSupportedWithFormats, - OpenId4VciCredentialConfigurationSupportedWithFormats, OpenId4VciCredentialOfferPayload, OpenId4VciCredentialRequest, - OpenId4VciCredentialRequestWithFormats, - OpenId4VciIssuerMetadataDisplay, + OpenId4VciCredentialRequestFormatSpecific, + OpenId4VciCredentialIssuerMetadataDisplay, OpenId4VciTxCode, } from '../shared' -import type { OpenId4VciAuthorizationServerConfig } from '../shared/models/AuthorizationServer' +import type { OpenId4VciAuthorizationServerConfig } from '../shared/models/OpenId4VciAuthorizationServerConfig' import type { AgentContext, ClaimFormat, @@ -23,7 +16,7 @@ import type { SdJwtVcSignOptions, JwaSignatureAlgorithm, MdocSignOptions, - Key, + KeyType, } from '@credo-ts/core' export interface OpenId4VciPreAuthorizedCodeFlowConfig { @@ -31,13 +24,6 @@ export interface OpenId4VciPreAuthorizedCodeFlowConfig { /** * 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 @@ -58,18 +44,6 @@ export interface OpenId4VciAuthorizationCodeFlowConfig { authorizationServerUrl?: string } -export type OpenId4VcIssuerMetadata = { - // The Credential Issuer's identifier. (URL using the https scheme) - issuerUrl: string - credentialEndpoint: string - tokenEndpoint: string - authorizationServers?: string[] - - issuerDisplay?: OpenId4VciIssuerMetadataDisplay[] - credentialConfigurationsSupported: OpenId4VciCredentialConfigurationsSupported - dpopSigningAlgValuesSupported?: [JwaSignatureAlgorithm, ...JwaSignatureAlgorithm[]] -} - export interface OpenId4VciCreateCredentialOfferOptions { // 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 @@ -112,11 +86,6 @@ 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: { agentContext: AgentContext @@ -129,7 +98,14 @@ export type OpenId4VciCredentialRequestToCredentialMapper = (options: { /** * The credential request received from the wallet */ - credentialRequest: OpenId4VciCredentialRequestWithFormats + 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 @@ -137,11 +113,12 @@ export type OpenId4VciCredentialRequestToCredentialMapper = (options: { 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[] /** * The credential configurations supported entries from the issuer metadata @@ -155,28 +132,32 @@ export type OpenId4VciCredentialRequestToCredentialMapper = (options: { * NOTE: This will probably become a single entry, as it will be matched on id */ credentialConfigurationIds: [string, ...string[]] -}) => Promise | OpenId4VciSignCredential +}) => 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 type OpenId4VciCreateIssuerOptions = { @@ -185,13 +166,20 @@ 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 +} export type OpenId4VcUpdateIssuerRecordOptions = Pick< OpenId4VcIssuerRecordProps, - 'issuerId' | 'display' | 'dpopSigningAlgValuesSupported' -> & - (OpenId4VcIssuerRecordCredentialSupportedProps | OpenId4VcIssuerRecordCredentialConfigurationsSupportedProps) + 'issuerId' | 'display' | 'dpopSigningAlgValuesSupported' | 'credentialConfigurationsSupported' +> 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..1200eda919 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,34 +39,42 @@ 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({ @@ -88,14 +95,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 +110,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 +126,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 +217,12 @@ describe('OpenId4VcIssuer', () => { issuerVerificationMethod = _issuerVerificationMethod openId4VcIssuer = await issuer.modules.openId4VcIssuer.createIssuer({ - credentialsSupported: [ + credentialConfigurationsSupported: { openBadgeCredential, universityDegreeCredential, universityDegreeCredentialLd, universityDegreeCredentialSdJwt, - ], + }, }) }) @@ -232,12 +238,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 +252,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 +273,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 +287,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 +303,57 @@ 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, 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 +374,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 +385,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 +402,47 @@ 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, + 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, 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 +453,6 @@ describe('OpenId4VcIssuer', () => { offeredCredentials: [openBadgeCredential.id], preAuthorizedCodeFlowConfig: { preAuthorizedCode, - userPinRequired: false, }, issuanceMetadata: { myIssuance: 'metadata', @@ -471,15 +460,14 @@ 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, credentialRequestToCredentialMapper: ({ issuanceSession }) => { @@ -490,36 +478,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 +522,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 +537,31 @@ 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, 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('No offered credentials matching the credential request') }) it('pre authorized code flow using multiple credentials_supported', async () => { @@ -583,51 +572,52 @@ 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, }), 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 +628,37 @@ 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, 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('No offered credentials matching the credential request') }) it('create credential offer and retrieve it from the uri (pre authorized flow)', async () => { @@ -678,7 +669,6 @@ describe('OpenId4VcIssuer', () => { offeredCredentials: [openBadgeCredential.id], preAuthorizedCodeFlowConfig: { preAuthorizedCode, - userPinRequired: false, }, }) @@ -697,13 +687,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 +706,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 +725,51 @@ 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, }), 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, }), 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/authorization/clientAuth.ts b/packages/openid4vc/src/openid4vc-issuer/authorization/clientAuth.ts deleted file mode 100644 index 5065249af9..0000000000 --- a/packages/openid4vc/src/openid4vc-issuer/authorization/clientAuth.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { CredoError } from '@credo-ts/core' -import { importOauth4webapi, type oauth } from '../oauth4webapi' - -// These two are well-supported and easy to implement -export enum SupportedClientAuthenticationMethod { - ClientSecretBasic = 'client_secret_basic', - ClientSecretPost = 'client_secret_post', -} - -const supportedClientAuthenticationMethodValues = Object.values(SupportedClientAuthenticationMethod) - -export type ClientAuthEndpointType = 'introspection' | 'token' - -function getSupportedClientAuthMethod( - authorizationServer: oauth.AuthorizationServer, - endpointType: ClientAuthEndpointType -): SupportedClientAuthenticationMethod { - if (endpointType === 'introspection' && authorizationServer.introspection_endpoint_auth_methods_supported) { - const supportedMethod = authorizationServer.introspection_endpoint_auth_methods_supported.find( - (m): m is SupportedClientAuthenticationMethod => - Object.values(SupportedClientAuthenticationMethod).includes(m as SupportedClientAuthenticationMethod) - ) - - if (!supportedMethod) { - throw new CredoError( - `Authorization server metadata for issuer '${ - authorizationServer.issuer - }' has 'introspection_endpoint_auth_methods_supported' metadata, but does not contain a supported value. Supported values by Credo are '${supportedClientAuthenticationMethodValues.join( - ', ' - )}', found values are '${authorizationServer.introspection_endpoint_auth_methods_supported.join(', ')}'` - ) - } - - return supportedMethod - } - - // We allow the introspection endpoint to fallback on the token endpoint metadata if the introspection - // metadata is not defined - if (authorizationServer.token_endpoint_auth_methods_supported) { - const supportedMethod = authorizationServer.token_endpoint_auth_methods_supported.find( - (m): m is SupportedClientAuthenticationMethod => - Object.values(SupportedClientAuthenticationMethod).includes(m as SupportedClientAuthenticationMethod) - ) - - if (!supportedMethod) { - throw new CredoError( - `Authorization server metadata for issuer '${ - authorizationServer.issuer - }' has 'token_endpoint_auth_methods_supported' metadata, but does not contain a supported value. Supported values by Credo are '${supportedClientAuthenticationMethodValues.join( - ', ' - )}', found values are '${authorizationServer.token_endpoint_auth_methods_supported.join(', ')}'` - ) - } - - return supportedMethod - } - - // If omitted from metadata, the default is "client_secret_basic" according to rfc8414 - return SupportedClientAuthenticationMethod.ClientSecretBasic -} - -export async function getClientAuth( - authorizationServer: oauth.AuthorizationServer, - { clientSecret, endpointType }: { clientSecret: string; endpointType: ClientAuthEndpointType } -): Promise { - const oauth = await importOauth4webapi() - const method = getSupportedClientAuthMethod(authorizationServer, endpointType) - - if (method === SupportedClientAuthenticationMethod.ClientSecretBasic) { - return oauth.ClientSecretBasic(clientSecret) - } - - if (method === SupportedClientAuthenticationMethod.ClientSecretPost) { - return oauth.ClientSecretPost(clientSecret) - } - - throw new CredoError( - `Unsupported client auth method ${method}. Supported values are ${Object.values( - SupportedClientAuthenticationMethod - ).join(', ')}` - ) -} diff --git a/packages/openid4vc/src/openid4vc-issuer/authorization/discover.ts b/packages/openid4vc/src/openid4vc-issuer/authorization/discover.ts deleted file mode 100644 index b6b1e4fc70..0000000000 --- a/packages/openid4vc/src/openid4vc-issuer/authorization/discover.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { importOauth4webapi } from '../oauth4webapi' - -interface DiscoverAuthorizationRequestMetadataOptions { - serverType: 'oidc' | 'oauth2' -} - -export async function discoverAuthorizationRequestMetadata( - issuer: string, - { serverType }: DiscoverAuthorizationRequestMetadataOptions -) { - const oauth = await importOauth4webapi() - - const as = await oauth - .discoveryRequest(new URL(issuer), { algorithm: serverType }) - .then((response) => oauth.processDiscoveryResponse(new URL(issuer), response)) - - return as -} diff --git a/packages/openid4vc/src/openid4vc-issuer/authorization/dpop.ts b/packages/openid4vc/src/openid4vc-issuer/authorization/dpop.ts deleted file mode 100644 index bf429baa95..0000000000 --- a/packages/openid4vc/src/openid4vc-issuer/authorization/dpop.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { OpenId4VcIssuerRecord } from '../repository' -import type { OpenId4VcIssuanceRequest } from '../router' -import type { AgentContext } from '@credo-ts/core' -import type { SigningAlgo } from '@sphereon/oid4vc-common' - -import { joinUriParts } 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 verifyResourceRequestDpop( - agentContext: AgentContext, - issuer: OpenId4VcIssuerRecord, - request: OpenId4VcIssuanceRequest -) { - const openId4VcIssuerService = agentContext.dependencyManager.resolve(OpenId4VcIssuerService) - const issuerConfig = agentContext.dependencyManager.resolve(OpenId4VcIssuerModuleConfig) - const issuerMetadata = openId4VcIssuerService.getIssuerMetadata(agentContext, issuer) - - 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, - } - ) -} diff --git a/packages/openid4vc/src/openid4vc-issuer/authorization/externalAuthorization.ts b/packages/openid4vc/src/openid4vc-issuer/authorization/externalAuthorization.ts deleted file mode 100644 index 35dab1497a..0000000000 --- a/packages/openid4vc/src/openid4vc-issuer/authorization/externalAuthorization.ts +++ /dev/null @@ -1,131 +0,0 @@ -import type { OpenId4VciAuthorizationServerConfig } from '../../shared' -import type { OpenId4VcIssuerRecord } from '../repository' -import type { OpenId4VcIssuanceRequest } from '../router' -import type { AgentContext, JwtPayloadJson } from '@credo-ts/core' - -import { Jwt, CredoError } from '@credo-ts/core' - -import { OpenId4VcIssuerService } from '../OpenId4VcIssuerService' - -import { discoverAuthorizationRequestMetadata } from './discover' -import { introspectToken } from './introspect' -import { importOauth4webapi, type oauth } from '../oauth4webapi' - -export async function verifyExternalAccessToken( - agentContext: AgentContext, - issuer: OpenId4VcIssuerRecord, - request: OpenId4VcIssuanceRequest, - accessToken: Jwt | string -) { - const oauth = await importOauth4webapi() - const openId4VcIssuerService = agentContext.dependencyManager.resolve(OpenId4VcIssuerService) - const issuerMetadata = openId4VcIssuerService.getIssuerMetadata(agentContext, issuer) - - let authorizationServer: OpenId4VciAuthorizationServerConfig - let tokenPayload: JwtPayloadJson | oauth.IntrospectionResponse - - if (!issuer.authorizationServerConfigs || issuer.authorizationServerConfigs.length === 0) { - throw new CredoError(`No external authorization servers configured on issuer '${issuerMetadata.issuerUrl}'`) - } - - if (accessToken instanceof Jwt) { - if (!accessToken.payload.iss) { - throw new CredoError("Missing 'iss' parameter in JWT access token.") - } - - const _authorizationServer = issuer.authorizationServerConfigs?.find( - (config) => config.issuer === accessToken.payload.iss - ) - - if (!_authorizationServer) { - throw new CredoError( - `Authorization server '${accessToken.payload.iss}' is not configured for issuer ${issuerMetadata.issuerUrl}` - ) - } - - const authorizationServerMetadata = await discoverAuthorizationRequestMetadata(_authorizationServer.issuer, { - serverType: _authorizationServer.serverType, - }) - - // This fetches the jwks_uri and uses that to verify the jwt access token. - // TODO: support dpop nonce - await oauth.validateJwtAccessToken( - authorizationServerMetadata, - new Request(request.originalUrl, { - headers: request.headers as Record, - method: request.method, - body: request.body, - }), - authorizationServerMetadata.issuer - ) - - authorizationServer = _authorizationServer - tokenPayload = accessToken.payload.toJson() - } - // JWT is of type string - else { - let _authorizationServer: OpenId4VciAuthorizationServerConfig | undefined = undefined - let _tokenPayload: oauth.IntrospectionResponse | undefined = undefined - - for (const authorizationServerConfig of issuer.authorizationServerConfigs) { - try { - if (!authorizationServerConfig.clientId || !authorizationServerConfig.clientSecret) { - throw new CredoError( - `Missing required clientId and clientSecret for authorization server '${authorizationServerConfig.issuer}' in issuer ${issuer.issuerId}. clientId and clientSecret are required for token introspection when using opaque tokens from external authorization servers.` - ) - } - - // TODO: store server type in authorization server config - const authorizationServerMetadata = await discoverAuthorizationRequestMetadata( - authorizationServerConfig.issuer, - { - serverType: 'oauth2', - } - ) - - const introspectionResponse = await introspectToken({ - authorizationServer: authorizationServerMetadata, - clientId: authorizationServerConfig.clientId, - clientSecret: authorizationServerConfig.clientSecret, - token: accessToken, - }) - - if (!introspectionResponse.active) { - throw new CredoError('Access token is not active') - } - - // TODO: support dpop verification - if (introspectionResponse.token_type === 'DPoP') { - throw new CredoError( - 'Access token with introspection is using DPoP which is not supported for opaque access tokens. DPoP is only supported for JWT access tokens.' - ) - } - - _authorizationServer = authorizationServerConfig - _tokenPayload = introspectionResponse - break - } catch (error) { - continue - } - } - - if (!_authorizationServer || !_tokenPayload) { - throw new CredoError( - 'Unable to verify opaque access token using introspection endpoint at any of the configured authorizaiton servers' - ) - } - authorizationServer = _authorizationServer - tokenPayload = _tokenPayload - } - - // we have verified the token payload here. Now we want to do some additional checks - if (tokenPayload.sub !== issuerMetadata.issuerUrl) { - throw new CredoError(`Expected access token 'sub' to equal issuer '${issuerMetadata.issuerUrl}'`) - } - - if (!tokenPayload.issuer_state || typeof tokenPayload.issuer_state !== 'string') { - throw new CredoError(`Missing 'issuer_state' parameter in access token or introspection response`) - } - - return { authorizationServerConfig: authorizationServer, issuerState: tokenPayload.issuer_state } -} diff --git a/packages/openid4vc/src/openid4vc-issuer/authorization/internalAuthorization.ts b/packages/openid4vc/src/openid4vc-issuer/authorization/internalAuthorization.ts deleted file mode 100644 index b28760ca4f..0000000000 --- a/packages/openid4vc/src/openid4vc-issuer/authorization/internalAuthorization.ts +++ /dev/null @@ -1,70 +0,0 @@ -import type { OpenId4VcIssuerRecord } from '../repository' -import type { AgentContext, Jwt } from '@credo-ts/core' - -import { CredoError, joinUriParts, JwsService } from '@credo-ts/core' - -import { OpenId4VcIssuerService } from '../OpenId4VcIssuerService' -import { OpenId4VcIssuerModuleConfig } from '../OpenId4VcIssuerModuleConfig' -import { OpenId4VcIssuanceRequest } from '../router' -import { SigningAlgo, verifyResourceDPoP } from '@sphereon/oid4vc-common' -import { getVerifyJwtCallback } from '../../shared/utils' - -export async function verifyInternalAccessToken( - agentContext: AgentContext, - issuer: OpenId4VcIssuerRecord, - request: OpenId4VcIssuanceRequest, - accessToken: Jwt -) { - const openId4VcIssuerService = agentContext.dependencyManager.resolve(OpenId4VcIssuerService) - - const issuerMetadata = openId4VcIssuerService.getIssuerMetadata(agentContext, issuer) - 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') - }, - }) - - 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') - } - - // TODO: support dpop nonce - // Verify DPoP - 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, - } - ) - - const { preAuthorizedCode } = accessToken.payload.additionalClaims - - if (!preAuthorizedCode) { - throw new CredoError('No preAuthorizedCode present in access token') - } - - if (typeof preAuthorizedCode !== 'string') { - throw new CredoError('Invalid preAuthorizedCode present in access token') - } - - return { - preAuthorizedCode, - } -} diff --git a/packages/openid4vc/src/openid4vc-issuer/authorization/introspect.ts b/packages/openid4vc/src/openid4vc-issuer/authorization/introspect.ts deleted file mode 100644 index 8369cde5bb..0000000000 --- a/packages/openid4vc/src/openid4vc-issuer/authorization/introspect.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { importOauth4webapi, type oauth } from '../oauth4webapi' -import { getClientAuth } from './clientAuth' - -export interface IntrospectTokenOptions { - authorizationServer: oauth.AuthorizationServer - - clientId: string - clientSecret: string - - token: string -} - -export async function introspectToken({ authorizationServer, clientId, clientSecret, token }: IntrospectTokenOptions) { - const oauth = await importOauth4webapi() - const response = await oauth.introspectionRequest( - authorizationServer, - { client_id: clientId }, - await getClientAuth(authorizationServer, { clientSecret, endpointType: 'introspection' }), - token - ) - - const introspectionResponse = await oauth.processIntrospectionResponse( - authorizationServer, - { client_id: clientId }, - response - ) - - return introspectionResponse -} diff --git a/packages/openid4vc/src/openid4vc-issuer/authorization/verifyResourceRequest.ts b/packages/openid4vc/src/openid4vc-issuer/authorization/verifyResourceRequest.ts deleted file mode 100644 index 0132d8bba8..0000000000 --- a/packages/openid4vc/src/openid4vc-issuer/authorization/verifyResourceRequest.ts +++ /dev/null @@ -1,50 +0,0 @@ -import type { OpenId4VcIssuerRecord } from '../repository' -import type { OpenId4VcIssuanceRequest } from '../router/requestContext' -import type { AgentContext } from '@credo-ts/core' - -import { CredoError, Jwt } from '@credo-ts/core' - -import { OpenId4VcIssuerService } from '../OpenId4VcIssuerService' - -import { verifyInternalAccessToken } from './internalAuthorization' -import { verifyExternalAccessToken } from './externalAuthorization' - -export async function verifyResourceRequest( - agentContext: AgentContext, - issuer: OpenId4VcIssuerRecord, - request: OpenId4VcIssuanceRequest -) { - const openId4VcIssuerService = agentContext.dependencyManager.resolve(OpenId4VcIssuerService) - const issuerMetadata = openId4VcIssuerService.getIssuerMetadata(agentContext, issuer) - 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.`) - } - - // Try parse it as JWT first, otherwise we treat it as an opaque token - let accessToken: Jwt | string - try { - accessToken = Jwt.fromSerializedJwt(authorizationHeader.replace('Bearer ', '').replace('DPoP ', '')) - } catch (error) { - accessToken = authorizationHeader.replace('Bearer ', '').replace('DPoP ', '') - } - - // TODO: we can support DPoP with opaque access token by extracting the data from the introspection - // endpiont, but that will require changes to the DPoP implementation in Sphereon's lib. - if (typeof accessToken === 'string' && authorizationHeader.startsWith('DPoP ')) { - throw new CredoError( - 'DPoP is not supported for opaque access tokens. Either disable DPoP on the authorization server, or use a JWT access token' - ) - } - - if (accessToken instanceof Jwt && accessToken.payload.iss === issuerMetadata.issuerUrl) { - return await verifyInternalAccessToken(agentContext, issuer, request, accessToken) - } - - return await verifyExternalAccessToken(agentContext, issuer, request, accessToken) -} diff --git a/packages/openid4vc/src/openid4vc-issuer/oauth4webapi.native.ts b/packages/openid4vc/src/openid4vc-issuer/oauth4webapi.native.ts deleted file mode 100644 index 89dd3e084d..0000000000 --- a/packages/openid4vc/src/openid4vc-issuer/oauth4webapi.native.ts +++ /dev/null @@ -1,5 +0,0 @@ -export function importOauth4webapi() { - throw new Error( - "oauth4webapi cannot be imported in React Native. This is probably because you are trying to use the 'OpenId4VcIssuerModule' or the 'OpenId4VcVerifierModule'." - ) -} diff --git a/packages/openid4vc/src/openid4vc-issuer/oauth4webapi.ts b/packages/openid4vc/src/openid4vc-issuer/oauth4webapi.ts deleted file mode 100644 index 190354ae24..0000000000 --- a/packages/openid4vc/src/openid4vc-issuer/oauth4webapi.ts +++ /dev/null @@ -1,12 +0,0 @@ -export type * as oauth from 'oauth4webapi' - -export async function importOauth4webapi() { - try { - // NOTE: 'oauth4webapi' is required in when using OpenID4VC Issuer module. - // eslint-disable-next-line import/no-extraneous-dependencies, @typescript-eslint/no-var-requires - const oauth4webapi = await import('oauth4webapi') - return oauth4webapi.default - } catch (error) { - throw new Error(`Could not import oauth4webapi. ${error.message}`) - } -} 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 c0cadd0a9c..0000000000 --- a/packages/openid4vc/src/openid4vc-issuer/repository/OpenId4VcCNonceStateManager.ts +++ /dev/null @@ -1,130 +0,0 @@ -import type { OpenId4VcIssuanceCodeType } from './OpenId4VcCredentialOfferSessionStateManager' -import type { OpenId4VcIssuanceSessionRecord } from './OpenId4VcIssuanceSessionRecord' -import type { AgentContext, Query } 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, type?: OpenId4VcIssuanceCodeType): 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 && !stateValue.issuerState) { - throw new CredoError("Expected the stateValue to have a 'preAuthorizedCode' or 'issuerState' property") - } - - // Record MUST exist (otherwise there's no issuance session active yet) - - const $or: Query[] = [] - - if (!type || type === 'preAuthorized') $or.push({ preAuthorizedCode: stateValue.preAuthorizedCode }) - if (!type || type === 'issuerState') $or.push({ issuerState: stateValue.issuerState }) - - const record = await this.openId4VcIssuanceSessionRepository.getSingleByQuery(this.agentContext, { - issuerId: this.issuerId, - $or, - }) - - // 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 25db9b6ea6..0000000000 --- a/packages/openid4vc/src/openid4vc-issuer/repository/OpenId4VcCredentialOfferSessionStateManager.ts +++ /dev/null @@ -1,253 +0,0 @@ -import type { OpenId4VcIssuanceSessionStateChangedEvent } from '../OpenId4VcIssuerEvents' -import type { AgentContext, Query } 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 type OpenId4VcIssuanceCodeType = 'preAuthorized' | 'issuerState' - -const createCodeQuery = ( - issuerId: string, - code: string, - type?: OpenId4VcIssuanceCodeType -): Query => { - const $or: Query[] = [] - - if (!type || type === 'preAuthorized') $or.push({ preAuthorizedCode: code }) - if (!type || type === 'issuerState') $or.push({ issuerState: code }) - - return { issuerId, $or } -} - -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(code: string, stateValue: CredentialOfferSession, type?: OpenId4VcIssuanceCodeType): 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 ( - (type === 'preAuthorized' && code !== stateValue.preAuthorizedCode) || - (type === 'issuerState' && code !== stateValue.issuerState) - ) { - throw new CredoError(`Expected the id of the credential offer state to be equal to the '${type}'`) - } - - if (code !== stateValue.issuerState && code !== stateValue.preAuthorizedCode) { - throw new CredoError( - `Expected the id of the credential offer state to be equal to the 'preAuthorizedCode' or 'issuerState'` - ) - } - - if (!stateValue.issuerState && !stateValue.preAuthorizedCode) { - throw new CredoError("Expected the stateValue to have a 'preAuthorizedCode' or 'issuerState' property") - } - - // Record may already exist - let record = await this.openId4VcIssuanceSessionRepository.findSingleByQuery( - this.agentContext, - createCodeQuery(this.issuerId, code, type) - ) - - 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.issuerState = stateValue.issuerState - 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, - issuerState: stateValue.issuerState, - 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(code: string, type?: OpenId4VcIssuanceCodeType): Promise { - const record = await this.openId4VcIssuanceSessionRepository.findSingleByQuery( - this.agentContext, - createCodeQuery(this.issuerId, code, type) - ) - - if (!record) return undefined - - if (!record.preAuthorizedCode && !record.issuerState) { - throw new CredoError("No 'preAuthorizedCode' and 'issuerState' 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, - issuerState: record.issuerState, - credentialDataSupplierInput: record.issuanceMetadata, - error: record.errorMessage, - txCode: record.userPin, - createdAt: record.createdAt.getTime(), - lastUpdatedAt: record.updatedAt?.getTime() ?? record.createdAt.getTime(), - } - } - - public async has(code: string, type?: OpenId4VcIssuanceCodeType): Promise { - const record = await this.openId4VcIssuanceSessionRepository.findSingleByQuery( - this.agentContext, - createCodeQuery(this.issuerId, code, type) - ) - - return record !== undefined - } - - public async delete(code: string, type?: OpenId4VcIssuanceCodeType): Promise { - const record = await this.openId4VcIssuanceSessionRepository.findSingleByQuery( - this.agentContext, - createCodeQuery(this.issuerId, code, type) - ) - - 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(code: string, type?: OpenId4VcIssuanceCodeType): Promise { - const state = await this.get(code, type) - - if (!state) { - throw new CredoError(`No credential offer state found for id '${code}'`) - } - - 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 f2e76255b3..5d1082a703 100644 --- a/packages/openid4vc/src/openid4vc-issuer/repository/OpenId4VcIssuanceSessionRecord.ts +++ b/packages/openid4vc/src/openid4vc-issuer/repository/OpenId4VcIssuanceSessionRecord.ts @@ -1,7 +1,8 @@ import type { OpenId4VciCredentialOfferPayload } from '../../shared' import type { RecordTags, TagsBase } from '@credo-ts/core' -import { CredoError, BaseRecord, utils, DateTransformer } from '@credo-ts/core' +import { PkceCodeChallengeMethod } from '@animo-id/oauth2' +import { CredoError, BaseRecord, utils } from '@credo-ts/core' import { Transform } from 'class-transformer' import { OpenId4VcIssuanceSessionState } from '../OpenId4VcIssuanceSessionState' @@ -11,9 +12,15 @@ export type OpenId4VcIssuanceSessionRecordTags = RecordTags - state: OpenId4VcIssuanceSessionState errorMessage?: string } @@ -66,32 +103,49 @@ export class OpenId4VcIssuanceSessionRecord extends BaseRecord 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 @@ -45,9 +32,13 @@ export type OpenId4VcIssuerRecordProps = { */ dpopSigningAlgValuesSupported?: [JwaSignatureAlgorithm, ...JwaSignatureAlgorithm[]] - display?: OpenId4VciIssuerMetadataDisplay[] + // FIXME: migrate to v13 structure (uri vs url) + display?: OpenId4VciCredentialIssuerMetadataDisplay[] authorizationServerConfigs?: OpenId4VciAuthorizationServerConfig[] -} & (OpenId4VcIssuerRecordCredentialSupportedProps | OpenId4VcIssuerRecordCredentialConfigurationsSupportedProps) + + // TODO: with formats or without formats? + credentialConfigurationsSupported: OpenId4VciCredentialConfigurationsSupportedWithFormats +} /** * 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. @@ -61,9 +52,10 @@ export class OpenId4VcIssuerRecord extends BaseRecord + public credentialConfigurationsSupported?: OpenId4VciCredentialConfigurationsSupportedWithFormats + public display?: OpenId4VciCredentialIssuerMetadataDisplay[] public authorizationServerConfigs?: OpenId4VciAuthorizationServerConfig[] public dpopSigningAlgValuesSupported?: [JwaSignatureAlgorithm, ...JwaSignatureAlgorithm[]] @@ -77,8 +69,6 @@ export class OpenId4VcIssuerRecord extends BaseRecord { - 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}`) - } - - // 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 - } + router.post(config.endpointPath, handleTokenRequest(config)) } export function handleTokenRequest(config: OpenId4VciAccessTokenEndpointConfig) { - const { tokenExpiresInSeconds, cNonceExpiresInSeconds } = config - 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 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.endpointPath]) + const requestLike = { + headers: new Headers(request.headers as Record), + method: request.method as HttpMethod, + url: fullRequestUrl, + } as const + + // What error does this throw? + 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, - next, - agentContext.config.logger, - 400, - TokenErrorResponse.invalid_dpop_proof, - error instanceof Error ? error.message : 'Unknown error' - ) - } - } - - 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, - }) - - return sendJsonResponse(response, next, accessTokenResponse) - } catch (error) { - return sendErrorResponse( + const issuanceSession = await issuanceSessionRepository.findSingleByQuery(agentContext, { + preAuthorizedCode: grant.grantType === preAuthorizedCodeGrantIdentifier ? grant.preAuthorizedCode : undefined, + authorizationCode: grant.grantType === authorizationCodeGrantIdentifier ? grant.code : undefined, + }) + if (!issuanceSession) { + return sendOauth2ErrorResponse( response, next, agentContext.config.logger, - 400, - TokenErrorResponse.invalid_request, - error + new Oauth2ServerErrorResponseError({ + error: Oauth2ErrorCodes.InvalidGrant, + error_description: 'Invalid authorization code', + }) ) } - } -} - -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 preAuthorizedCode = request.body[PRE_AUTH_CODE_LITERAL] - if (!preAuthorizedCode || typeof preAuthorizedCode !== 'string') { - throw new TokenError( - 400, - TokenErrorResponse.invalid_request, - `Missing '${PRE_AUTH_CODE_LITERAL}' parameter in access token request body` - ) + 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, + required: issuanceSession.dpopRequired, + }, + expectedTxCode: issuanceSession.userPin, + preAuthorizedCodeExpiresAt: addSecondsToDate( + issuanceSession.createdAt, + config.preAuthorizedCodeExpirationInSeconds + ), + }) + } 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, + required: issuanceSession.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 issuanceSessionRepository = agentContext.dependencyManager.resolve(OpenId4VcIssuanceSessionRepository) - const openId4VcIssuanceSession = await issuanceSessionRepository.getSingleByQuery(agentContext, { - preAuthorizedCode, - issuerId: issuer.issuerId, - }) - if ( - ![OpenId4VcIssuanceSessionState.OfferCreated, OpenId4VcIssuanceSessionState.OfferUriRetrieved].includes( - openId4VcIssuanceSession.state - ) - ) { - throw new TokenError(400, TokenErrorResponse.invalid_request, 'Access token has already been retrieved') - } + await openId4VcIssuerService.updateState( + agentContext, + issuanceSession, + OpenId4VcIssuanceSessionState.AccessTokenRequested + ) + const { cNonce, cNonceExpiresInSeconds } = await openId4VcIssuerService.createNonce(agentContext, issuer) + + // Extract scopes + const scopes = extractScopesForCredentialConfigurationIds({ + credentialConfigurationIds: issuanceSession.credentialOfferPayload.credential_configuration_ids, + issuerMetadata, + }) - await assertValidAccessTokenRequest(request.body, { - expirationDuration: options.preAuthorizedCodeExpirationInSeconds, - credentialOfferSessions, + const signerJwk = getJwkFromKey(accessTokenSigningKey) + const accessTokenResponse = await oauth2AuthorizationServer.createAccessTokenResponse({ + audience: issuerMetadata.credentialIssuer.credential_issuer, + authorizationServer: issuerMetadata.credentialIssuer.credential_issuer, + expiresInSeconds: config.tokenExpiresInSeconds, + // TODO: we need to include kid and also host the jwks? + // Or we should somehow bypass the jwks_uri resolving if we verify our own token (only we will verify the token) + signer: { + method: 'jwk', + alg: signerJwk.supportedSignatureAlgorithms[0], + publicJwk: signerJwk.toJson(), + }, + dpopJwk: verificationResult.dpopJwk, + scope: scopes?.join(','), + clientId: issuanceSession.clientId, + subject: grant.grantType === preAuthorizedCodeGrantIdentifier ? grant.preAuthorizedCode : grant.code, + + // NOTE: these have been removed in newer drafts. Keeping them in for now + cNonce, + cNonceExpiresIn: cNonceExpiresInSeconds, }) - next() + await openId4VcIssuerService.updateState( + agentContext, + issuanceSession, + OpenId4VcIssuanceSessionState.AccessTokenCreated + ) + + return sendJsonResponse(response, next, accessTokenResponse) } catch (error) { - if (error instanceof TokenError) { - return sendErrorResponse( - response, - next, - agentContext.config.logger, - error.statusCode, - error.responseError, - error.getDescription() - ) - } else { - return sendErrorResponse( - response, - next, - agentContext.config.logger, - 400, - TokenErrorResponse.invalid_request, - error - ) + if (error instanceof Oauth2ServerErrorResponseError) { + return sendOauth2ErrorResponse(response, next, agentContext.config.logger, error) } + + return sendUnknownServerErrorResponse(response, next, agentContext.config.logger, error) } } } diff --git a/packages/openid4vc/src/openid4vc-issuer/router/authorizationServerMetadataEndpoint.ts b/packages/openid4vc/src/openid4vc-issuer/router/authorizationServerMetadataEndpoint.ts index 881e5036d4..b3de123291 100644 --- a/packages/openid4vc/src/openid4vc-issuer/router/authorizationServerMetadataEndpoint.ts +++ b/packages/openid4vc/src/openid4vc-issuer/router/authorizationServerMetadataEndpoint.ts @@ -1,8 +1,9 @@ import type { OpenId4VcIssuanceRequest } from './requestContext' -import type { AuthorizationServerMetadata } from '@animo-id/oid4vci' import type { Router, Response } from 'express' -import { getRequestContext, sendErrorResponse, sendJsonResponse } from '../../shared/router' +import { getAuthorizationServerMetadataFromList } from '@animo-id/oauth2' + +import { getRequestContext, sendJsonResponse, sendUnknownServerErrorResponse } from '../../shared/router' import { OpenId4VcIssuerService } from '../OpenId4VcIssuerService' /** @@ -12,22 +13,19 @@ import { OpenId4VcIssuerService } from '../OpenId4VcIssuerService' export function configureOAuthAuthorizationServerMetadataEndpoint(router: Router) { router.get( '/.well-known/oauth-authorization-server', - (_request: OpenId4VcIssuanceRequest, response: Response, next) => { + async (_request: OpenId4VcIssuanceRequest, response: Response, next) => { const { agentContext, issuer } = getRequestContext(_request) try { const openId4VcIssuerService = agentContext.dependencyManager.resolve(OpenId4VcIssuerService) - const issuerMetadata = openId4VcIssuerService.getIssuerMetadata(agentContext, issuer) - - const authorizationServerMetadata = { - issuer: issuerMetadata.issuerUrl, - token_endpoint: issuerMetadata.tokenEndpoint, - dpop_signing_alg_values_supported: issuerMetadata.dpopSigningAlgValuesSupported, - 'pre-authorized_grant_anonymous_access_supported': true, - } satisfies AuthorizationServerMetadata + const issuerMetadata = await openId4VcIssuerService.getIssuerMetadata(agentContext, issuer) + const issuerAuthorizationServer = getAuthorizationServerMetadataFromList( + issuerMetadata.authorizationServers, + issuerMetadata.credentialIssuer.credential_issuer + ) - return sendJsonResponse(response, next, authorizationServerMetadata) + return sendJsonResponse(response, next, issuerAuthorizationServer) } catch (e) { - return sendErrorResponse(response, next, agentContext.config.logger, 500, 'invalid_request', 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 95fdd4ca61..61f44a839d 100644 --- a/packages/openid4vc/src/openid4vc-issuer/router/credentialEndpoint.ts +++ b/packages/openid4vc/src/openid4vc-issuer/router/credentialEndpoint.ts @@ -1,16 +1,20 @@ import type { OpenId4VcIssuanceRequest } from './requestContext' -import type { OpenId4VciCredentialRequest } from '../../shared' import type { OpenId4VciCredentialRequestToCredentialMapper } from '../OpenId4VcIssuerServiceOptions' +import type { HttpMethod } from '@animo-id/oauth2' import type { Router, Response } from 'express' -import { getRequestContext, sendErrorResponse, sendJsonResponse } from '../../shared/router' -import { OpenId4VcIssuerService } from '../OpenId4VcIssuerService' -import { getCNonceFromCredentialRequest } from '../util/credentialRequest' +import { Oauth2ErrorCodes, Oauth2ServerErrorResponseError } from '@animo-id/oauth2' +import { joinUriParts } from '@credo-ts/core' -import { verifyResourceRequest } from '../authorization/verifyResourceRequest' -import { OpenId4VcIssuanceSessionRecord, OpenId4VcIssuanceSessionRepository } from '../repository' -import { OpenId4VcIssuerModuleConfig } from '../OpenId4VcIssuerModuleConfig' -import { utils } from '@credo-ts/core' +import { + getRequestContext, + sendJsonResponse, + sendOauth2ErrorResponse, + sendUnauthorizedError, + sendUnknownServerErrorResponse, +} from '../../shared/router' +import { OpenId4VcIssuerService } from '../OpenId4VcIssuerService' +import { OpenId4VcIssuanceSessionRepository } from '../repository' export interface OpenId4VciCredentialEndpointConfig { /** @@ -31,64 +35,64 @@ export function configureCredentialEndpoint(router: Router, config: OpenId4VciCr router.post(config.endpointPath, async (request: OpenId4VcIssuanceRequest, response: Response, next) => { const { agentContext, issuer } = getRequestContext(request) const openId4VcIssuerService = agentContext.dependencyManager.resolve(OpenId4VcIssuerService) - const issuanceModuleConfig = agentContext.dependencyManager.resolve(OpenId4VcIssuerModuleConfig) + // TODO: we should allow delaying fetching auth metadat until it's needed + // also we should cache it. (both request and response) + const issuerMetadata = await openId4VcIssuerService.getIssuerMetadata(agentContext, issuer, true) + const resourceServer = openId4VcIssuerService.getResourceServer(agentContext) - // Verify the access token (should at some point be moved to a middleware function or something) - const verifyAccessTokenResult = await verifyResourceRequest(agentContext, issuer, request).catch((error) => { - sendErrorResponse(response, next, agentContext.config.logger, 401, 'unauthorized', error) - }) - if (!verifyAccessTokenResult) return + const fullRequestUrl = joinUriParts(issuerMetadata.credentialIssuer.credential_issuer, [config.endpointPath]) + const resourceRequestResult = await resourceServer + .verifyResourceRequest({ + authorizationServers: issuerMetadata.authorizationServers, + resourceServer: issuerMetadata.credentialIssuer.credential_issuer, + request: { + // FIXME: we need to make the input type here easier + 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 } = resourceRequestResult - try { - const credentialRequest = request.body - const issuanceSessionRepository = agentContext.dependencyManager.resolve(OpenId4VcIssuanceSessionRepository) + const credentialRequest = request.body + const issuanceSessionRepository = agentContext.dependencyManager.resolve(OpenId4VcIssuanceSessionRepository) - let issuanceSession: OpenId4VcIssuanceSessionRecord | null - if ('preAuthorizedCode' in verifyAccessTokenResult) { - issuanceSession = await issuanceSessionRepository.findSingleByQuery(agentContext, { - issuerId: issuer.issuerId, - preAuthorizedCode: verifyAccessTokenResult.preAuthorizedCode, - }) - } else { - issuanceSession = await issuanceSessionRepository.findSingleByQuery(agentContext, { - issuerId: issuer.issuerId, - issuerState: verifyAccessTokenResult.issuerState, - }) - } + // TODO: we should support dynamic issuance sessions (based on config) + const issuanceSession = await issuanceSessionRepository.findSingleByQuery(agentContext, { + issuerId: issuer.issuerId, + // TODO: maybe make pre-auth also custom prop to be more explicit + ...(typeof tokenPayload.issuer_state === 'string' + ? { issuerState: tokenPayload.issuer_state } + : { preAuthorizedCode: tokenPayload.sub }), + }) - if (!issuanceSession) { - agentContext.config.logger.warn( - `No issuance session found for incoming credential request for issuer ${issuer.issuerId} and access token data`, + if (!issuanceSession) { + agentContext.config.logger.warn( + `No issuance session found for incoming credential request for issuer ${issuer.issuerId} and access token data`, + { + tokenPayload, + } + ) + return sendOauth2ErrorResponse( + response, + next, + agentContext.config.logger, + new Oauth2ServerErrorResponseError( { - verifyAccessTokenResult, + error: Oauth2ErrorCodes.CredentialRequestDenied, + }, + { + internalMessage: `No issuance session found for incoming credential request for issuer ${issuer.issuerId} and access token data`, } ) - return sendErrorResponse(response, next, agentContext.config.logger, 404, 'invalid_request', null) - } - - try { - const cNonce = getCNonceFromCredentialRequest(credentialRequest) - - if (!issuanceSession.cNonce || cNonce !== issuanceSession.cNonce) { - throw new Error('Invalid c_nonce') - } - } catch (error) { - // If no c_nonce could be extracted we generate a new one and send that in the error response - const expiresAtDate = new Date( - Date.now() + issuanceModuleConfig.accessTokenEndpoint.cNonceExpiresInSeconds * 1000 - ) - - issuanceSession.cNonce = utils.uuid() - issuanceSession.cNonceExpiresAt = expiresAtDate - issuanceSessionRepository.update(agentContext, issuanceSession) - - return sendErrorResponse(response, next, agentContext.config.logger, 404, 'invalid_proof', null, { - c_nonce: issuanceSession.cNonce, - c_nonce_expires_in: issuanceModuleConfig.accessTokenEndpoint.cNonceExpiresInSeconds, - }) - } + ) + } - // TODO: invalidate nonce if this method fails + try { const { credentialResponse } = await openId4VcIssuerService.createCredentialResponse(agentContext, { issuanceSession, credentialRequest, @@ -96,7 +100,10 @@ export function configureCredentialEndpoint(router: Router, config: OpenId4VciCr return sendJsonResponse(response, next, credentialResponse) } catch (error) { - return sendErrorResponse(response, next, agentContext.config.logger, 500, 'invalid_request', error) + if (error instanceof Oauth2ServerErrorResponseError) { + return sendOauth2ErrorResponse(response, next, agentContext.config.logger, error) + } + 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 6442171e14..08aca09d3c 100644 --- a/packages/openid4vc/src/openid4vc-issuer/router/credentialOfferEndpoint.ts +++ b/packages/openid4vc/src/openid4vc-issuer/router/credentialOfferEndpoint.ts @@ -1,12 +1,16 @@ import type { OpenId4VcIssuanceRequest } from './requestContext' -import type { OpenId4VcIssuanceSessionStateChangedEvent } from '../OpenId4VcIssuerEvents' import type { Router, Response } from 'express' -import { joinUriParts, EventEmitter } from '@credo-ts/core' +import { joinUriParts } from '@credo-ts/core' -import { getRequestContext, sendErrorResponse, sendJsonResponse } 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' @@ -40,13 +44,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, [ + const fullCredentialOfferUri = joinUriParts(issuerMetadata.credentialIssuer.credential_issuer, [ issuerConfig.credentialOfferEndpoint.endpointPath, request.params.credentialOfferId, ]) @@ -55,54 +59,29 @@ export function configureCredentialOfferEndpoint(router: Router, config: OpenId4 issuerId: issuer.issuerId, credentialOfferUri: fullCredentialOfferUri, }) - - if (!openId4VcIssuanceSession || !openId4VcIssuanceSession.credentialOfferPayload) { - return sendErrorResponse( - response, - next, - 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, - next, - 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 + ) } return sendJsonResponse(response, next, openId4VcIssuanceSession.credentialOfferPayload) } catch (error) { - return sendErrorResponse(response, next, agentContext.config.logger, 500, 'invalid_request', error) + return sendUnknownServerErrorResponse(response, next, agentContext.config.logger, error) } } ) diff --git a/packages/openid4vc/src/openid4vc-issuer/router/index.ts b/packages/openid4vc/src/openid4vc-issuer/router/index.ts index 5dab29f903..4b4ce862e7 100644 --- a/packages/openid4vc/src/openid4vc-issuer/router/index.ts +++ b/packages/openid4vc/src/openid4vc-issuer/router/index.ts @@ -4,3 +4,5 @@ export { configureIssuerMetadataEndpoint } from './issuerMetadataEndpoint' export { configureOAuthAuthorizationServerMetadataEndpoint } from './authorizationServerMetadataEndpoint' export { configureCredentialOfferEndpoint, OpenId4VciCredentialOfferEndpointConfig } from './credentialOfferEndpoint' export { OpenId4VcIssuanceRequest } from './requestContext' +export { configureJwksEndpoint } from './jwksEndpoint' +export { OpenId4VciNonceEndpointConfig, configureNonceEndpoint } from './nonceEndpoint' diff --git a/packages/openid4vc/src/openid4vc-issuer/router/issuerMetadataEndpoint.ts b/packages/openid4vc/src/openid4vc-issuer/router/issuerMetadataEndpoint.ts index 2d0cc37a89..5f6019ab55 100644 --- a/packages/openid4vc/src/openid4vc-issuer/router/issuerMetadataEndpoint.ts +++ b/packages/openid4vc/src/openid4vc-issuer/router/issuerMetadataEndpoint.ts @@ -1,41 +1,39 @@ import type { OpenId4VcIssuanceRequest } from './requestContext' -import type { OpenId4VciIssuerMetadata } from '../../shared' +import type { OpenId4VciCredentialIssuerMetadata } from '../../shared' import type { Router, Response } from 'express' -import { getRequestContext, sendErrorResponse, sendJsonResponse } from '../../shared/router' +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', - (_request: OpenId4VcIssuanceRequest, response: Response, next) => { + async (_request: OpenId4VcIssuanceRequest, response: Response, next) => { const { agentContext, issuer } = getRequestContext(_request) try { const openId4VcIssuerService = agentContext.dependencyManager.resolve(OpenId4VcIssuerService) - const issuerMetadata = openId4VcIssuerService.getIssuerMetadata(agentContext, issuer) + const issuerMetadata = await openId4VcIssuerService.getIssuerMetadata(agentContext, issuer) + const vcIssuer = openId4VcIssuerService.getIssuer(agentContext, issuer) + const issuerAuthorizationServer = getAuthorizationServerMetadataFromList( + issuerMetadata.authorizationServers, + issuerMetadata.credentialIssuer.credential_issuer + ) const transformedMetadata = { - credential_issuer: issuerMetadata.issuerUrl, - credential_endpoint: issuerMetadata.credentialEndpoint, - display: issuerMetadata.issuerDisplay, - - // OID4VCI draft 11 (only one auth server is supported) - authorization_server: issuerMetadata.authorizationServers?.[0], - credentials_supported: issuerMetadata.credentialsSupported, - - // OID4VCI draft 13 - authorization_servers: issuerMetadata.authorizationServers, - credential_configurations_supported: issuerMetadata.credentialConfigurationsSupported, + // 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: issuerMetadata.tokenEndpoint, - dpop_signing_alg_values_supported: issuerMetadata.dpopSigningAlgValuesSupported, - } satisfies OpenId4VciIssuerMetadata + 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 sendErrorResponse(response, next, agentContext.config.logger, 500, 'invalid_request', 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..3e1c200e54 --- /dev/null +++ b/packages/openid4vc/src/openid4vc-issuer/router/jwksEndpoint.ts @@ -0,0 +1,22 @@ +import type { OpenId4VcIssuanceRequest } from './requestContext' +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) { + router.get('/jwks.json', 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/nonceEndpoint.ts b/packages/openid4vc/src/openid4vc-issuer/router/nonceEndpoint.ts new file mode 100644 index 0000000000..584628a591 --- /dev/null +++ b/packages/openid4vc/src/openid4vc-issuer/router/nonceEndpoint.ts @@ -0,0 +1,50 @@ +import type { OpenId4VcIssuanceRequest } from './requestContext' +import type { NextFunction, Response, Router } from 'express' + +import { getRequestContext, sendJsonResponse, sendUnknownServerErrorResponse } from '../../shared/router' +import { OpenId4VcIssuerService } from '../OpenId4VcIssuerService' + +export interface OpenId4VciNonceEndpointConfig { + /** + * The path at which the nonce endpoint should be made available. Note that it will be + * hosted at a subpath to take into account multiple tenants and issuers. + * + * @default /nonce + */ + endpointPath: string + + /** + * The time after which the cNonce from the nonce response will + * expire. + * + * @default 60 (1 minute) + */ + cNonceExpiresInSeconds: number +} + +export function configureNonceEndpoint(router: Router, config: OpenId4VciNonceEndpointConfig) { + router.post( + config.endpointPath, + 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, issuer) + + 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/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..89db5ad076 100644 --- a/packages/openid4vc/src/openid4vc-verifier/OpenId4VcSiopVerifierService.ts +++ b/packages/openid4vc/src/openid4vc-verifier/OpenId4VcSiopVerifierService.ts @@ -148,7 +148,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, @@ -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, @@ -444,6 +445,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 +467,7 @@ export class OpenId4VcSiopVerifierService { private async getRelyingParty( agentContext: AgentContext, - verifierId: string, + verifier: OpenId4VcVerifierRecord, { idToken, presentationDefinition, @@ -510,7 +512,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 +552,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,6 +561,7 @@ 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 diff --git a/packages/openid4vc/src/openid4vc-verifier/OpenId4VcSiopVerifierServiceOptions.ts b/packages/openid4vc/src/openid4vc-verifier/OpenId4VcSiopVerifierServiceOptions.ts index a29ccced72..a08fa49cab 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, @@ -75,9 +75,24 @@ export interface OpenId4VcSiopVerifiedAuthorizationResponse { } } +/** + * Verifier metadata that will be send when creating a request + */ +export interface OpenId4VcSiopVerifierClientMetadata { + client_name?: string + logo_uri?: string +} + 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/repository/OpenId4VcVerifierRecord.ts b/packages/openid4vc/src/openid4vc-verifier/repository/OpenId4VcVerifierRecord.ts index a5c90f486c..92b8ecc54f 100644 --- a/packages/openid4vc/src/openid4vc-verifier/repository/OpenId4VcVerifierRecord.ts +++ b/packages/openid4vc/src/openid4vc-verifier/repository/OpenId4VcVerifierRecord.ts @@ -1,3 +1,4 @@ +import type { OpenId4VcSiopVerifierClientMetadata } from '../OpenId4VcSiopVerifierServiceOptions' import type { RecordTags, TagsBase } from '@credo-ts/core' import { BaseRecord, utils } from '@credo-ts/core' @@ -14,6 +15,8 @@ export interface OpenId4VcVerifierRecordProps { tags?: TagsBase verifierId: string + + clientMetadata?: OpenId4VcSiopVerifierClientMetadata } /** @@ -26,6 +29,7 @@ export class OpenId4VcVerifierRecord 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..5c0594f2b9 --- /dev/null +++ b/packages/openid4vc/src/shared/callbacks.ts @@ -0,0 +1,107 @@ +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 + ) + + // 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}'` + ) + if (!authorizationServer) 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 69769d856a..bf135e0742 100644 --- a/packages/openid4vc/src/shared/issuerMetadataUtils.ts +++ b/packages/openid4vc/src/shared/issuerMetadataUtils.ts @@ -1,6 +1,4 @@ - - -import { OpenId4VciCredentialConfigurationsSupported } from './models' +import type { OpenId4VciCredentialConfigurationsSupported } from './models' /** * Returns all entries from the credential offer with the associated metadata resolved. @@ -10,7 +8,6 @@ export function getOfferedCredentials( credentialConfigurationsSupported: OpenId4VciCredentialConfigurationsSupported ): OpenId4VciCredentialConfigurationsSupported { const offeredCredentialConfigurations: OpenId4VciCredentialConfigurationsSupported = {} - for (const offeredCredentialConfigurationId of offeredCredentialConfigurationIds) { const foundCredentialConfiguration = credentialConfigurationsSupported[offeredCredentialConfigurationId] diff --git a/packages/openid4vc/src/shared/models/AuthorizationServer.ts b/packages/openid4vc/src/shared/models/AuthorizationServer.ts deleted file mode 100644 index 69c8e90171..0000000000 --- a/packages/openid4vc/src/shared/models/AuthorizationServer.ts +++ /dev/null @@ -1,10 +0,0 @@ -export interface OpenId4VciAuthorizationServerConfig { - // The base Url of your OAuth Server - issuer: string - - serverType: 'oidc' | 'oauth2' - - // client id and client secret are needed when introspection is performed - clientId?: string - clientSecret?: string -} 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 5f98079cb8..c99cbeab96 100644 --- a/packages/openid4vc/src/shared/models/index.ts +++ b/packages/openid4vc/src/shared/models/index.ts @@ -1,9 +1,15 @@ -import { +import type { CredentialConfigurationSupported, CredentialConfigurationSupportedWithFormats, CredentialIssuerMetadata, + CredentialIssuerMetadataDisplayEntry, + CredentialOfferPreAuthorizedCodeGrantTxCode, CredentialRequest, + CredentialRequestFormatSpecific, CredentialRequestWithFormats, + IssuerMetadataResult, + ParseCredentialRequestReturn, + CredentialOfferObject, } from '@animo-id/oid4vci' import type { VerifiedAuthorizationRequest, @@ -11,8 +17,8 @@ import type { AuthorizationResponsePayload, IDTokenPayload, } from '@sphereon/did-auth-siop' -import { CredentialOfferObject } from '@animo-id/oid4vci' -import { PreAuthorizedCodeGrantIdentifier } from '@animo-id/oauth2' + +export { preAuthorizedCodeGrantIdentifier, authorizationCodeGrantIdentifier } from '@animo-id/oauth2' export type OpenId4VciCredentialConfigurationSupportedWithFormats = CredentialConfigurationSupportedWithFormats export type OpenId4VciCredentialConfigurationSupported = CredentialConfigurationSupported @@ -20,18 +26,18 @@ export type OpenId4VciCredentialConfigurationSupported = CredentialConfiguration export type OpenId4VciCredentialConfigurationsSupported = Record export type OpenId4VciCredentialConfigurationsSupportedWithFormats = Record< string, - OpenId4VciCredentialConfigurationSupported + OpenId4VciCredentialConfigurationSupportedWithFormats > -// TODO: export in @animo-id/oid4vc -export type OpenId4VciTxCode = NonNullable< - NonNullable[PreAuthorizedCodeGrantIdentifier]>['tx_code'] -> +export type OpenId4VciMetadata = IssuerMetadataResult + +export type OpenId4VciTxCode = CredentialOfferPreAuthorizedCodeGrantTxCode +export type OpenId4VciCredentialIssuerMetadata = CredentialIssuerMetadata -export type OpenId4VciIssuerMetadata = CredentialIssuerMetadata +export type OpenId4VciParsedCredentialRequest = ParseCredentialRequestReturn +export type OpenId4VciCredentialRequestFormatSpecific = CredentialRequestFormatSpecific -// TODO: export in @animo-id/oid4vc -export type OpenId4VciIssuerMetadataDisplay = NonNullable[number] +export type OpenId4VciCredentialIssuerMetadataDisplay = CredentialIssuerMetadataDisplayEntry export type OpenId4VciCredentialRequest = CredentialRequest export type OpenId4VciCredentialRequestWithFormats = CredentialRequestWithFormats @@ -46,4 +52,4 @@ export type OpenId4VcSiopIdTokenPayload = IDTokenPayload export * from './OpenId4VcJwtIssuer' export * from './CredentialHolderBinding' export * from './OpenId4VciCredentialFormatProfile' -export * from './AuthorizationServer' +export * from './OpenId4VciAuthorizationServerConfig' diff --git a/packages/openid4vc/src/shared/router/context.ts b/packages/openid4vc/src/shared/router/context.ts index 521f2a2585..bab696fe9d 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, NextFunction } from 'express' +import { Oauth2ResourceUnauthorizedError, SupportedAuthenticationScheme } from '@animo-id/oauth2' import { CredoError } from '@credo-ts/core' export interface OpenId4VcRequest = Record> extends Request { @@ -11,32 +13,87 @@ export interface OpenId4VcRequestContext { agentContext: AgentContext } +export function sendUnauthorizedError( + response: Response, + next: NextFunction, + logger: Logger, + error: unknown | Oauth2ResourceUnauthorizedError +) { + 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(401).send('Unauthorized') + 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, + }) + + 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, + }) + + 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, - code: number, - message: string, + status: number, + message: Oauth2ErrorCodes | string, error: unknown, additionalPayload?: Record ) { - const error_description = - error instanceof Error ? error.message : typeof error === 'string' ? error : 'An unknown error occurred.' - - const body = { error: message, error_description, ...additionalPayload } + const body = { error: message, ...additionalPayload } logger.warn(`[OID4VC] Sending error response: ${JSON.stringify(body)}`, { error, }) - response.status(code).json(body) + 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, body: any) { - response.status(200).json(body) +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function sendJsonResponse(response: Response, next: NextFunction, body: any, contentType?: string) { + response.setHeader('Content-Type', contentType ?? 'application/json').send(JSON.stringify(body)) next() } diff --git a/packages/openid4vc/src/shared/utils.ts b/packages/openid4vc/src/shared/utils.ts index cb1abcb8d5..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,7 +139,10 @@ 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.') } @@ -187,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.e2e.test.ts b/packages/openid4vc/tests/openid4vc.e2e.test.ts index 26a9a2caf4..6fe97ab3da 100644 --- a/packages/openid4vc/tests/openid4vc.e2e.test.ts +++ b/packages/openid4vc/tests/openid4vc.e2e.test.ts @@ -1,10 +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 { createHttpsProxy, disableSslVerification } from '../../../tests/https-proxy' +import { + calculateJwkThumbprint, + clientAuthenticationNone, + HashAlgorithm, + Oauth2AuthorizationServer, + preAuthorizedCodeGrantIdentifier, +} from '@animo-id/oauth2' +import { AuthorizationFlow } from '@animo-id/oid4vci' import { CredoError, ClaimFormat, @@ -25,6 +33,9 @@ import { X509ModuleConfig, parseDid, X509Service, + Hasher, + JwsService, + JwtPayload, } from '@credo-ts/core' import express, { type Express } from 'express' @@ -35,10 +46,10 @@ import { OpenId4VcHolderModule, OpenId4VcIssuanceSessionState, OpenId4VcIssuerModule, - OpenId4VcVerificationSessionRepository, OpenId4VcVerificationSessionState, OpenId4VcVerifierModule, } from '../src' +import { getOid4vciCallbacks } from '../src/shared/callbacks' import { waitForVerificationSessionRecordSubject, @@ -49,22 +60,18 @@ import { import { universityDegreeCredentialConfigurationSupported, universityDegreeCredentialConfigurationSupportedMdoc, - universityDegreeCredentialSdJwt, universityDegreeCredentialSdJwt2, } from './utilsVci' import { openBadgePresentationDefinition, universityDegreePresentationDefinition } from './utilsVp' -const proxyPort = 3443 const serverPort = 1234 -const baseUrl = `https://localhost:${proxyPort}` +const baseUrl = `http://localhost:${serverPort}` const issuanceBaseUrl = `${baseUrl}/oid4vci` const verificationBaseUrl = `${baseUrl}/oid4vp` describe('OpenId4Vc', () => { let expressApp: Express let expressServer: Server - let proxyServer: Server - let resetSslVerification: () => void let issuer: AgentType<{ openId4VcIssuer: OpenId4VcIssuerModule @@ -87,14 +94,6 @@ describe('OpenId4Vc', () => { let verifier1: TenantType let verifier2: TenantType - beforeAll(() => { - resetSslVerification = disableSslVerification() - }) - - afterAll(() => { - resetSslVerification() - }) - beforeEach(async () => { expressApp = express() @@ -106,7 +105,12 @@ describe('OpenId4Vc', () => { baseUrl: issuanceBaseUrl, endpoints: { credential: { - credentialRequestToCredentialMapper: async ({ agentContext, credentialRequest, holderBinding }) => { + 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' }) @@ -115,21 +119,21 @@ describe('OpenId4Vc', () => { if (!verificationMethod) { throw new Error('No verification method found') } + const credentialConfigurationId = credentialConfigurationIds[0] if (credentialRequest.format === 'vc+sd-jwt') { return { - credentialSupportedId: - credentialRequest.vct === 'UniversityDegreeCredential' - ? universityDegreeCredentialSdJwt.id - : universityDegreeCredentialSdJwt2.id, + credentialConfigurationId, format: credentialRequest.format, - payload: { vct: credentialRequest.vct, university: 'innsbruck', degree: 'bachelor' }, - holder: holderBinding, - issuer: { - method: 'did', - didUrl: verificationMethod.id, - }, - disclosureFrame: { _sd: ['university', 'degree'] }, + 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 = @@ -139,17 +143,19 @@ describe('OpenId4Vc', () => { } return { - credentialSupportedId: '', + credentialConfigurationId, format: ClaimFormat.MsoMdoc, - docType: universityDegreeCredentialConfigurationSupportedMdoc.doctype, - issuerCertificate: trustedCertificates[0], - holderKey: holderBinding.key, - namespaces: { - 'Leopold-Franzens-University': { - degree: 'bachelor', + credentials: holderBindings.map((holderBinding) => ({ + docType: universityDegreeCredentialConfigurationSupportedMdoc.doctype, + issuerCertificate: trustedCertificates[0], + holderKey: holderBinding.key, + namespaces: { + 'Leopold-Franzens-University': { + degree: 'bachelor', + }, }, - }, - } satisfies OpenId4VciSignMdocCredential + })), + } satisfies OpenId4VciSignMdocCredentials } else { throw new Error('Invalid request') } @@ -161,7 +167,7 @@ describe('OpenId4Vc', () => { tenants: new TenantsModule(), }, '96213c3d7fc8d4d6754c7a0fd969598g' - )) as unknown as typeof issuer + )) as unknown as typeof issuer issuer1 = await createTenantForAgent(issuer.agent, 'iTenant1') issuer2 = await createTenantForAgent(issuer.agent, 'iTenant2') @@ -196,15 +202,10 @@ describe('OpenId4Vc', () => { expressApp.use('/oid4vp', verifier.agent.modules.openId4VcVerifier.config.router) expressServer = expressApp.listen(serverPort) - proxyServer = createHttpsProxy({ - port: proxyPort, - target: `http://localhost:${serverPort}`, - }) }) afterEach(async () => { expressServer?.close() - proxyServer?.close() await issuer.agent.shutdown() await issuer.agent.wallet.delete() @@ -246,23 +247,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'], proof_types_supported: { jwt: { - proof_signing_alg_values_supported: ['EdDSA'], + proof_signing_alg_values_supported: ['EdDSA', 'ES256'], }, }, vct: universityDegreeCredentialConfigurationSupported.vct, @@ -271,14 +262,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', }) @@ -286,7 +284,9 @@ describe('OpenId4Vc', () => { await issuerTenant2.modules.openId4VcIssuer.createCredentialOffer({ issuerId: openIdIssuerTenant2.issuerId, offeredCredentials: [universityDegreeCredentialSdJwt2.id], - preAuthorizedCodeFlowConfig: {}, // { userPinRequired: true }, + preAuthorizedCodeFlowConfig: { + txCode: {}, + }, version: 'v1.draft11-13', }) @@ -317,7 +317,7 @@ describe('OpenId4Vc', () => { cryptographic_binding_methods_supported: ['did:key'], proof_types_supported: { jwt: { - proof_signing_alg_values_supported: ['EdDSA'], + proof_signing_alg_values_supported: ['EdDSA', 'ES256'], }, }, vct: universityDegreeCredentialConfigurationSupported.vct, @@ -338,12 +338,31 @@ describe('OpenId4Vc', () => { // Bind to JWK const tokenResponseTenant1 = await holderTenant1.modules.openId4VcHolder.requestToken({ resolvedCredentialOffer: resolvedCredentialOffer1, + txCode: issuanceSession1.userPin, }) 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, + }), + }, + scope: 'UniversityDegreeCredential', + 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: resolvedCredentialOffer1.credentialOfferPayload.grants?.[preAuthorizedCodeGrantIdentifier]?.[ + 'pre-authorized_code' + ], + }) const credentialsTenant1 = await holderTenant1.modules.openId4VcHolder.requestCredentials({ resolvedCredentialOffer: resolvedCredentialOffer1, @@ -373,8 +392,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') @@ -432,29 +451,90 @@ 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.skip('e2e flow with tenants, issuer endpoints requesting a sd-jwt-vc using authorization code flow', async () => { + 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', + 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({ - dpopSigningAlgValuesSupported: [JwaSignatureAlgorithm.EdDSA], + issuerId: '8bc91672-6a32-466c-96ec-6efca8760068', credentialConfigurationsSupported: { universityDegree: universityDegreeCredentialConfigurationSupported, }, authorizationServerConfigs: [ { - issuer: 'https://localhost:3042', - serverType: 'oidc', - clientId: 'issuer-server', - clientSecret: 'issuer-server', + issuer: 'http://localhost:4747', }, ], }) @@ -463,7 +543,8 @@ describe('OpenId4Vc', () => { issuerId: openIdIssuerTenant.issuerId, offeredCredentials: ['universityDegree'], authorizationCodeFlowConfig: { - authorizationServerUrl: 'https://localhost:3042', + authorizationServerUrl: 'http://localhost:4747', + issuerState: 'dbf99eea-0131-48b0-9022-17f7ebe25ea7', }, version: 'v1.draft13', }) @@ -471,40 +552,29 @@ describe('OpenId4Vc', () => { await issuerTenant.endSession() const resolvedCredentialOffer = await holderTenant.modules.openId4VcHolder.resolveCredentialOffer(credentialOffer) - - console.log(JSON.stringify(resolvedCredentialOffer, null, 2)) - - let code = new Promise((resolve) => { - expressApp.get('/redirect', (req, res) => { - console.log('incoming request', req.query) - if (req.query.code) { - resolve(req.query.code as string) - } - res.send('') - }) - }) - - const auth = await holderTenant.modules.openId4VcHolder.resolveIssuanceAuthorizationRequest( + const resolvedAuthorization = await holderTenant.modules.openId4VcHolder.resolveIssuanceAuthorizationRequest( resolvedCredentialOffer, { clientId: 'foo', - redirectUri: 'https://localhost:3443/redirect', + redirectUri: 'http://localhost:1234/redirect', scope: ['UniversityDegreeCredential'], } ) - - console.log(JSON.stringify(auth)) + if (resolvedAuthorization.authorizationFlow === AuthorizationFlow.PresentationDuringIssuance) { + throw new Error('Not supported') + } // Bind to JWK const tokenResponseTenant = await holderTenant.modules.openId4VcHolder.requestToken({ resolvedCredentialOffer, - code: await code, - resolvedAuthorizationRequest: auth, + // Mock the authorization code flow part, + code: 'some-authorization-code', + clientId: 'foo', + redirectUri: 'http://localhost:1234/redirect', + codeVerifier: resolvedAuthorization.codeVerifier, }) - console.log(JSON.stringify(tokenResponseTenant)) - - const credentials = await holderTenant.modules.openId4VcHolder.requestCredentials({ + const credentialResponse = await holderTenant.modules.openId4VcHolder.requestCredentials({ resolvedCredentialOffer, ...tokenResponseTenant, credentialBindingResolver, @@ -516,12 +586,13 @@ describe('OpenId4Vc', () => { contextCorrelationId: issuer1.tenantId, }) - expect(credentials).toHaveLength(1) - const compactSdJwtVcTenant1 = (credentials[0].credential as SdJwtVc).compact + 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 () => { @@ -858,7 +929,7 @@ describe('OpenId4Vc', () => { const certificate = await verifier.agent.x509.createSelfSignedCertificate({ key: await verifier.agent.wallet.createKey({ keyType: KeyType.Ed25519 }), - extensions: [[{ type: 'dns', value: `localhost:${proxyPort}` }]], + extensions: [[{ type: 'dns', value: `localhost:${serverPort}` }]], }) const rawCertificate = certificate.toString('base64') @@ -911,7 +982,7 @@ describe('OpenId4Vc', () => { }) expect(authorizationRequest).toEqual( - `openid4vp://?client_id=localhost%3A3443&request_uri=${encodeURIComponent( + `openid4vp://?client_id=localhost%3A1234&request_uri=${encodeURIComponent( verificationSession.authorizationRequestUri )}` ) @@ -970,108 +1041,103 @@ describe('OpenId4Vc', () => { resolvedAuthorizationRequest.presentationExchange.credentialsForRequest ) - try { - const { serverResponse, submittedResponse } = - await holder.agent.modules.openId4VcHolder.acceptSiopAuthorizationRequest({ - authorizationRequest: resolvedAuthorizationRequest.authorizationRequest, - presentationExchange: { - credentials: selectedCredentials, - }, - }) - - // path_nested should not be used for sd-jwt - expect(submittedResponse.presentation_submission?.descriptor_map[0].path_nested).toBeUndefined() - expect(submittedResponse).toEqual({ - presentation_submission: { - definition_id: 'OpenBadgeCredential', - descriptor_map: [ - { - format: 'vc+sd-jwt', - id: 'OpenBadgeCredentialDescriptor', - path: '$', - }, - ], - id: expect.any(String), + const { serverResponse, submittedResponse } = + await holder.agent.modules.openId4VcHolder.acceptSiopAuthorizationRequest({ + authorizationRequest: resolvedAuthorizationRequest.authorizationRequest, + presentationExchange: { + credentials: selectedCredentials, }, - state: expect.any(String), - vp_token: expect.any(String), - }) - expect(serverResponse).toMatchObject({ - status: 200, }) - // The RP MUST validate that the aud (audience) Claim contains the value of the client_id - // that the RP sent in the Authorization Request as an audience. - // When the request has been signed, the value might be an HTTPS URL, or a Decentralized Identifier. - await waitForVerificationSessionRecordSubject(verifier.replaySubject, { - contextCorrelationId: verifier.agent.context.contextCorrelationId, - state: OpenId4VcVerificationSessionState.ResponseVerified, - verificationSessionId: verificationSession.id, - }) - const { idToken, presentationExchange } = - await verifier.agent.modules.openId4VcVerifier.getVerifiedAuthorizationResponse(verificationSession.id) + // path_nested should not be used for sd-jwt + expect(submittedResponse.presentation_submission?.descriptor_map[0].path_nested).toBeUndefined() + expect(submittedResponse).toEqual({ + presentation_submission: { + definition_id: 'OpenBadgeCredential', + descriptor_map: [ + { + format: 'vc+sd-jwt', + id: 'OpenBadgeCredentialDescriptor', + path: '$', + }, + ], + id: expect.any(String), + }, + state: expect.any(String), + vp_token: expect.any(String), + }) + expect(serverResponse).toMatchObject({ + status: 200, + }) - expect(idToken).toBeUndefined() + // The RP MUST validate that the aud (audience) Claim contains the value of the client_id + // that the RP sent in the Authorization Request as an audience. + // When the request has been signed, the value might be an HTTPS URL, or a Decentralized Identifier. + await waitForVerificationSessionRecordSubject(verifier.replaySubject, { + contextCorrelationId: verifier.agent.context.contextCorrelationId, + state: OpenId4VcVerificationSessionState.ResponseVerified, + verificationSessionId: verificationSession.id, + }) + const { idToken, presentationExchange } = + await verifier.agent.modules.openId4VcVerifier.getVerifiedAuthorizationResponse(verificationSession.id) - const presentation = presentationExchange?.presentations[0] as SdJwtVc + expect(idToken).toBeUndefined() - // name SHOULD NOT be disclosed - expect(presentation.prettyClaims).not.toHaveProperty('name') + const presentation = presentationExchange?.presentations[0] as SdJwtVc - // university and name SHOULD NOT be in the signed payload - expect(presentation.payload).not.toHaveProperty('university') - expect(presentation.payload).not.toHaveProperty('name') + // name SHOULD NOT be disclosed + expect(presentation.prettyClaims).not.toHaveProperty('name') - expect(presentationExchange).toEqual({ - definition: presentationDefinition, - submission: { - definition_id: 'OpenBadgeCredential', - descriptor_map: [ - { - format: 'vc+sd-jwt', - id: 'OpenBadgeCredentialDescriptor', - path: '$', - }, - ], - id: expect.any(String), - }, - presentations: [ + // university and name SHOULD NOT be in the signed payload + expect(presentation.payload).not.toHaveProperty('university') + expect(presentation.payload).not.toHaveProperty('name') + + expect(presentationExchange).toEqual({ + definition: presentationDefinition, + submission: { + definition_id: 'OpenBadgeCredential', + descriptor_map: [ { - compact: expect.any(String), - header: { - alg: 'EdDSA', - kid: '#z6MkrzQPBr4pyqC776KKtrz13SchM5ePPbssuPuQZb5t4uKQ', - typ: 'vc+sd-jwt', - }, - payload: { - _sd: [expect.any(String), expect.any(String)], - _sd_alg: 'sha-256', - cnf: { - kid: 'did:key:z6MkpGR4gs4Rc3Zph4vj8wRnjnAxgAPSxcR8MAVKutWspQzc#z6MkpGR4gs4Rc3Zph4vj8wRnjnAxgAPSxcR8MAVKutWspQzc', - }, - iat: expect.any(Number), - iss: 'did:key:z6MkrzQPBr4pyqC776KKtrz13SchM5ePPbssuPuQZb5t4uKQ', - vct: 'OpenBadgeCredential', - degree: 'bachelor', + format: 'vc+sd-jwt', + id: 'OpenBadgeCredentialDescriptor', + path: '$', + }, + ], + id: expect.any(String), + }, + presentations: [ + { + compact: expect.any(String), + header: { + alg: 'EdDSA', + kid: '#z6MkrzQPBr4pyqC776KKtrz13SchM5ePPbssuPuQZb5t4uKQ', + typ: 'vc+sd-jwt', + }, + payload: { + _sd: [expect.any(String), expect.any(String)], + _sd_alg: 'sha-256', + cnf: { + kid: 'did:key:z6MkpGR4gs4Rc3Zph4vj8wRnjnAxgAPSxcR8MAVKutWspQzc#z6MkpGR4gs4Rc3Zph4vj8wRnjnAxgAPSxcR8MAVKutWspQzc', }, - // university SHOULD be disclosed - prettyClaims: { - cnf: { - kid: 'did:key:z6MkpGR4gs4Rc3Zph4vj8wRnjnAxgAPSxcR8MAVKutWspQzc#z6MkpGR4gs4Rc3Zph4vj8wRnjnAxgAPSxcR8MAVKutWspQzc', - }, - iat: expect.any(Number), - iss: 'did:key:z6MkrzQPBr4pyqC776KKtrz13SchM5ePPbssuPuQZb5t4uKQ', - vct: 'OpenBadgeCredential', - degree: 'bachelor', - university: 'innsbruck', + iat: expect.any(Number), + iss: 'did:key:z6MkrzQPBr4pyqC776KKtrz13SchM5ePPbssuPuQZb5t4uKQ', + vct: 'OpenBadgeCredential', + degree: 'bachelor', + }, + // university SHOULD be disclosed + prettyClaims: { + cnf: { + kid: 'did:key:z6MkpGR4gs4Rc3Zph4vj8wRnjnAxgAPSxcR8MAVKutWspQzc#z6MkpGR4gs4Rc3Zph4vj8wRnjnAxgAPSxcR8MAVKutWspQzc', }, + iat: expect.any(Number), + iss: 'did:key:z6MkrzQPBr4pyqC776KKtrz13SchM5ePPbssuPuQZb5t4uKQ', + vct: 'OpenBadgeCredential', + degree: 'bachelor', + university: 'innsbruck', }, - ], - }) - } catch (error) { - console.error('error', error) - throw error - } + }, + ], + }) }) it('e2e flow with verifier endpoints verifying a sd-jwt-vc with selective disclosure', async () => { @@ -1096,7 +1162,7 @@ describe('OpenId4Vc', () => { const certificate = await verifier.agent.x509.createSelfSignedCertificate({ key: await verifier.agent.wallet.createKey({ keyType: KeyType.Ed25519 }), - extensions: [[{ type: 'dns', value: `localhost:${proxyPort}` }]], + extensions: [[{ type: 'dns', value: `localhost:${serverPort}` }]], }) const rawCertificate = certificate.toString('base64') @@ -1148,7 +1214,7 @@ describe('OpenId4Vc', () => { }) expect(authorizationRequest).toEqual( - `openid4vp://?client_id=localhost%3A3443&request_uri=${encodeURIComponent( + `openid4vp://?client_id=localhost%3A1234&request_uri=${encodeURIComponent( verificationSession.authorizationRequestUri )}` ) @@ -1344,7 +1410,7 @@ describe('OpenId4Vc', () => { const certificate = await verifier.agent.x509.createSelfSignedCertificate({ key: await verifier.agent.wallet.createKey({ keyType: KeyType.Ed25519 }), - extensions: [[{ type: 'dns', value: `localhost:${proxyPort}` }]], + extensions: [[{ type: 'dns', value: `localhost:${serverPort}` }]], }) const rawCertificate = certificate.toString('base64') @@ -1420,7 +1486,7 @@ describe('OpenId4Vc', () => { }) expect(authorizationRequest).toEqual( - `openid4vp://?client_id=localhost%3A3443&request_uri=${encodeURIComponent( + `openid4vp://?client_id=localhost%3A1234&request_uri=${encodeURIComponent( verificationSession.authorizationRequestUri )}` ) @@ -1666,22 +1732,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'], proof_types_supported: { jwt: { - proof_signing_alg_values_supported: ['ES256'], + proof_signing_alg_values_supported: ['ES256', 'EdDSA'], }, }, doctype: universityDegreeCredentialConfigurationSupportedMdoc.doctype, @@ -1693,7 +1750,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', }) @@ -1712,26 +1769,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'], 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` ) @@ -1743,7 +1802,26 @@ 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, + }), + }, + scope: 'UniversityDegreeCredential', + 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: resolvedCredentialOffer1.credentialOfferPayload.grants?.[preAuthorizedCodeGrantIdentifier]?.[ + 'pre-authorized_code' + ], + }) const credentialsTenant1 = await holderTenant1.modules.openId4VcHolder.requestCredentials({ resolvedCredentialOffer: resolvedCredentialOffer1, @@ -1773,8 +1851,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') @@ -1892,12 +1970,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', @@ -1912,28 +1984,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({ @@ -2013,10 +2071,6 @@ describe('OpenId4Vc', () => { 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, @@ -2249,11 +2303,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, @@ -2267,28 +2316,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({ @@ -2370,10 +2405,6 @@ describe('OpenId4Vc', () => { 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, diff --git a/packages/openid4vc/tests/utils.ts b/packages/openid4vc/tests/utils.ts index c11c752a63..d5ebe4dd3e 100644 --- a/packages/openid4vc/tests/utils.ts +++ b/packages/openid4vc/tests/utils.ts @@ -10,6 +10,7 @@ import type { Observable } from 'rxjs' import { Agent, LogLevel, utils } from '@credo-ts/core' import { ReplaySubject, lastValueFrom, filter, timeout, catchError, take, map } from 'rxjs' + import { TestLogger, agentDependencies, @@ -18,12 +19,22 @@ import { } from '../../core/tests' import { OpenId4VcVerifierEvents, OpenId4VcIssuerEvents } from '../src' -export async function createAgentFromModules(label: string, modulesMap: MM, secretKey: string, customFetch?: typeof global.fetch) { +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) }, + config: { + label, + walletConfig: { id: utils.uuid(), key: utils.uuid() }, + allowInsecureHttpUrls: true, + logger: new TestLogger(LogLevel.off), + }, dependencies: { ...agentDependencies, - fetch: customFetch ?? agentDependencies.fetch + fetch: customFetch ?? agentDependencies.fetch, }, modules: modulesMap, }) diff --git a/packages/openid4vc/tests/utilsVci.ts b/packages/openid4vc/tests/utilsVci.ts index be87dd96ad..e123332702 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 +} 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 +} 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 fc43ca1272..687fad122b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -582,8 +582,8 @@ importers: specifier: ^6.0.0 version: 6.0.3 nock: - specifier: ^13.3.0 - version: 13.5.5 + specifier: ^14.0.0-beta.15 + version: 14.0.0-beta.15 rimraf: specifier: ^4.4.0 version: 4.4.1 @@ -712,8 +712,8 @@ importers: specifier: ^8.5.4 version: 8.5.12 nock: - specifier: ^13.3.0 - version: 13.5.5 + specifier: ^14.0.0-beta.15 + version: 14.0.0-beta.15 rimraf: specifier: ^4.4.0 version: 4.4.1 @@ -724,11 +724,11 @@ importers: packages/openid4vc: dependencies: '@animo-id/oauth2': - specifier: 0.0.2-alpha-20241104215631 - version: 0.0.2-alpha-20241104215631(typescript@5.5.4) + specifier: 0.0.2-alpha-20241108100449 + version: 0.0.2-alpha-20241108100449(typescript@5.5.4) '@animo-id/oid4vci': - specifier: 0.0.2-alpha-20241104215631 - version: 0.0.2-alpha-20241104215631(typescript@5.5.4) + specifier: 0.0.2-alpha-20241108100449 + version: 0.0.2-alpha-20241108100449(typescript@5.5.4) '@credo-ts/core': specifier: workspace:* version: link:../core @@ -744,9 +744,6 @@ importers: class-transformer: specifier: ^0.5.1 version: 0.5.1 - oauth4webapi: - specifier: ^3.0.1 - version: 3.0.1 rxjs: specifier: ^7.8.0 version: 7.8.1 @@ -764,8 +761,8 @@ importers: specifier: ^4.18.2 version: 4.19.2 nock: - specifier: ^13.3.0 - version: 13.5.5 + specifier: ^14.0.0-beta.15 + version: 14.0.0-beta.15 rimraf: specifier: ^4.4.0 version: 4.4.1 @@ -937,14 +934,14 @@ packages: react: '*' react-native: '*' - '@animo-id/oauth2-utils@0.0.2-alpha-20241104215631': - resolution: {integrity: sha512-HRDkLU+hJBRiFG1P1ZOXcjCeE4dv8Novbvc7/VcHAl5eUv4exFCwvUgei3J3EM4E/vsO595PVpUsSXpVhXvLxg==} + '@animo-id/oauth2-utils@0.0.2-alpha-20241108100449': + resolution: {integrity: sha512-FOA172Sqr/feUlusUln7+gfcmTcRn+tuhzl2W72xQArZUPr3dgJX8ijT5cwfw7m2ZPc0alJ8ieAR6gCr4pD4wQ==} - '@animo-id/oauth2@0.0.2-alpha-20241104215631': - resolution: {integrity: sha512-EZr1i5DD9va8uFBVv0b0wtO/cifpfgbD+NOHhuZP7Eo266S2Nka+NmVWOmB6KBlz+hI8fi1aEvo2zGKAxZ1VYw==} + '@animo-id/oauth2@0.0.2-alpha-20241108100449': + resolution: {integrity: sha512-b21zfOb6zYORy1bQvO9MMRrshB+vHpTRQi5tAb5AUQ5VpNRqwron9ct4Huw2Sh43kBT7QadN+5kt3mKStHBXng==} - '@animo-id/oid4vci@0.0.2-alpha-20241104215631': - resolution: {integrity: sha512-khS+7h3A43kvo0cjO97TijsbGVdwFE2VDkFE47ptJpJAfHtvgZlQ4CuffK3PA8iVF0FsT9PAUbZveKQKNxtDVw==} + '@animo-id/oid4vci@0.0.2-alpha-20241108100449': + resolution: {integrity: sha512-GYLEwvKlqbcvxDl6mlnaE+hc+vRM/63Ah7Bwe8b1SUepnLvSfBJamkQiinMSfqiB23lp5j0NoQRURM8euc+8Bw==} '@animo-id/react-native-bbs-signatures@0.1.0': resolution: {integrity: sha512-7qvsiWhGfUev8ngE8YzF6ON9PtCID5LiYVYM4EC5eyj80gCdhx3R46CI7K1qbqIlGsoTYQ/Xx5Ubo5Ji9eaUEA==} @@ -2645,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==} @@ -2682,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==} @@ -2926,6 +2936,7 @@ packages: '@sphereon/kmp-mdl-mdoc@0.2.0-SNAPSHOT.22': resolution: {integrity: sha512-uAZZExVy+ug9JLircejWa5eLtAZ7bnBP6xb7DO2+86LRsHNLh2k2jMWJYxp+iWtGHTsh6RYsZl14ScQLvjiQ/A==} + bundledDependencies: [] '@sphereon/oid4vc-common@0.16.1-fix.173': resolution: {integrity: sha512-+AAUvEEFs0vzz1mrgjSgvDkcBtr18d2XEVgJex7QlAqxCKVGfjzZlqL2Q2vOLKYVaXsazhD5LnYiY6B5WMTC3Q==} @@ -5324,6 +5335,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'} @@ -6324,9 +6338,9 @@ 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'} node-addon-api@3.2.1: resolution: {integrity: sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==} @@ -6408,9 +6422,6 @@ packages: nullthrows@1.1.1: resolution: {integrity: sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==} - oauth4webapi@3.0.1: - resolution: {integrity: sha512-72NtHQg8vCvm62NE7ckW9brybI3yBaFka5BeEs7Z4jws/k3W1e5UAt61icij/dL1cMnVpxul5jfkMcCTHLXqUQ==} - ob1@0.73.10: resolution: {integrity: sha512-aO6EYC+QRRCkZxVJhCWhLKgVjhNuD6Gu1riGjxrIm89CqLsmKgxzYDDEsktmKsoDeRdWGQM5EdMzXDl5xcVfsw==} @@ -6522,6 +6533,9 @@ 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'} @@ -7307,6 +7321,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'} @@ -8189,24 +8206,24 @@ snapshots: 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/oauth2-utils@0.0.2-alpha-20241104215631(typescript@5.5.4)': + '@animo-id/oauth2-utils@0.0.2-alpha-20241108100449(typescript@5.5.4)': dependencies: buffer: 6.0.3 valibot: 0.42.1(typescript@5.5.4) transitivePeerDependencies: - typescript - '@animo-id/oauth2@0.0.2-alpha-20241104215631(typescript@5.5.4)': + '@animo-id/oauth2@0.0.2-alpha-20241108100449(typescript@5.5.4)': dependencies: - '@animo-id/oauth2-utils': 0.0.2-alpha-20241104215631(typescript@5.5.4) + '@animo-id/oauth2-utils': 0.0.2-alpha-20241108100449(typescript@5.5.4) valibot: 0.42.1(typescript@5.5.4) transitivePeerDependencies: - typescript - '@animo-id/oid4vci@0.0.2-alpha-20241104215631(typescript@5.5.4)': + '@animo-id/oid4vci@0.0.2-alpha-20241108100449(typescript@5.5.4)': dependencies: - '@animo-id/oauth2': 0.0.2-alpha-20241104215631(typescript@5.5.4) - '@animo-id/oauth2-utils': 0.0.2-alpha-20241104215631(typescript@5.5.4) + '@animo-id/oauth2': 0.0.2-alpha-20241108100449(typescript@5.5.4) + '@animo-id/oauth2-utils': 0.0.2-alpha-20241108100449(typescript@5.5.4) valibot: 0.42.1(typescript@5.5.4) transitivePeerDependencies: - typescript @@ -11741,6 +11758,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': {} @@ -11771,6 +11797,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 @@ -15792,6 +15827,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 @@ -17278,13 +17315,11 @@ 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 - transitivePeerDependencies: - - supports-color node-addon-api@3.2.1: {} @@ -17355,8 +17390,6 @@ snapshots: nullthrows@1.1.1: {} - oauth4webapi@3.0.1: {} - ob1@0.73.10: {} object-assign@4.1.1: {} @@ -17502,6 +17535,8 @@ snapshots: outdent@0.5.0: {} + outvariant@1.4.3: {} + p-cancelable@3.0.0: {} p-filter@2.1.0: @@ -18400,6 +18435,8 @@ snapshots: streamsearch@1.1.0: {} + strict-event-emitter@0.5.1: {} + strict-uri-encode@2.0.0: {} string-length@4.0.2: diff --git a/tests/https-proxy.ts b/tests/https-proxy.ts deleted file mode 100644 index bf398125c6..0000000000 --- a/tests/https-proxy.ts +++ /dev/null @@ -1,71 +0,0 @@ -import express from 'express' -import { createProxyMiddleware } from 'http-proxy-middleware' -import selfsigned from 'selfsigned' -import https, { globalAgent } from 'https' -import { setGlobalDispatcher, Agent, getGlobalDispatcher } from 'undici' -import { agentDependencies } from '@credo-ts/node' - -export function disableSslVerification() { - - const originalDispatcher = getGlobalDispatcher() - // Works for global fetch (undici) - const dispatcher = new Agent({ - connect: { - rejectUnauthorized: false, - }, - }) - const originalFetch = fetch - global.fetch = (url, options) => - // @ts-ignore - originalFetch(url, { - ...options, - dispatcher, - }) - agentDependencies.fetch = global.fetch - setGlobalDispatcher(dispatcher) - - // We use a self-signed certificate and so we need to disable invalid ssl certificates - // Works for node-fetch (cross-fetch) - globalAgent.options.rejectUnauthorized = false - - return () => { - global.fetch = originalFetch - agentDependencies.fetch = originalFetch - setGlobalDispatcher(originalDispatcher) - globalAgent.options.rejectUnauthorized = true - } -} - -export function createHttpsProxy({ target, port }: { target: string; port: number }) { - // Generate certificates on the fly - const attrs = [{ name: 'commonName', value: `localhost:${port}` }] - const pems = selfsigned.generate(attrs, { - algorithm: 'sha256', - days: 30, - keySize: 2048, - }) - - const app = express() - - // Configure proxy middleware - const proxy = createProxyMiddleware({ - target, - changeOrigin: true, - secure: false, - }) - - // Use proxy for all routes - app.use(proxy) - - const server = https - .createServer( - { - key: pems.private, - cert: pems.cert, - }, - app - ) - .listen(port) - - return server -} From bee17c8d54d2acd7d5c261a436c5b3a3a6fb3b60 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Fri, 8 Nov 2024 17:26:49 +0530 Subject: [PATCH 9/9] docs(changeset): 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. Signed-off-by: Timo Glastra --- .changeset/shiny-sheep-appear.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .changeset/shiny-sheep-appear.md 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.