diff --git a/packages/ebsi-support/package.json b/packages/ebsi-support/package.json index c6e4216b4..87e698bb0 100644 --- a/packages/ebsi-support/package.json +++ b/packages/ebsi-support/package.json @@ -15,8 +15,8 @@ }, "dependencies": { "@ethersproject/random": "^5.7.0", - "@sphereon/did-auth-siop": "0.16.1-next.233", - "@sphereon/did-auth-siop-adapter": "0.16.1-next.233", + "@sphereon/did-auth-siop": "0.16.1-next.339", + "@sphereon/did-auth-siop-adapter": "0.16.1-next.339", "@sphereon/pex": "5.0.0-unstable.28", "@sphereon/pex-models": "^2.3.2", "@sphereon/ssi-sdk-ext.did-resolver-ebsi": "0.27.0", @@ -44,8 +44,8 @@ "xstate": "^4.38.3" }, "devDependencies": { - "@sphereon/oid4vci-client": "0.16.1-next.233", - "@sphereon/oid4vci-common": "0.16.1-next.233", + "@sphereon/oid4vci-client": "0.16.1-next.339", + "@sphereon/oid4vci-common": "0.16.1-next.339", "@sphereon/ssi-express-support": "workspace:*", "@sphereon/ssi-sdk-ext.key-manager": "0.27.0", "@sphereon/ssi-sdk-ext.kms-local": "0.27.0", diff --git a/packages/mdl-mdoc/package.json b/packages/mdl-mdoc/package.json index 876c0f50f..e079c408e 100644 --- a/packages/mdl-mdoc/package.json +++ b/packages/mdl-mdoc/package.json @@ -14,7 +14,7 @@ "build:clean": "tsc --build --clean && tsc --build" }, "dependencies": { - "@sphereon/did-auth-siop": "0.16.1-next.233", + "@sphereon/did-auth-siop": "0.16.1-next.339", "@sphereon/kmp-mdoc-core": "0.2.0-SNAPSHOT.26", "@sphereon/pex": "5.0.0-unstable.28", "@sphereon/pex-models": "^2.3.2", @@ -35,8 +35,8 @@ "uuid": "^9.0.1" }, "devDependencies": { - "@sphereon/oid4vci-client": "0.16.1-next.233", - "@sphereon/oid4vci-common": "0.16.1-next.233", + "@sphereon/oid4vci-client": "0.16.1-next.339", + "@sphereon/oid4vci-common": "0.16.1-next.339", "@sphereon/ssi-express-support": "workspace:*", "@sphereon/ssi-sdk-ext.key-manager": "0.27.0", "@sphereon/ssi-sdk-ext.kms-local": "0.27.0", diff --git a/packages/oid4vci-holder/package.json b/packages/oid4vci-holder/package.json index 62557fa34..3ec00c889 100644 --- a/packages/oid4vci-holder/package.json +++ b/packages/oid4vci-holder/package.json @@ -15,8 +15,9 @@ }, "dependencies": { "@sphereon/kmp-mdoc-core": "0.2.0-SNAPSHOT.26", - "@sphereon/oid4vci-client": "0.16.1-next.233", - "@sphereon/oid4vci-common": "0.16.1-next.233", + "@sphereon/did-auth-siop": "0.16.1-next.339", + "@sphereon/oid4vci-client": "0.16.1-next.339", + "@sphereon/oid4vci-common": "0.16.1-next.339", "@sphereon/ssi-sdk-ext.did-utils": "0.27.0", "@sphereon/ssi-sdk-ext.identifier-resolution": "0.27.0", "@sphereon/ssi-sdk-ext.jwt-service": "0.27.0", @@ -30,6 +31,7 @@ "@sphereon/ssi-sdk.mdl-mdoc": "workspace:*", "@sphereon/ssi-sdk.oidf-client": "workspace:*", "@sphereon/ssi-sdk.sd-jwt": "workspace:*", + "@sphereon/ssi-sdk.siopv2-oid4vp-op-auth": "workspace:*", "@sphereon/ssi-sdk.xstate-machine-persistence": "workspace:*", "@sphereon/ssi-types": "workspace:*", "@veramo/core": "4.2.0", @@ -43,7 +45,8 @@ "xstate": "^4.38.3" }, "devDependencies": { - "@sphereon/oid4vc-common": "0.16.1-next.187", + "@sphereon/oid4vc-common": "0.16.1-next.339", + "@sphereon/ssi-sdk.siopv2-oid4vp-common": "workspace:*", "@sphereon/ssi-sdk-ext.did-resolver-jwk": "0.27.0", "@types/i18n-js": "^3.8.9", "@types/lodash.memoize": "^4.1.9", diff --git a/packages/oid4vci-holder/src/agent/OID4VCIHolder.ts b/packages/oid4vci-holder/src/agent/OID4VCIHolder.ts index 056129865..e3019e63f 100644 --- a/packages/oid4vci-holder/src/agent/OID4VCIHolder.ts +++ b/packages/oid4vci-holder/src/agent/OID4VCIHolder.ts @@ -70,12 +70,12 @@ import { import { asArray, computeEntryHash } from '@veramo/utils' import { decodeJWT } from 'did-jwt' import { v4 as uuidv4 } from 'uuid' -import { OID4VCIMachine } from '../machine/oid4vciMachine' +import { OID4VCIMachine } from '../machines/oid4vciMachine' import { AddContactIdentityArgs, AssertValidCredentialsArgs, Attribute, - createCredentialsToSelectFromArgs, + CreateCredentialsToSelectFromArgs, CredentialToAccept, CredentialToSelectFromResult, GetContactArgs, @@ -91,6 +91,8 @@ import { OID4VCIHolderOptions, OID4VCIMachine as OID4VCIMachineId, OID4VCIMachineInstanceOpts, + OID4VCIMachineServiceDefinitions, + OID4VCIMachineServices, OnContactIdentityCreatedArgs, OnCredentialStoredArgs, OnIdentifierCreatedArgs, @@ -98,13 +100,14 @@ import { RequestType, RequiredContext, SendNotificationArgs, + StartFirstPartApplicationMachine, StartResult, StoreCredentialBrandingArgs, StoreCredentialsArgs, StoreIssuerBrandingArgs, VerificationResult, VerifyEBSICredentialIssuerArgs, - VerifyEBSICredentialIssuerResult, + VerifyEBSICredentialIssuerResult } from '../types/IOID4VCIHolder' import { getBasicIssuerLocaleBranding, @@ -115,8 +118,8 @@ import { mapCredentialToAccept, selectCredentialLocaleBranding, verifyCredentialToAccept, -} from './OID4VCIHolderService' - + startFirstPartApplicationMachine +} from '../services/OID4VCIHolderService' import 'cross-fetch/polyfill' /** @@ -307,8 +310,8 @@ export class OID4VCIHolder implements IAgentPlugin { */ private async oid4vciHolderGetMachineInterpreter(opts: OID4VCIMachineInstanceOpts, context: RequiredContext): Promise { const authorizationRequestOpts = { ...this.defaultAuthorizationRequestOpts, ...opts.authorizationRequestOpts } - const services = { - start: (args: PrepareStartArgs) => + const services: OID4VCIMachineServiceDefinitions = { + [OID4VCIMachineServices.start]: (args: PrepareStartArgs) => this.oid4vciHolderStart( { ...args, @@ -316,18 +319,18 @@ export class OID4VCIHolder implements IAgentPlugin { }, context, ), - createCredentialsToSelectFrom: (args: createCredentialsToSelectFromArgs) => this.oid4vciHolderCreateCredentialsToSelectFrom(args, context), - getContact: (args: GetContactArgs) => this.oid4vciHolderGetContact(args, context), - getCredentials: (args: GetCredentialsArgs) => - this.oid4vciHolderGetCredentials({ accessTokenOpts: args.accessTokenOpts ?? opts.accessTokenOpts, ...args }, context), - addContactIdentity: (args: AddContactIdentityArgs) => this.oid4vciHolderAddContactIdentity(args, context), - getIssuerBranding: (args: GetIssuerBrandingArgs) => this.oid4vciHolderGetIssuerBranding(args, context), - storeIssuerBranding: (args: StoreIssuerBrandingArgs) => this.oid4vciHolderStoreIssuerBranding(args, context), - assertValidCredentials: (args: AssertValidCredentialsArgs) => this.oid4vciHolderAssertValidCredentials(args, context), - storeCredentialBranding: (args: StoreCredentialBrandingArgs) => this.oid4vciHolderStoreCredentialBranding(args, context), - storeCredentials: (args: StoreCredentialsArgs) => this.oid4vciHolderStoreCredentials(args, context), - sendNotification: (args: SendNotificationArgs) => this.oid4vciHolderSendNotification(args, context), - getFederationTrust: (args: GetFederationTrustArgs) => this.getFederationTrust(args, context), + [OID4VCIMachineServices.startFirstPartApplicationFlow]: (args: StartFirstPartApplicationMachine) => startFirstPartApplicationMachine({ ...args, stateNavigationListener: opts.firstPartyStateNavigationListener }, context), + [OID4VCIMachineServices.createCredentialsToSelectFrom]: (args: CreateCredentialsToSelectFromArgs) => this.oid4vciHolderCreateCredentialsToSelectFrom(args, context), + [OID4VCIMachineServices.getContact]: (args: GetContactArgs) => this.oid4vciHolderGetContact(args, context), + [OID4VCIMachineServices.getCredentials]: (args: GetCredentialsArgs) => this.oid4vciHolderGetCredentials({ accessTokenOpts: args.accessTokenOpts ?? opts.accessTokenOpts, ...args }, context), + [OID4VCIMachineServices.addContactIdentity]: (args: AddContactIdentityArgs) => this.oid4vciHolderAddContactIdentity(args, context), + [OID4VCIMachineServices.getIssuerBranding]: (args: GetIssuerBrandingArgs) => this.oid4vciHolderGetIssuerBranding(args, context), + [OID4VCIMachineServices.storeIssuerBranding]: (args: StoreIssuerBrandingArgs) => this.oid4vciHolderStoreIssuerBranding(args, context), + [OID4VCIMachineServices.assertValidCredentials]: (args: AssertValidCredentialsArgs) => this.oid4vciHolderAssertValidCredentials(args, context), + [OID4VCIMachineServices.storeCredentialBranding]: (args: StoreCredentialBrandingArgs) => this.oid4vciHolderStoreCredentialBranding(args, context), + [OID4VCIMachineServices.storeCredentials]: (args: StoreCredentialsArgs) => this.oid4vciHolderStoreCredentials(args, context), + [OID4VCIMachineServices.sendNotification]: (args: SendNotificationArgs) => this.oid4vciHolderSendNotification(args, context), + [OID4VCIMachineServices.getFederationTrust]: (args: GetFederationTrustArgs) => this.getFederationTrust(args, context), } const oid4vciMachineInstanceArgs: OID4VCIMachineInstanceOpts = { @@ -463,7 +466,7 @@ export class OID4VCIHolder implements IAgentPlugin { } private async oid4vciHolderCreateCredentialsToSelectFrom( - args: createCredentialsToSelectFromArgs, + args: CreateCredentialsToSelectFromArgs, context: RequiredContext, ): Promise> { const { credentialBranding, locale, selectedCredentials /*, openID4VCIClientState*/, credentialsSupported } = args diff --git a/packages/oid4vci-holder/src/index.ts b/packages/oid4vci-holder/src/index.ts index b26549202..9d58b8849 100644 --- a/packages/oid4vci-holder/src/index.ts +++ b/packages/oid4vci-holder/src/index.ts @@ -3,7 +3,9 @@ */ export { OID4VCIHolder, oid4vciHolderContextMethods, signCallback } from './agent/OID4VCIHolder' -export * from './agent/OID4VCIHolderService' +export * from './services/OID4VCIHolderService' +export * from './services/FirstPartyMachineServices' export * from './types/IOID4VCIHolder' -export * from './machine/headlessStateNavListener' +export * from './types/FirstPartyMachine' +export * from './listeners/headlessStateNavListener' export * from './link-handler' diff --git a/packages/oid4vci-holder/src/link-handler/index.ts b/packages/oid4vci-holder/src/link-handler/index.ts index d08c86d30..6532e8fe7 100644 --- a/packages/oid4vci-holder/src/link-handler/index.ts +++ b/packages/oid4vci-holder/src/link-handler/index.ts @@ -3,23 +3,28 @@ import { AuthorizationRequestOpts, AuthorizationServerClientOpts, AuthzFlowType, import { DefaultLinkPriorities, LinkHandlerAdapter } from '@sphereon/ssi-sdk.core' import { IMachineStatePersistence, interpreterStartOrResume, SerializableState } from '@sphereon/ssi-sdk.xstate-machine-persistence' import { IAgentContext } from '@veramo/core' -import { GetMachineArgs, IOID4VCIHolder, OID4VCIMachineEvents, OID4VCIMachineInterpreter, OID4VCIMachineState } from '../types/IOID4VCIHolder' +import { + GetMachineArgs, + IOID4VCIHolder, + OID4VCIMachineEvents, + OID4VCIMachineStateNavigationListener +} from '../types/IOID4VCIHolder' +import { FirstPartyMachineStateNavigationListener } from '../types/FirstPartyMachine' /** * This handler only handles credential offer links (either by value or by reference) */ export class OID4VCIHolderLinkHandler extends LinkHandlerAdapter { private readonly context: IAgentContext - private readonly stateNavigationListener: - | ((oid4vciMachine: OID4VCIMachineInterpreter, state: OID4VCIMachineState, navigation?: any) => Promise) - | undefined + private readonly stateNavigationListener?: OID4VCIMachineStateNavigationListener + private readonly firstPartyStateNavigationListener?: FirstPartyMachineStateNavigationListener private readonly noStateMachinePersistence: boolean private readonly authorizationRequestOpts?: AuthorizationRequestOpts private readonly clientOpts?: AuthorizationServerClientOpts private readonly trustAnchors?: Array constructor( - args: Pick & { + args: Pick & { priority?: number | DefaultLinkPriorities protocols?: Array noStateMachinePersistence?: boolean @@ -32,6 +37,7 @@ export class OID4VCIHolderLinkHandler extends LinkHandlerAdapter { this.context = args.context this.noStateMachinePersistence = args.noStateMachinePersistence === true this.stateNavigationListener = args.stateNavigationListener + this.firstPartyStateNavigationListener = args.firstPartyStateNavigationListener this.trustAnchors = args.trustAnchors } @@ -63,6 +69,7 @@ export class OID4VCIHolderLinkHandler extends LinkHandlerAdapter { authorizationRequestOpts: { ...this.authorizationRequestOpts, ...opts?.authorizationRequestOpts }, ...((clientOpts.clientId || clientOpts.clientAssertionType) && { clientOpts: clientOpts as AuthorizationServerClientOpts }), stateNavigationListener: this.stateNavigationListener, + firstPartyStateNavigationListener: this.firstPartyStateNavigationListener }) const interpreter = oid4vciMachine.interpreter diff --git a/packages/oid4vci-holder/src/machine/headlessStateNavListener.ts b/packages/oid4vci-holder/src/listeners/headlessStateNavListener.ts similarity index 100% rename from packages/oid4vci-holder/src/machine/headlessStateNavListener.ts rename to packages/oid4vci-holder/src/listeners/headlessStateNavListener.ts diff --git a/packages/oid4vci-holder/src/localization/translations/en.json b/packages/oid4vci-holder/src/localization/translations/en.json index 927b6ab88..668c54415 100644 --- a/packages/oid4vci-holder/src/localization/translations/en.json +++ b/packages/oid4vci-holder/src/localization/translations/en.json @@ -11,5 +11,9 @@ "oid4vci_machine_initiation_error_title": "Initiate OID4VCI provider", "oid4vci_machine_credential_verification_failed_message": "The credential verification resulted in an error.", "oid4vci_machine_credential_verification_schema_failed_message": "The credential schema verification resulted in an error.", - "oid4vci_machine_retrieve_federation_trust_error_title": "Retrieve federation trust" + "oid4vci_machine_retrieve_federation_trust_error_title": "Retrieve federation trust", + "oid4vci_machine_first_party_error_title": "First party flow", + "oid4vci_machine_send_authorization_challenge_request_error_title": "Sending authorization challenge request", + "oid4vci_machine_create_config_error_title": "Creating siopV2 config", + "oid4vci_machine_get_request_error_title": "Getting siopV2 request" } diff --git a/packages/oid4vci-holder/src/localization/translations/nl.json b/packages/oid4vci-holder/src/localization/translations/nl.json index 7c7e50827..587d8ec55 100644 --- a/packages/oid4vci-holder/src/localization/translations/nl.json +++ b/packages/oid4vci-holder/src/localization/translations/nl.json @@ -10,5 +10,9 @@ "oid4vci_machine_credential_selection_error_title": "Credential selectie", "oid4vci_machine_initiation_error_title": "Initiƫren OID4VCI provider", "oid4vci_machine_credential_verification_failed_message": "Verificatie van de credential leidde tot een fout.", - "oid4vci_machine_retrieve_federation_trust_error_title": "Ophalen federatievertrouwen" + "oid4vci_machine_retrieve_federation_trust_error_title": "Ophalen federatievertrouwen", + "oid4vci_machine_first_party_error_title": "Eerste partijstroom", + "oid4vci_machine_send_authorization_challenge_request_error_title": "Versturen autorisatie-uitdaging aanvraag", + "oid4vci_machine_create_config_error_title": "SiopV2-configuratie aanmaken", + "oid4vci_machine_get_request_error_title": "SiopV2-verzoek ophalen" } diff --git a/packages/oid4vci-holder/src/machines/firstPartyMachine.ts b/packages/oid4vci-holder/src/machines/firstPartyMachine.ts new file mode 100644 index 000000000..40320a443 --- /dev/null +++ b/packages/oid4vci-holder/src/machines/firstPartyMachine.ts @@ -0,0 +1,287 @@ +import { assign, createMachine, DoneInvokeEvent, interpret } from 'xstate' +import { + AuthorizationChallengeCodeResponse, + AuthorizationChallengeError, + AuthorizationChallengeErrorResponse +} from '@sphereon/oid4vci-common' +import { DidAuthConfig } from '@sphereon/ssi-sdk.data-store' +import { CreateConfigResult } from '@sphereon/ssi-sdk.siopv2-oid4vp-op-auth' +import { + createConfig, + getSiopRequest, + sendAuthorizationChallengeRequest, + sendAuthorizationResponse +} from '../services/FirstPartyMachineServices' +import { translate } from '../localization/Localization' +import { ErrorDetails } from '../types/IOID4VCIHolder' +import { + CreateConfigArgs, + CreateFirstPartyMachineOpts, + FirstPartyMachineContext, + FirstPartyMachineEvents, + FirstPartyMachineEventTypes, + FirstPartyMachineInterpreter, + FirstPartyMachineServices, + FirstPartyMachineState, + FirstPartyMachineStatesConfig, + FirstPartyMachineStateTypes, + FirstPartyMachineServiceDefinitions, + FirstPartyStateMachine, + GetSiopRequestArgs, + InstanceFirstPartyMachineOpts, + SiopV2AuthorizationRequestData, + SendAuthorizationResponseArgs, + FirstPartySelectCredentialsEvent +} from '../types/FirstPartyMachine' + +const firstPartyMachineStates: FirstPartyMachineStatesConfig = { + [FirstPartyMachineStateTypes.sendAuthorizationChallengeRequest]: { + id: FirstPartyMachineStateTypes.sendAuthorizationChallengeRequest, + invoke: { + src: FirstPartyMachineServices.sendAuthorizationChallengeRequest, + onDone: { + target: FirstPartyMachineStateTypes.done, + actions: assign({ + authorizationCodeResponse: (_ctx: FirstPartyMachineContext, _event: DoneInvokeEvent) => _event.data + }) + }, + onError: [ + { + target: FirstPartyMachineStateTypes.createConfig, + cond: (_ctx: FirstPartyMachineContext, _event: DoneInvokeEvent): boolean => _event.data.error === AuthorizationChallengeError.insufficient_authorization, + actions: assign({ + authSession: (_ctx: FirstPartyMachineContext, _event: DoneInvokeEvent) => _event.data.auth_session, + presentationUri: (_ctx: FirstPartyMachineContext, _event: DoneInvokeEvent) => _event.data.presentation, + }), + }, + { + target: FirstPartyMachineStateTypes.error, + actions: assign({ + error: (_ctx: FirstPartyMachineContext, _event: DoneInvokeEvent): ErrorDetails => ({ + title: translate('oid4vci_machine_send_authorization_challenge_request_error_title'), + message: _event.data.message, + stack: _event.data.stack, + }), + }), + } + ], + } + }, + [FirstPartyMachineStateTypes.createConfig]: { + id: FirstPartyMachineStateTypes.createConfig, + invoke: { + src: FirstPartyMachineServices.createConfig, + onDone: { + target: FirstPartyMachineStateTypes.getSiopRequest, + actions: assign({ + didAuthConfig: (_ctx: FirstPartyMachineContext, _event: DoneInvokeEvent) => _event.data, + }), + }, + onError: { + target: FirstPartyMachineStateTypes.error, + actions: assign({ + error: (_ctx: FirstPartyMachineContext, _event: DoneInvokeEvent): ErrorDetails => ({ + title: translate('oid4vci_machine_create_config_error_title'), + message: _event.data.message, + stack: _event.data.stack, + }), + }), + }, + }, + }, + [FirstPartyMachineStateTypes.getSiopRequest]: { + id: FirstPartyMachineStateTypes.getSiopRequest, + invoke: { + src: FirstPartyMachineServices.getSiopRequest, + onDone: { + target: FirstPartyMachineStateTypes.selectCredentials, + actions: assign({ + authorizationRequestData: (_ctx: FirstPartyMachineContext, _event: DoneInvokeEvent) => _event.data, + }), + }, + onError: { + target: FirstPartyMachineStateTypes.error, + actions: assign({ + error: (_ctx: FirstPartyMachineContext, _event: DoneInvokeEvent): ErrorDetails => ({ + title: translate('siopV2_machine_get_request_error_title'), + message: _event.data.message, + stack: _event.data.stack, + }), + }), + }, + }, + }, + [FirstPartyMachineStateTypes.selectCredentials]: { + id: FirstPartyMachineStateTypes.selectCredentials, + on: { + [FirstPartyMachineEvents.SET_SELECTED_CREDENTIALS]: { + actions: assign({selectedCredentials: (_ctx: FirstPartyMachineContext, _event: FirstPartySelectCredentialsEvent) => _event.data}), + }, + [FirstPartyMachineEvents.NEXT]: { + target: FirstPartyMachineStateTypes.sendAuthorizationResponse, + }, + [FirstPartyMachineEvents.DECLINE]: { + target: FirstPartyMachineStateTypes.declined, + }, + [FirstPartyMachineEvents.PREVIOUS]: { + target: FirstPartyMachineStateTypes.aborted, + }, + }, + }, + [FirstPartyMachineStateTypes.sendAuthorizationResponse]: { + id: FirstPartyMachineStateTypes.sendAuthorizationResponse, + invoke: { + src: FirstPartyMachineServices.sendAuthorizationResponse, + onDone: { + target: FirstPartyMachineStateTypes.sendAuthorizationChallengeRequest, + actions: assign({ + presentationDuringIssuanceSession: (_ctx: FirstPartyMachineContext, _event: DoneInvokeEvent) => _event.data, + }), + }, + onError: { + target: FirstPartyMachineStateTypes.error, + actions: assign({ + error: (_ctx: FirstPartyMachineContext, _event: DoneInvokeEvent): ErrorDetails => ({ + title: translate('oid4vci_machine_get_request_error_title'), + message: _event.data.message, + stack: _event.data.stack, + }), + }), + }, + } + }, + [FirstPartyMachineStateTypes.aborted]: { + id: FirstPartyMachineStateTypes.aborted, + type: 'final' + }, + [FirstPartyMachineStateTypes.declined]: { + id: FirstPartyMachineStateTypes.declined, + type: 'final' + }, + [FirstPartyMachineStateTypes.error]: { + id: FirstPartyMachineStateTypes.error, + type: 'final', + }, + [FirstPartyMachineStateTypes.done]: { + id: FirstPartyMachineStateTypes.done, + type: 'final', + } +} + +const createFirstPartyActivationMachine = (opts: CreateFirstPartyMachineOpts): FirstPartyStateMachine => { + const initialContext: FirstPartyMachineContext = { + openID4VCIClientState: opts.openID4VCIClientState, + contact: opts.contact, + selectedCredentials: [], + }; + + return createMachine( + { + id: opts?.machineId ?? 'FirstParty', + predictableActionArguments: true, + initial: FirstPartyMachineStateTypes.sendAuthorizationChallengeRequest, + context: initialContext, + states: firstPartyMachineStates, + schema: { + events: {} as FirstPartyMachineEventTypes, + services: {} as { + [FirstPartyMachineServices.sendAuthorizationChallengeRequest]: { + data: void + }, + [FirstPartyMachineServices.createConfig]: { + data: CreateConfigResult + }, + [FirstPartyMachineServices.getSiopRequest]: { + data: SiopV2AuthorizationRequestData + }, + [FirstPartyMachineServices.sendAuthorizationResponse]: { + data: string + } + } + } + } + ); +}; + +export class FirstPartyMachine { + private static _instance: FirstPartyMachineInterpreter | undefined; + + static hasInstance(): boolean { + return FirstPartyMachine._instance !== undefined; + } + + static get instance(): FirstPartyMachineInterpreter { + if (!FirstPartyMachine._instance) { + throw Error('Please initialize ESIMActivation machine first'); + } + return FirstPartyMachine._instance; + } + + static clearInstance(opts: {stop: boolean}) { + const {stop} = opts; + if (FirstPartyMachine.hasInstance()) { + if (stop) { + FirstPartyMachine.stopInstance(); + } + } + FirstPartyMachine._instance = undefined; + } + + static stopInstance(): void { + if (!FirstPartyMachine.hasInstance()) { + return; + } + FirstPartyMachine.instance.stop(); + FirstPartyMachine._instance = undefined; + } + + public static newInstance(opts: InstanceFirstPartyMachineOpts): FirstPartyMachineInterpreter { + const { agentContext } = opts + const services: FirstPartyMachineServiceDefinitions = { + [FirstPartyMachineServices.sendAuthorizationChallengeRequest]: sendAuthorizationChallengeRequest, + [FirstPartyMachineServices.createConfig]: (args: CreateConfigArgs) => createConfig(args, agentContext), + [FirstPartyMachineServices.getSiopRequest]: (args: GetSiopRequestArgs) => getSiopRequest(args, agentContext), + [FirstPartyMachineServices.sendAuthorizationResponse]: (args: SendAuthorizationResponseArgs) => sendAuthorizationResponse(args, agentContext), + } + + const newInst: FirstPartyMachineInterpreter = interpret( + createFirstPartyActivationMachine(opts).withConfig({ + services: { + ...services, + ...opts?.services, + }, + guards: { + ...opts?.guards, + }, + }), + ); + + if (typeof opts?.subscription === 'function') { + newInst.onTransition(opts.subscription); + } + + if (opts?.requireCustomNavigationHook !== true) { + newInst.onTransition((snapshot: FirstPartyMachineState): void => { + if (opts?.stateNavigationListener) { + void opts.stateNavigationListener(newInst, snapshot) + } + }); + } + + return newInst; + } + + static getInstance( + opts: InstanceFirstPartyMachineOpts & { + requireExisting?: boolean; + }, + ): FirstPartyMachineInterpreter { + if (!FirstPartyMachine._instance) { + if (opts?.requireExisting === true) { + throw Error(`Existing FirstPartyMachine instance requested, but none was created at this point!`); + } + FirstPartyMachine._instance = FirstPartyMachine.newInstance(opts); + } + return FirstPartyMachine._instance; + } +} diff --git a/packages/oid4vci-holder/src/machine/oid4vciMachine.ts b/packages/oid4vci-holder/src/machines/oid4vciMachine.ts similarity index 91% rename from packages/oid4vci-holder/src/machine/oid4vciMachine.ts rename to packages/oid4vci-holder/src/machines/oid4vciMachine.ts index 11854f303..bf5c21d76 100644 --- a/packages/oid4vci-holder/src/machine/oid4vciMachine.ts +++ b/packages/oid4vci-holder/src/machines/oid4vciMachine.ts @@ -1,4 +1,8 @@ -import { AuthzFlowType, toAuthorizationResponsePayload } from '@sphereon/oid4vci-common' +import { + AuthorizationChallengeCodeResponse, + AuthzFlowType, + toAuthorizationResponsePayload +} from '@sphereon/oid4vci-common' import { IBasicIssuerLocaleBranding, Identity, IIssuerLocaleBranding, Party } from '@sphereon/ssi-sdk.data-store' import { assign, createMachine, DoneInvokeEvent, interpret } from 'xstate' import { translate } from '../localization/Localization' @@ -29,6 +33,7 @@ import { SetAuthorizationCodeURLEvent, VerificationCodeEvent, } from '../types/IOID4VCIHolder' +import { FirstPartyMachineStateTypes } from '../types/FirstPartyMachine' const oid4vciHasNoContactGuard = (_ctx: OID4VCIMachineContext, _event: OID4VCIMachineEventTypes): boolean => { const { contact } = _ctx @@ -117,6 +122,10 @@ const oid4vciHasAuthorizationResponse = (ctx: OID4VCIMachineContext, _event: OID return !!ctx.openID4VCIClientState?.authorizationCodeResponse } +const oid4vciIsFirstPartyApplication = (ctx: OID4VCIMachineContext, _event: OID4VCIMachineEventTypes): boolean => { + return !!ctx.serverMetadata?.authorization_challenge_endpoint +} + const createOID4VCIMachine = (opts?: CreateOID4VCIMachineOpts): OID4VCIStateMachine => { const initialContext: OID4VCIMachineContext = { // TODO WAL-671 we need to store the data from OpenIdProvider here in the context and make sure we can restart the machine with it and init the OpenIdProvider @@ -153,7 +162,8 @@ const createOID4VCIMachine = (opts?: CreateOID4VCIMachineOpts): OID4VCIStateMach | { type: OID4VCIMachineGuards.hasSelectedCredentialsGuard } | { type: OID4VCIMachineGuards.hasAuthorizationResponse } | { type: OID4VCIMachineGuards.isOIDFOriginGuard } - | { type: OID4VCIMachineGuards.contactHasLowTrustGuard }, + | { type: OID4VCIMachineGuards.contactHasLowTrustGuard } + | { type: OID4VCIMachineGuards.isFirstPartyApplication }, services: {} as { [OID4VCIMachineServices.start]: { data: StartResult @@ -188,6 +198,9 @@ const createOID4VCIMachine = (opts?: CreateOID4VCIMachineOpts): OID4VCIStateMach [OID4VCIMachineServices.getIssuerBranding]: { data: Array } + [OID4VCIMachineServices.startFirstPartApplicationFlow]: { + data: void + } }, }, context: initialContext, @@ -332,6 +345,10 @@ const createOID4VCIMachine = (opts?: CreateOID4VCIMachineOpts): OID4VCIStateMach target: OID4VCIMachineStates.selectCredentials, cond: OID4VCIMachineGuards.credentialsToSelectRequiredGuard, }, + { + target: OID4VCIMachineStates.startFirstPartApplicationFlow, + cond: OID4VCIMachineGuards.isFirstPartyApplication, + }, { target: OID4VCIMachineStates.initiateAuthorizationRequest, cond: OID4VCIMachineGuards.requireAuthorizationGuard, @@ -422,6 +439,10 @@ const createOID4VCIMachine = (opts?: CreateOID4VCIMachineOpts): OID4VCIStateMach target: OID4VCIMachineStates.selectCredentials, cond: OID4VCIMachineGuards.credentialsToSelectRequiredGuard, }, + { + target: OID4VCIMachineStates.startFirstPartApplicationFlow, + cond: OID4VCIMachineGuards.isFirstPartyApplication, + }, { target: OID4VCIMachineStates.initiateAuthorizationRequest, cond: OID4VCIMachineGuards.requireAuthorizationGuard, @@ -435,6 +456,41 @@ const createOID4VCIMachine = (opts?: CreateOID4VCIMachineOpts): OID4VCIStateMach }, ], }, + [OID4VCIMachineStates.startFirstPartApplicationFlow] :{ + id: OID4VCIMachineStates.startFirstPartApplicationFlow, + invoke: { + src: OID4VCIMachineServices.startFirstPartApplicationFlow, + onDone: [ + { + target: OID4VCIMachineStates.aborted, + cond: (_ctx: OID4VCIMachineContext, _event: DoneInvokeEvent): boolean => _event.data === FirstPartyMachineStateTypes.aborted, + }, + { + target: OID4VCIMachineStates.declined, + cond: (_ctx: OID4VCIMachineContext, _event: DoneInvokeEvent): boolean => _event.data === FirstPartyMachineStateTypes.declined, + }, + { + target: OID4VCIMachineStates.getCredentials, + actions: assign({ + openID4VCIClientState: (_ctx: OID4VCIMachineContext, _event: DoneInvokeEvent) => { + const authorizationCodeResponse = toAuthorizationResponsePayload(_event.data) + return { ..._ctx.openID4VCIClientState!, authorizationCodeResponse } + } + }) + } + ], + onError: { + target: OID4VCIMachineStates.handleError, + actions: assign({ + error: (_ctx: OID4VCIMachineContext, _event: DoneInvokeEvent): ErrorDetails => ({ + title: _event.data.title ?? translate('oid4vci_machine_first_party_error_title'), + message: _event.data.message, + stack: _event.data.stack, + }), + }), + }, + }, + }, [OID4VCIMachineStates.selectCredentials]: { id: OID4VCIMachineStates.selectCredentials, on: { @@ -453,6 +509,10 @@ const createOID4VCIMachine = (opts?: CreateOID4VCIMachineOpts): OID4VCIStateMach [OID4VCIMachineStates.transitionFromSelectingCredentials]: { id: OID4VCIMachineStates.transitionFromSelectingCredentials, always: [ + { + target: OID4VCIMachineStates.startFirstPartApplicationFlow, + cond: OID4VCIMachineGuards.isFirstPartyApplication, + }, { target: OID4VCIMachineStates.verifyPin, cond: OID4VCIMachineGuards.requirePinGuard, @@ -726,6 +786,7 @@ export class OID4VCIMachine { oid4vciHasAuthorizationResponse, oid4vciIsOIDFOriginGuard, oid4vciContactHasLowTrustGuard, + oid4vciIsFirstPartyApplication, ...opts?.guards, }, }), @@ -737,7 +798,7 @@ export class OID4VCIMachine { if (opts?.requireCustomNavigationHook !== true) { if (typeof opts?.stateNavigationListener === 'function') { interpreter.onTransition((snapshot: OID4VCIMachineState): void => { - if (opts?.stateNavigationListener !== undefined) { + if (opts?.stateNavigationListener) { opts.stateNavigationListener(interpreter, snapshot) } }) diff --git a/packages/oid4vci-holder/src/agent/OIDC4VCIBrandingMapper.ts b/packages/oid4vci-holder/src/mappers/OIDC4VCIBrandingMapper.ts similarity index 100% rename from packages/oid4vci-holder/src/agent/OIDC4VCIBrandingMapper.ts rename to packages/oid4vci-holder/src/mappers/OIDC4VCIBrandingMapper.ts diff --git a/packages/oid4vci-holder/src/services/FirstPartyMachineServices.ts b/packages/oid4vci-holder/src/services/FirstPartyMachineServices.ts new file mode 100644 index 000000000..b486bda5e --- /dev/null +++ b/packages/oid4vci-holder/src/services/FirstPartyMachineServices.ts @@ -0,0 +1,63 @@ +import { OpenID4VCIClient } from '@sphereon/oid4vci-client' +import { AuthorizationChallengeValidationResponse } from '@sphereon/ssi-sdk.siopv2-oid4vp-common' +import { AuthorizationChallengeCodeResponse } from '@sphereon/oid4vci-common' +import { CreateConfigResult } from '@sphereon/ssi-sdk.siopv2-oid4vp-op-auth' +import { v4 as uuidv4 } from 'uuid' +import { RequiredContext } from '../types/IOID4VCIHolder' +import { + CreateConfigArgs, + GetSiopRequestArgs, + SendAuthorizationChallengeRequestArgs, + SendAuthorizationResponseArgs, + SiopV2AuthorizationRequestData +} from '../types/FirstPartyMachine' + +export const sendAuthorizationChallengeRequest = async (args: SendAuthorizationChallengeRequestArgs): Promise => { + const { openID4VCIClientState, authSession, presentationDuringIssuanceSession } = args + + const oid4vciClient = await OpenID4VCIClient.fromState({ state: openID4VCIClientState }) + return oid4vciClient.acquireAuthorizationChallengeCode({ + clientId: oid4vciClient.clientId ?? uuidv4(), + ...(authSession && { authSession }), + ...(!authSession && openID4VCIClientState.credentialOffer?.preAuthorizedCode && { issuerState: openID4VCIClientState.credentialOffer?.preAuthorizedCode }), + ...(!authSession && openID4VCIClientState.credentialOffer?.issuerState && { issuerState: openID4VCIClientState.credentialOffer?.issuerState }), + ...(presentationDuringIssuanceSession && { presentationDuringIssuanceSession }) + }) +} + +export const createConfig = async (args: CreateConfigArgs, context: RequiredContext): Promise => { + const { presentationUri } = args; + + if (!presentationUri) { + return Promise.reject(Error('Missing presentation uri in context')); + } + + return context.agent.siopCreateConfig({ url: presentationUri }) +}; + +export const getSiopRequest = async (args: GetSiopRequestArgs, context: RequiredContext): Promise => { + const {didAuthConfig, presentationUri} = args; + + if (presentationUri === undefined) { + return Promise.reject(Error('Missing presentation uri in context')); + } + + if (didAuthConfig === undefined) { + return Promise.reject(Error('Missing did auth config in context')); + } + + return context.agent.siopGetSiopRequest({ didAuthConfig, url: presentationUri }) +} + +export const sendAuthorizationResponse = async (args: SendAuthorizationResponseArgs, context: RequiredContext): Promise => { + const { didAuthConfig, authorizationRequestData, selectedCredentials } = args + + const responseData = await context.agent.siopSendResponse({ + authorizationRequestData, + selectedCredentials, + didAuthConfig, + isFirstParty: true + }) + + return (responseData.body).presentation_during_issuance_session +} diff --git a/packages/oid4vci-holder/src/agent/OID4VCIHolderService.ts b/packages/oid4vci-holder/src/services/OID4VCIHolderService.ts similarity index 93% rename from packages/oid4vci-holder/src/agent/OID4VCIHolderService.ts rename to packages/oid4vci-holder/src/services/OID4VCIHolderService.ts index 4ac379450..f480f4cf6 100644 --- a/packages/oid4vci-holder/src/agent/OID4VCIHolderService.ts +++ b/packages/oid4vci-holder/src/services/OID4VCIHolderService.ts @@ -10,6 +10,7 @@ import { getTypesFromObject, MetadataDisplay, OpenId4VCIVersion, + AuthorizationChallengeCodeResponse } from '@sphereon/oid4vci-common' import { KeyUse } from '@sphereon/ssi-sdk-ext.did-resolver-jwk' import { getOrCreatePrimaryIdentifier, SupportedDidMethodEnum } from '@sphereon/ssi-sdk-ext.did-utils' @@ -56,12 +57,16 @@ import { SelectAppLocaleBrandingArgs, VerificationResult, VerifyCredentialToAcceptArgs, + StartFirstPartApplicationMachine, + RequiredContext } from '../types/IOID4VCIHolder' import { oid4vciGetCredentialBrandingFrom, sdJwtGetCredentialBrandingFrom, issuerLocaleBrandingFrom -} from './OIDC4VCIBrandingMapper' +} from '../mappers/OIDC4VCIBrandingMapper' +import { FirstPartyMachine } from '../machines/firstPartyMachine' +import { FirstPartyMachineState, FirstPartyMachineStateTypes } from '../types/FirstPartyMachine' export const getCredentialBranding = async (args: GetCredentialBrandingArgs): Promise>> => { const { credentialsSupported, context } = args @@ -615,3 +620,45 @@ export const getIssuanceCryptoSuite = async (opts: GetIssuanceCryptoSuiteArgs): return Promise.reject(Error(`Credential format '${credentialSupported.format}' not supported`)) } } + +export const startFirstPartApplicationMachine = async (args: StartFirstPartApplicationMachine, context: RequiredContext): Promise => { + const { openID4VCIClientState, stateNavigationListener, contact } = args + + if (!openID4VCIClientState) { + return Promise.reject(Error('Missing openID4VCI client state in context')) + } + + if (!contact) { + return Promise.reject(Error('Missing contact in context')) + } + + const firstPartyMachineInstance = FirstPartyMachine.newInstance({ + openID4VCIClientState, + contact, + agentContext: context, + stateNavigationListener + }); + + return new Promise((resolve, reject) => { + try { + firstPartyMachineInstance.onTransition((state: FirstPartyMachineState) => { + if (state.matches(FirstPartyMachineStateTypes.done)) { + const authorizationCodeResponse = state.context.authorizationCodeResponse + if (!authorizationCodeResponse) { + reject(Error('No authorizationCodeResponse acquired')); + } + resolve(authorizationCodeResponse!); + } else if (state.matches(FirstPartyMachineStateTypes.aborted)) { + resolve(FirstPartyMachineStateTypes.aborted); + } else if (state.matches(FirstPartyMachineStateTypes.declined)) { + resolve(FirstPartyMachineStateTypes.declined); + } else if (state.matches(FirstPartyMachineStateTypes.error)) { + reject(state.context.error); + } + }) + firstPartyMachineInstance.start(); + } catch (error) { + reject(error); + } + }); +}; diff --git a/packages/oid4vci-holder/src/types/FirstPartyMachine.ts b/packages/oid4vci-holder/src/types/FirstPartyMachine.ts new file mode 100644 index 000000000..18a7ccc28 --- /dev/null +++ b/packages/oid4vci-holder/src/types/FirstPartyMachine.ts @@ -0,0 +1,169 @@ +import { + BaseActionObject, + Interpreter, + ResolveTypegenMeta, + ServiceMap, State, + StateMachine, + StatesConfig, + TypegenDisabled +} from 'xstate' +import { OpenID4VCIClientState } from '@sphereon/oid4vci-client' +import { DidAuthConfig, Party } from '@sphereon/ssi-sdk.data-store' +import { + PresentationDefinitionWithLocation, + RPRegistrationMetadataPayload +} from '@sphereon/did-auth-siop' +import { UniqueDigitalCredential } from '@sphereon/ssi-sdk.credential-store' +import { AuthorizationChallengeCodeResponse } from '@sphereon/oid4vci-common' +import { IIdentifier } from '@veramo/core' +import { ErrorDetails, RequiredContext } from './IOID4VCIHolder' + +export enum FirstPartyMachineStateTypes { + sendAuthorizationChallengeRequest = 'sendAuthorizationChallengeRequest', + sendAuthorizationResponse = 'sendAuthorizationResponse', + selectCredentials = 'selectCredentials', + createConfig = 'createConfig', + getSiopRequest = 'getSiopRequest', + error = 'error', + done = 'done', + aborted = 'aborted', + declined = 'declined' +} + +export enum FirstPartyMachineServices { + sendAuthorizationChallengeRequest = 'sendAuthorizationChallengeRequest', + sendAuthorizationResponse = 'sendAuthorizationResponse', + createConfig = 'createConfig', + getSiopRequest = 'getSiopRequest', +} + +export type FirstPartyMachineStates = Record; + +export type FirstPartyMachineContext = { + openID4VCIClientState: OpenID4VCIClientState + selectedCredentials: Array + contact: Party + authSession?: string + presentationUri?: string + identifier?: IIdentifier + didAuthConfig?: Omit + authorizationRequestData?: SiopV2AuthorizationRequestData + presentationDuringIssuanceSession?: string + authorizationCodeResponse?: AuthorizationChallengeCodeResponse + error?: ErrorDetails; +}; + +export enum FirstPartyMachineEvents { + NEXT = 'NEXT', + PREVIOUS = 'PREVIOUS', + DECLINE = 'DECLINE', + SET_SELECTED_CREDENTIALS = 'SET_SELECTED_CREDENTIALS' +} + +export type FirstPartyNextEvent = {type: FirstPartyMachineEvents.NEXT}; +export type FirstPartyPreviousEvent = {type: FirstPartyMachineEvents.PREVIOUS}; +export type FirstPartyDeclineEvent = {type: FirstPartyMachineEvents.DECLINE}; +export type FirstPartySelectCredentialsEvent = { + type: FirstPartyMachineEvents.SET_SELECTED_CREDENTIALS; + data: Array; +}; + +export type FirstPartyMachineEventTypes = + FirstPartyNextEvent | + FirstPartyPreviousEvent | + FirstPartyDeclineEvent | + FirstPartySelectCredentialsEvent + +export type FirstPartyMachineStatesConfig = StatesConfig< + FirstPartyMachineContext, + { + states: FirstPartyMachineStates; + }, + FirstPartyMachineEventTypes, + any +>; + +export type CreateFirstPartyMachineOpts = { + openID4VCIClientState: OpenID4VCIClientState + contact: Party + agentContext: RequiredContext + machineId?: string; +}; + +export type FirstPartyStateMachine = StateMachine< + FirstPartyMachineContext, + any, + FirstPartyMachineEventTypes, + { + value: any; + context: FirstPartyMachineContext; + }, + BaseActionObject, + ServiceMap, + ResolveTypegenMeta +>; + +export type FirstPartyMachineInterpreter = Interpreter< + FirstPartyMachineContext, + any, + FirstPartyMachineEventTypes, + { + value: any; + context: FirstPartyMachineContext; + }, + any +>; + +export type FirstPartyMachineStateNavigationListener = (firstPartyMachine: FirstPartyMachineInterpreter, state: FirstPartyMachineState, navigation?: any) => Promise + +export type InstanceFirstPartyMachineOpts = { + services?: any; + guards?: any; + subscription?: () => void; + requireCustomNavigationHook?: boolean; + stateNavigationListener?: FirstPartyMachineStateNavigationListener +} & CreateFirstPartyMachineOpts; + +export type FirstPartyMachineState = State< + FirstPartyMachineContext, + FirstPartyMachineEventTypes, + any, + { + value: any; + context: FirstPartyMachineContext; + }, + any +>; + +export type FirstPartyMachineServiceDefinitions = Record< + keyof typeof FirstPartyMachineServices, + (...args: Array) => any +>; + +export type SendAuthorizationChallengeRequestArgs = Pick + +export type SendAuthorizationResponseArgs = Pick + +export type CreateConfigArgs = Pick + +export type GetSiopRequestArgs = Pick + +export type SiopV2AuthorizationRequestData = { + correlationId: string; + registrationMetadataPayload: RPRegistrationMetadataPayload; + issuer?: string; + name?: string; + uri?: URL; + clientIdScheme?: string; + clientId?: string; + entityId?: string; + presentationDefinitions?: PresentationDefinitionWithLocation[]; +}; + +export type FirstPartyMachineNavigationArgs = { + firstPartyMachine: FirstPartyMachineInterpreter + state: FirstPartyMachineState + navigation: any + onNext?: () => void + onBack?: () => void +} diff --git a/packages/oid4vci-holder/src/types/IOID4VCIHolder.ts b/packages/oid4vci-holder/src/types/IOID4VCIHolder.ts index e2199aa92..06b6ef1d2 100644 --- a/packages/oid4vci-holder/src/types/IOID4VCIHolder.ts +++ b/packages/oid4vci-holder/src/types/IOID4VCIHolder.ts @@ -37,6 +37,8 @@ import { import { IIssuanceBranding } from '@sphereon/ssi-sdk.issuance-branding' import { ImDLMdoc } from '@sphereon/ssi-sdk.mdl-mdoc' import { ISDJwtPlugin } from '@sphereon/ssi-sdk.sd-jwt' +import { ICredentialValidation, SchemaValidation } from '@sphereon/ssi-sdk.credential-validation' +import { IDidAuthSiopOpAuthenticator } from '@sphereon/ssi-sdk.siopv2-oid4vp-op-auth' import { Hasher, IVerifiableCredential, @@ -62,7 +64,7 @@ import { VerificationPolicies, } from '@veramo/core' import { BaseActionObject, Interpreter, ResolveTypegenMeta, ServiceMap, State, StateMachine, TypegenDisabled } from 'xstate' -import { ICredentialValidation, SchemaValidation } from '@sphereon/ssi-sdk.credential-validation' +import { FirstPartyMachineStateNavigationListener } from './FirstPartyMachine' export interface IOID4VCIHolder extends IPluginMethodMap { oid4vciHolderGetIssuerMetadata(args: GetIssuerMetadataArgs, context: RequiredContext): Promise @@ -72,7 +74,7 @@ export interface IOID4VCIHolder extends IPluginMethodMap { oid4vciHolderStart(args: PrepareStartArgs, context: RequiredContext): Promise oid4vciHolderCreateCredentialsToSelectFrom( - args: createCredentialsToSelectFromArgs, + args: CreateCredentialsToSelectFromArgs, context: RequiredContext, ): Promise> @@ -137,14 +139,15 @@ export type GetMachineArgs = { clientOpts?: AuthorizationServerClientOpts didMethodPreferences?: Array issuanceOpt?: Partial - stateNavigationListener?: (oid4vciMachine: OID4VCIMachineInterpreter, state: OID4VCIMachineState, navigation?: any) => Promise + stateNavigationListener?: OID4VCIMachineStateNavigationListener + firstPartyStateNavigationListener?: FirstPartyMachineStateNavigationListener } export type PrepareStartArgs = Pick< OID4VCIMachineContext, 'requestData' | 'authorizationRequestOpts' | 'didMethodPreferences' | 'issuanceOpt' | 'accessTokenOpts' > -export type createCredentialsToSelectFromArgs = Pick< +export type CreateCredentialsToSelectFromArgs = Pick< OID4VCIMachineContext, 'credentialsSupported' | 'credentialBranding' | 'selectedCredentials' | 'locale' | 'openID4VCIClientState' > @@ -170,6 +173,7 @@ export type SendNotificationArgs = Pick< 'credentialsToAccept' | 'serverMetadata' | 'credentialsSupported' | 'openID4VCIClientState' > & { notificationRequest?: NotificationRequest; stored: boolean } export type GetFederationTrustArgs = Pick +export type StartFirstPartApplicationMachine = Pick & { stateNavigationListener?: FirstPartyMachineStateNavigationListener } export enum OID4VCIHolderEvent { CONTACT_IDENTITY_CREATED = 'contact_identity_created', @@ -245,6 +249,7 @@ export enum OID4VCIMachineStates { storeIssuerBranding = 'storeIssuerBranding', addIssuerBrandingAfterIdentity = 'addIssuerBrandingAfterIdentity', transitionFromContactSetup = 'transitionFromContactSetup', + startFirstPartApplicationFlow = 'startFirstPartApplicationFlow', selectCredentials = 'selectCredentials', transitionFromSelectingCredentials = 'transitionFromSelectingCredentials', verifyPin = 'verifyPin', @@ -314,6 +319,8 @@ export type CreateOID4VCIMachineOpts = { issuanceOpt?: IssuanceOpts } +export type OID4VCIMachineStateNavigationListener = (oid4vciMachine: OID4VCIMachineInterpreter, state: OID4VCIMachineState, navigation?: any) => Promise + export type OID4VCIMachineInstanceOpts = { services?: any guards?: any @@ -322,7 +329,8 @@ export type OID4VCIMachineInstanceOpts = { authorizationRequestOpts?: AuthorizationRequestOpts didMethodPreferences?: Array issuanceOpt?: IssuanceOpts // restrict the issuance to these opts - stateNavigationListener?: (oid4vciMachine: OID4VCIMachineInterpreter, state: OID4VCIMachineState, navigation?: any) => Promise + stateNavigationListener?: OID4VCIMachineStateNavigationListener + firstPartyStateNavigationListener?: FirstPartyMachineStateNavigationListener } & CreateOID4VCIMachineOpts export type OID4VCIProviderProps = { @@ -370,6 +378,7 @@ export enum OID4VCIMachineGuards { hasSelectedCredentialsGuard = 'oid4vciHasSelectedCredentialsGuard', isOIDFOriginGuard = 'oid4vciIsOIDFOriginGuard', contactHasLowTrustGuard = 'oid4vciContactHasLowTrustGuard', + isFirstPartyApplication = 'oid4vciIsFirstPartyApplication' } export enum OID4VCIMachineServices { @@ -380,14 +389,19 @@ export enum OID4VCIMachineServices { createCredentialsToSelectFrom = 'createCredentialsToSelectFrom', getIssuerBranding = 'getIssuerBranding', storeIssuerBranding = 'storeIssuerBranding', - createCredentialSelection = 'createCredentialSelection', getCredentials = 'getCredentials', assertValidCredentials = 'assertValidCredentials', storeCredentialBranding = 'storeCredentialBranding', sendNotification = 'sendNotification', storeCredentials = 'storeCredentials', + startFirstPartApplicationFlow = 'startFirstPartApplicationFlow' } +export type OID4VCIMachineServiceDefinitions = Record< + keyof typeof OID4VCIMachineServices, + (...args: Array) => any +>; + export type NextEvent = { type: OID4VCIMachineEvents.NEXT } export type PreviousEvent = { type: OID4VCIMachineEvents.PREVIOUS } export type DeclineEvent = { type: OID4VCIMachineEvents.DECLINE } @@ -397,14 +411,9 @@ export type VerificationCodeEvent = { type: OID4VCIMachineEvents.SET_VERIFICATIO export type ContactConsentEvent = { type: OID4VCIMachineEvents.SET_CONTACT_CONSENT; data: boolean } export type ContactAliasEvent = { type: OID4VCIMachineEvents.SET_CONTACT_ALIAS; data: string } export type SetAuthorizationCodeURLEvent = { type: OID4VCIMachineEvents.SET_AUTHORIZATION_CODE_URL; data: string } -export type InvokeAuthorizationRequestEvent = { - type: OID4VCIMachineEvents.INVOKED_AUTHORIZATION_CODE_REQUEST - data: string -} -export type AuthorizationResponseEvent = { - type: OID4VCIMachineEvents.PROVIDE_AUTHORIZATION_CODE_RESPONSE - data: string | AuthorizationResponse -} +export type InvokeAuthorizationRequestEvent = { type: OID4VCIMachineEvents.INVOKED_AUTHORIZATION_CODE_REQUEST, data: string } +export type AuthorizationResponseEvent = { type: OID4VCIMachineEvents.PROVIDE_AUTHORIZATION_CODE_RESPONSE, data: string | AuthorizationResponse } + export type OID4VCIMachineEventTypes = | NextEvent | PreviousEvent @@ -633,22 +642,6 @@ export interface VerifyCredentialArgs { [x: string]: any } -export type RequiredContext = IAgentContext< - IIssuanceBranding & - IContactManager & - ICredentialValidation & - ICredentialVerifier & - ICredentialIssuer & - ICredentialStore & - IIdentifierResolution & - IJwtService & - IDIDManager & - IResolver & - IKeyManager & - ISDJwtPlugin & - ImDLMdoc -> - export type IssuerType = 'RootTAO' | 'TAO' | 'TI' | 'Revoked or Undefined' export type VerifyEBSICredentialIssuerArgs = { @@ -724,3 +717,20 @@ export type DynamicRegistrationClientMetadataDisplay = Pick< > export type DidAgents = TAgent + +export type RequiredContext = IAgentContext< + IIssuanceBranding & + IContactManager & + ICredentialValidation & + ICredentialVerifier & + ICredentialIssuer & + ICredentialStore & + IIdentifierResolution & + IJwtService & + IDIDManager & + IResolver & + IKeyManager & + ISDJwtPlugin & + ImDLMdoc & + IDidAuthSiopOpAuthenticator +> diff --git a/packages/oid4vci-holder/tsconfig.json b/packages/oid4vci-holder/tsconfig.json index a3efa7e7d..4ada108e9 100644 --- a/packages/oid4vci-holder/tsconfig.json +++ b/packages/oid4vci-holder/tsconfig.json @@ -40,6 +40,9 @@ }, { "path": "../oidf-client" + }, + { + "path": "../siopv2-oid4vp-op-auth" } ] } diff --git a/packages/oid4vci-issuer-rest-api/package.json b/packages/oid4vci-issuer-rest-api/package.json index 9dbeff277..b3cbf2575 100644 --- a/packages/oid4vci-issuer-rest-api/package.json +++ b/packages/oid4vci-issuer-rest-api/package.json @@ -11,9 +11,9 @@ "start:dev": "ts-node __tests__/RestAPI.ts" }, "dependencies": { - "@sphereon/oid4vci-common": "0.16.1-next.233", - "@sphereon/oid4vci-issuer": "0.16.1-next.233", - "@sphereon/oid4vci-issuer-server": "0.16.1-next.233", + "@sphereon/oid4vci-common": "0.16.1-next.339", + "@sphereon/oid4vci-issuer": "0.16.1-next.339", + "@sphereon/oid4vci-issuer-server": "0.16.1-next.339", "@sphereon/ssi-express-support": "workspace:*", "@sphereon/ssi-sdk-ext.identifier-resolution": "0.27.0", "@sphereon/ssi-sdk.kv-store-temp": "workspace:*", diff --git a/packages/oid4vci-issuer-rest-api/src/OID4VCIRestAPI.ts b/packages/oid4vci-issuer-rest-api/src/OID4VCIRestAPI.ts index 459ef52f0..fd355d789 100644 --- a/packages/oid4vci-issuer-rest-api/src/OID4VCIRestAPI.ts +++ b/packages/oid4vci-issuer-rest-api/src/OID4VCIRestAPI.ts @@ -2,7 +2,13 @@ import { CredentialDataSupplier, VcIssuer } from '@sphereon/oid4vci-issuer' import { OID4VCIServer } from '@sphereon/oid4vci-issuer-server' import { IOID4VCIServerOpts } from '@sphereon/oid4vci-issuer-server' import { ExpressSupport } from '@sphereon/ssi-express-support' -import { getAccessTokenSignerCallback, IIssuerInstanceArgs, IssuerInstance } from '@sphereon/ssi-sdk.oid4vci-issuer' +import { + createAuthRequestUriCallback, + getAccessTokenSignerCallback, + IIssuerInstanceArgs, + IssuerInstance, + createVerifyAuthResponseCallback +} from '@sphereon/ssi-sdk.oid4vci-issuer' import { DIDDocument } from 'did-resolver' import { Express } from 'express' import { IRequiredContext } from './types' @@ -56,6 +62,45 @@ export class OID4VCIRestAPI { args.context, ) } + + if (opts?.endpointOpts.authorizationChallengeOpts?.enabled === true) { + if (!instance.issuerOptions.presentationDefinitionId) { + throw Error( + `Unable to set createAuthRequestUriCallback. No presentationDefinitionId present in issuer options`, + ) + } + + if (typeof opts?.endpointOpts.authorizationChallengeOpts.createAuthRequestUriCallback !== 'function') { + if (!opts.endpointOpts.authorizationChallengeOpts?.createAuthRequestUriEndpointPath) { + throw Error( + `Unable to set createAuthRequestUriCallback. No createAuthRequestUriEndpointPath present in options`, + ) + } + + opts.endpointOpts.authorizationChallengeOpts.createAuthRequestUriCallback = await createAuthRequestUriCallback( + { + path: opts.endpointOpts.authorizationChallengeOpts.createAuthRequestUriEndpointPath, + presentationDefinitionId: instance.issuerOptions.presentationDefinitionId + } + ) + } + + if (typeof opts?.endpointOpts.authorizationChallengeOpts?.verifyAuthResponseCallback !== 'function') { + if (!opts.endpointOpts.authorizationChallengeOpts?.verifyAuthResponseEndpointPath) { + throw Error( + `Unable to set verifyAuthResponseCallback. No createAuthRequestUriEndpointPath present in options`, + ) + } + + opts.endpointOpts.authorizationChallengeOpts.verifyAuthResponseCallback = await createVerifyAuthResponseCallback( + { + path: opts.endpointOpts.authorizationChallengeOpts.verifyAuthResponseEndpointPath, + presentationDefinitionId: instance.issuerOptions.presentationDefinitionId + } + ) + } + } + return new OID4VCIRestAPI({ context, issuerInstanceArgs, expressSupport, opts, instance, issuer }) } diff --git a/packages/oid4vci-issuer-rest-client/package.json b/packages/oid4vci-issuer-rest-client/package.json index 3805474f3..413e719aa 100644 --- a/packages/oid4vci-issuer-rest-client/package.json +++ b/packages/oid4vci-issuer-rest-client/package.json @@ -16,7 +16,7 @@ "generate-plugin-schema": "ts-node ../../packages/dev/bin/sphereon.js dev generate-plugin-schema" }, "dependencies": { - "@sphereon/oid4vci-common": "0.16.1-next.233", + "@sphereon/oid4vci-common": "0.16.1-next.339", "@sphereon/ssi-types": "workspace:*", "@veramo/core": "4.2.0", "cross-fetch": "^3.1.8" diff --git a/packages/oid4vci-issuer-store/package.json b/packages/oid4vci-issuer-store/package.json index 917f07f1e..ed597c783 100644 --- a/packages/oid4vci-issuer-store/package.json +++ b/packages/oid4vci-issuer-store/package.json @@ -14,7 +14,7 @@ "build:clean": "tsc --build --clean && tsc --build" }, "dependencies": { - "@sphereon/oid4vci-common": "0.16.1-next.233", + "@sphereon/oid4vci-common": "0.16.1-next.339", "@sphereon/ssi-sdk-ext.did-utils": "0.27.0", "@sphereon/ssi-sdk-ext.identifier-resolution": "0.27.0", "@sphereon/ssi-sdk.kv-store-temp": "workspace:*", diff --git a/packages/oid4vci-issuer-store/src/types/IOID4VCIStore.ts b/packages/oid4vci-issuer-store/src/types/IOID4VCIStore.ts index 892b4848d..53c6018cf 100644 --- a/packages/oid4vci-issuer-store/src/types/IOID4VCIStore.ts +++ b/packages/oid4vci-issuer-store/src/types/IOID4VCIStore.ts @@ -65,6 +65,12 @@ export interface IIssuerOptions { didOpts?: IDIDOptions userPinRequired?: boolean cNonceExpiresIn?: number + + /** + * Used in the callbacks for the first party flow + */ + // FIXME SPRIND-151 we need to start supporting a map with a definition id per credential, we can use the credential offer session to check which credential is being issued and then look it up in this map + presentationDefinitionId?: string } export interface IMetadataOptions { @@ -102,6 +108,7 @@ export interface IMetadataPersistArgs extends Ioid4vciStorePersistArgs, IMetadat export interface IIssuerOptsPersistArgs extends Ioid4vciStorePersistArgs { issuerOpts: IIssuerOptions + endpointOpts: unknown // FIXME these types are all in OID4VC all over the place } export interface Ioid4vciStorePersistArgs { correlationId: string // The credential Issuer to store the metadata for diff --git a/packages/oid4vci-issuer/package.json b/packages/oid4vci-issuer/package.json index e3b9bb0c1..41e9ce3de 100644 --- a/packages/oid4vci-issuer/package.json +++ b/packages/oid4vci-issuer/package.json @@ -14,8 +14,8 @@ "build:clean": "tsc --build --clean && tsc --build" }, "dependencies": { - "@sphereon/oid4vci-common": "0.16.1-next.233", - "@sphereon/oid4vci-issuer": "0.16.1-next.233", + "@sphereon/oid4vci-common": "0.16.1-next.339", + "@sphereon/oid4vci-issuer": "0.16.1-next.339", "@sphereon/ssi-sdk-ext.did-utils": "0.27.0", "@sphereon/ssi-sdk-ext.identifier-resolution": "0.27.0", "@sphereon/ssi-sdk.agent-config": "workspace:*", @@ -35,6 +35,7 @@ "uuid": "^9.0.1" }, "devDependencies": { + "@sphereon/did-auth-siop": "0.16.1-next.339", "@sphereon/did-uni-client": "^0.6.3", "@veramo/did-provider-key": "4.2.0", "@veramo/did-resolver": "4.2.0", diff --git a/packages/oid4vci-issuer/src/functions.ts b/packages/oid4vci-issuer/src/functions.ts index cdd295e06..e2ddfcbef 100644 --- a/packages/oid4vci-issuer/src/functions.ts +++ b/packages/oid4vci-issuer/src/functions.ts @@ -20,6 +20,8 @@ import { createJWT, decodeJWT, JWTVerifyOptions, verifyJWT } from 'did-jwt' import { Resolvable } from 'did-resolver' import { jwtDecode } from 'jwt-decode' import { IIssuerOptions, IRequiredContext } from './types/IOID4VCIIssuer' +import fetch from 'cross-fetch' +import { AuthorizationResponseStateStatus } from '@sphereon/did-auth-siop' export function getJwtVerifyCallback({ verifyOpts }: { verifyOpts?: JWTVerifyOptions }, _context: IRequiredContext) { return async (args: { jwt: string; kid?: string }): Promise> => { @@ -323,3 +325,58 @@ export async function createVciIssuer( ) ).build() } + +export async function createAuthRequestUriCallback(opts: { path: string, presentationDefinitionId: string }): Promise<() => Promise> { + async function authRequestUriCallback(): Promise { + const path = opts.path.replace(':definitionId', opts.presentationDefinitionId) + return fetch(path, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + } + }) + .then(async (response): Promise => { + if (response.status >= 400) { + return Promise.reject(Error(await response.text())) + } else { + const responseData = await response.json(); + + if (!responseData.authRequestURI) { + return Promise.reject(Error('Missing auth request uri in response body')) + } + + return responseData.authRequestURI + } + }) + + } + + return authRequestUriCallback +} + +export async function createVerifyAuthResponseCallback(opts: { path: string, presentationDefinitionId: string }): Promise<(correlationId: string) => Promise> { + async function verifyAuthResponseCallback(correlationId: string): Promise { + return fetch(opts.path, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ definitionId: opts.presentationDefinitionId, correlationId }), + }) + .then(async (response): Promise => { + if (response.status >= 400) { + return Promise.reject(Error(await response.text())) + } else { + const responseData = await response.json(); + + if (!responseData.status) { + return Promise.reject(Error('Missing status in response body')) + } + + return responseData.status === AuthorizationResponseStateStatus.VERIFIED + } + }) + } + + return verifyAuthResponseCallback +} diff --git a/packages/oid4vci-issuer/src/types/IOID4VCIIssuer.ts b/packages/oid4vci-issuer/src/types/IOID4VCIIssuer.ts index 84bd75148..5866485ca 100644 --- a/packages/oid4vci-issuer/src/types/IOID4VCIIssuer.ts +++ b/packages/oid4vci-issuer/src/types/IOID4VCIIssuer.ts @@ -82,6 +82,12 @@ export interface IIssuerOptions { didOpts?: IDIDOptions userPinRequired?: boolean cNonceExpiresIn?: number + + /** + * Used in the callbacks for the first party flow + */ + // FIXME SPRIND-151 we need to start supporting a map with a definition id per credential, we can use the credential offer session to check which credential is being issued and then look it up in this map + presentationDefinitionId?: string } export interface IMetadataOptions { diff --git a/packages/siopv2-oid4vp-common/package.json b/packages/siopv2-oid4vp-common/package.json index e04ed6d53..e084da24e 100644 --- a/packages/siopv2-oid4vp-common/package.json +++ b/packages/siopv2-oid4vp-common/package.json @@ -12,7 +12,7 @@ "access": "public" }, "dependencies": { - "@sphereon/did-auth-siop": "0.16.1-next.233", + "@sphereon/did-auth-siop": "0.16.1-next.339", "@sphereon/ssi-sdk.core": "workspace:*", "@sphereon/ssi-types": "workspace:*", "uint8arrays": "3.1.1" diff --git a/packages/siopv2-oid4vp-common/src/auth-model.ts b/packages/siopv2-oid4vp-common/src/auth-model.ts index 9cc564d70..0d3c7bec3 100644 --- a/packages/siopv2-oid4vp-common/src/auth-model.ts +++ b/packages/siopv2-oid4vp-common/src/auth-model.ts @@ -6,6 +6,10 @@ export interface ClaimPayloadCommonOpts { [x: string]: any } +export interface AuthorizationChallengeValidationResponse { + presentation_during_issuance_session: string; +} + export type AuthorizationRequestStateStatus = 'created' | 'sent' | 'received' | 'verified' | 'error' export type AuthorizationResponseStateStatus = 'created' | 'sent' | 'received' | 'verified' | 'error' diff --git a/packages/siopv2-oid4vp-op-auth/__tests__/restAgent.test.ts b/packages/siopv2-oid4vp-op-auth/__tests__/restAgent.test.ts index 6adeb496a..461ae9193 100644 --- a/packages/siopv2-oid4vp-op-auth/__tests__/restAgent.test.ts +++ b/packages/siopv2-oid4vp-op-auth/__tests__/restAgent.test.ts @@ -43,7 +43,7 @@ const getAgent = (options?: IAgentOptions) => createAgent({ ...options, plugins: [ - new DidAuthSiopOpAuthenticator(presentationSignCallback), + new DidAuthSiopOpAuthenticator({ presentationSignCallback }), new DIDResolverPlugin({ resolver: new Resolver({ ...getDidKeyResolver(), diff --git a/packages/siopv2-oid4vp-op-auth/package.json b/packages/siopv2-oid4vp-op-auth/package.json index c84b7dd19..eb08fb0d5 100644 --- a/packages/siopv2-oid4vp-op-auth/package.json +++ b/packages/siopv2-oid4vp-op-auth/package.json @@ -14,9 +14,9 @@ "build:clean": "tsc --build --clean && tsc --build" }, "dependencies": { - "@sphereon/did-auth-siop": "0.16.1-next.233", - "@sphereon/did-auth-siop-adapter": "0.16.1-next.233", - "@sphereon/oid4vc-common": "0.16.1-next.233", + "@sphereon/did-auth-siop": "0.16.1-next.339", + "@sphereon/did-auth-siop-adapter": "0.16.1-next.339", + "@sphereon/oid4vc-common": "0.16.1-next.339", "@sphereon/pex": "5.0.0-unstable.28", "@sphereon/pex-models": "^2.3.2", "@sphereon/ssi-sdk-ext.did-utils": "0.27.0", diff --git a/packages/siopv2-oid4vp-op-auth/src/agent/DidAuthSiopOpAuthenticator.ts b/packages/siopv2-oid4vp-op-auth/src/agent/DidAuthSiopOpAuthenticator.ts index 25b3aacae..ce7dbc318 100644 --- a/packages/siopv2-oid4vp-op-auth/src/agent/DidAuthSiopOpAuthenticator.ts +++ b/packages/siopv2-oid4vp-op-auth/src/agent/DidAuthSiopOpAuthenticator.ts @@ -8,7 +8,7 @@ import { NonPersistedIdentity, Party, } from '@sphereon/ssi-sdk.data-store' -import { Loggers } from '@sphereon/ssi-types' +import { Hasher, Loggers } from '@sphereon/ssi-types' import { IAgentPlugin } from '@veramo/core' import { v4 as uuidv4 } from 'uuid' import { @@ -25,32 +25,30 @@ import { import { Siopv2Machine } from '../machine/Siopv2Machine' import { getSelectableCredentials, siopSendAuthorizationResponse, translateCorrelationIdToName } from '../services/Siopv2MachineService' import { OpSession } from '../session' +import { PEX, Status } from '@sphereon/pex' +import { computeEntryHash } from '@veramo/utils' +import { UniqueDigitalCredential } from '@sphereon/ssi-sdk.credential-store' +import { EventEmitter } from 'events' import { + AddIdentityArgs, + CreateConfigArgs, + CreateConfigResult, + GetSiopRequestArgs, IDidAuthSiopOpAuthenticator, IGetSiopSessionArgs, IRegisterCustomApprovalForSiopArgs, IRemoveCustomApprovalForSiopArgs, IRemoveSiopSessionArgs, IRequiredContext, -} from '../types/IDidAuthSiopOpAuthenticator' -import { Siopv2Machine as Siopv2MachineId, Siopv2MachineInstanceOpts } from '../types/machine' - -import { - AddIdentityArgs, - CreateConfigArgs, - CreateConfigResult, - GetSiopRequestArgs, OnContactIdentityCreatedArgs, OnIdentifierCreatedArgs, RetrieveContactArgs, SendResponseArgs, Siopv2AuthorizationRequestData, Siopv2HolderEvent, -} from '../types/siop-service' -import { PEX, Status } from '@sphereon/pex' -import { computeEntryHash } from '@veramo/utils' -import { UniqueDigitalCredential } from '@sphereon/ssi-sdk.credential-store' -import { EventEmitter } from 'events' + Siopv2Machine as Siopv2MachineId, + Siopv2MachineInstanceOpts +} from '../types' const logger = Loggers.DEFAULT.options(LOGGER_NAMESPACE, {}).get(LOGGER_NAMESPACE) @@ -86,26 +84,29 @@ export class DidAuthSiopOpAuthenticator implements IAgentPlugin { siopGetSelectableCredentials: this.siopGetSelectableCredentials.bind(this), } + private readonly hasher?: Hasher private readonly sessions: Map private readonly customApprovals: Record Promise> private readonly presentationSignCallback?: PresentationSignCallback - private readonly onContactIdentityCreated?: (args: OnContactIdentityCreatedArgs) => Promise private readonly onIdentifierCreated?: (args: OnIdentifierCreatedArgs) => Promise private readonly eventEmitter?: EventEmitter - constructor( - presentationSignCallback?: PresentationSignCallback, - customApprovals?: Record Promise>, - options?: DidAuthSiopOpAuthenticatorOptions, - ) { - const { onContactIdentityCreated, onIdentifierCreated } = options ?? {} + constructor(options?: DidAuthSiopOpAuthenticatorOptions) { + const { + onContactIdentityCreated, + onIdentifierCreated, + hasher, + customApprovals = {}, + presentationSignCallback + } = { ...options } + + this.hasher = hasher this.onContactIdentityCreated = onContactIdentityCreated this.onIdentifierCreated = onIdentifierCreated - - this.sessions = new Map() - this.customApprovals = customApprovals || {} this.presentationSignCallback = presentationSignCallback + this.sessions = new Map() + this.customApprovals = customApprovals } public async onEvent(event: any, context: RequiredContext): Promise { @@ -185,8 +186,8 @@ export class DidAuthSiopOpAuthenticator implements IAgentPlugin { return Siopv2Machine.newInstance(siopv2MachineOpts) } - private async siopCreateConfig(args: CreateConfigArgs): Promise { - const { url } = args + private async siopCreateConfig(context: TContext): Promise { + const { url } = context if (!url) { return Promise.reject(Error('Missing request uri in context')) @@ -215,7 +216,7 @@ export class DidAuthSiopOpAuthenticator implements IAgentPlugin { const session: OpSession = await agent .siopGetOPSession({ sessionId }) - .catch(async () => await agent.siopRegisterOPSession({ requestJwtOrUri: redirectUrl, sessionId, op: { eventEmitter: this.eventEmitter } })) + .catch(async () => await agent.siopRegisterOPSession({ requestJwtOrUri: redirectUrl, sessionId, op: { eventEmitter: this.eventEmitter, hasher: this.hasher } })) logger.debug(`session: ${JSON.stringify(session.id, null, 2)}`) const verifiedAuthorizationRequest = await session.getAuthorizationRequest() @@ -332,7 +333,7 @@ export class DidAuthSiopOpAuthenticator implements IAgentPlugin { } private async siopSendResponse(args: SendResponseArgs, context: RequiredContext): Promise { - const { didAuthConfig, authorizationRequestData, selectedCredentials } = args + const { didAuthConfig, authorizationRequestData, selectedCredentials, isFirstParty } = args if (didAuthConfig === undefined) { return Promise.reject(Error('Missing config in context')) @@ -342,7 +343,7 @@ export class DidAuthSiopOpAuthenticator implements IAgentPlugin { return Promise.reject(Error('Missing authorization request data in context')) } - const pex = new PEX() + const pex = new PEX({ hasher: this.hasher }) const verifiableCredentialsWithDefinition: Array = [] authorizationRequestData.presentationDefinitions?.forEach((presentationDefinition) => { @@ -378,6 +379,8 @@ export class DidAuthSiopOpAuthenticator implements IAgentPlugin { sessionId: didAuthConfig.sessionId, ...(args.idOpts && { idOpts: args.idOpts }), ...(authorizationRequestData.presentationDefinitions !== undefined && { verifiableCredentialsWithDefinition }), + isFirstParty, + hasher: this.hasher }, context, ) diff --git a/packages/siopv2-oid4vp-op-auth/src/services/Siopv2MachineService.ts b/packages/siopv2-oid4vp-op-auth/src/services/Siopv2MachineService.ts index 46eadf91c..a9c2cbe88 100644 --- a/packages/siopv2-oid4vp-op-auth/src/services/Siopv2MachineService.ts +++ b/packages/siopv2-oid4vp-op-auth/src/services/Siopv2MachineService.ts @@ -4,7 +4,7 @@ import { InputDescriptorV1, InputDescriptorV2, PresentationDefinitionV1, Present import { isOID4VCIssuerIdentifier, ManagedIdentifierOptsOrResult } from '@sphereon/ssi-sdk-ext.identifier-resolution' import { verifiableCredentialForRoleFilter } from '@sphereon/ssi-sdk.credential-store' import { ConnectionType, CredentialRole } from '@sphereon/ssi-sdk.data-store' -import { CredentialMapper, Loggers, PresentationSubmission } from '@sphereon/ssi-types' +import { CredentialMapper, Hasher, Loggers, PresentationSubmission } from '@sphereon/ssi-types' import { OID4VP, OpSession } from '../session' import { DidAgents, @@ -48,12 +48,14 @@ export const siopSendAuthorizationResponse = async ( sessionId: string verifiableCredentialsWithDefinition?: VerifiableCredentialsWithDefinition[] idOpts?: ManagedIdentifierOptsOrResult + isFirstParty?: boolean + hasher?: Hasher }, context: RequiredContext, ) => { const { agent } = context const agentContext = { ...context, agent: context.agent as DidAgents } - let { idOpts } = args + let { idOpts, isFirstParty, hasher } = args if (connectionType !== ConnectionType.SIOPv2_OpenID4VP) { return Promise.reject(Error(`No supported authentication provider for type: ${connectionType}`)) @@ -67,7 +69,7 @@ export const siopSendAuthorizationResponse = async ( let presentationsAndDefs: VerifiablePresentationWithDefinition[] | undefined let presentationSubmission: PresentationSubmission | undefined if (await session.hasPresentationDefinitions()) { - const oid4vp: OID4VP = await session.getOID4VP({}) + const oid4vp: OID4VP = await session.getOID4VP({ hasher }) const credentialsAndDefinitions = args.verifiableCredentialsWithDefinition ? args.verifiableCredentialsWithDefinition @@ -162,6 +164,7 @@ export const siopSendAuthorizationResponse = async ( ...(presentationSubmission && { presentationSubmission }), // todo: Change issuer value in case we do not use identifier. Use key.meta.jwkThumbprint then responseSignerOpts: idOpts!, + isFirstParty, }) } diff --git a/packages/siopv2-oid4vp-op-auth/src/session/OpSession.ts b/packages/siopv2-oid4vp-op-auth/src/session/OpSession.ts index b8fa0fa04..2a20b752c 100644 --- a/packages/siopv2-oid4vp-op-auth/src/session/OpSession.ts +++ b/packages/siopv2-oid4vp-op-auth/src/session/OpSession.ts @@ -240,7 +240,7 @@ export class OpSession { private createPresentationVerificationCallback(context: IRequiredContext) { async function presentationVerificationCallback( args: W3CVerifiablePresentation | CompactSdJwtVc, - presentationSubmission: PresentationSubmission, + presentationSubmission?: PresentationSubmission, ): Promise { let result: IVerifyResult if (CredentialMapper.isSdJwtEncoded(args)) { @@ -360,6 +360,7 @@ export class OpSession { const responseOpts = { verification, issuer, + ...(args.isFirstParty && { isFirstParty: args.isFirstParty }), ...(args.verifiablePresentations && { presentationExchange: { verifiablePresentations, diff --git a/packages/siopv2-oid4vp-op-auth/src/types/IDidAuthSiopOpAuthenticator.ts b/packages/siopv2-oid4vp-op-auth/src/types/IDidAuthSiopOpAuthenticator.ts index 047c9d20d..a59d31457 100644 --- a/packages/siopv2-oid4vp-op-auth/src/types/IDidAuthSiopOpAuthenticator.ts +++ b/packages/siopv2-oid4vp-op-auth/src/types/IDidAuthSiopOpAuthenticator.ts @@ -123,6 +123,7 @@ export interface IOpsSendSiopAuthorizationResponseArgs { presentationSubmission?: PresentationSubmission verifiablePresentations?: W3CVerifiablePresentation[] hasher?: Hasher + isFirstParty?: boolean } export enum events { diff --git a/packages/siopv2-oid4vp-op-auth/src/types/machine/index.ts b/packages/siopv2-oid4vp-op-auth/src/types/machine/index.ts index 481403e16..d2c5f5063 100644 --- a/packages/siopv2-oid4vp-op-auth/src/types/machine/index.ts +++ b/packages/siopv2-oid4vp-op-auth/src/types/machine/index.ts @@ -18,6 +18,7 @@ export type Siopv2MachineContext = { contactAlias: string selectableCredentialsMap?: SelectableCredentialsMap selectedCredentials: Array + isFirstParty?: boolean error?: ErrorDetails } diff --git a/packages/siopv2-oid4vp-op-auth/src/types/siop-service/index.ts b/packages/siopv2-oid4vp-op-auth/src/types/siop-service/index.ts index 0fd3fb152..842de04b9 100644 --- a/packages/siopv2-oid4vp-op-auth/src/types/siop-service/index.ts +++ b/packages/siopv2-oid4vp-op-auth/src/types/siop-service/index.ts @@ -1,4 +1,8 @@ -import { PresentationDefinitionWithLocation, RPRegistrationMetadataPayload } from '@sphereon/did-auth-siop' +import { + PresentationDefinitionWithLocation, + PresentationSignCallback, + RPRegistrationMetadataPayload, VerifiedAuthorizationRequest +} from '@sphereon/did-auth-siop' import { IIdentifierResolution, ManagedIdentifierOptsOrResult } from '@sphereon/ssi-sdk-ext.identifier-resolution' import { IContactManager } from '@sphereon/ssi-sdk.contact-manager' import { ICredentialStore, UniqueDigitalCredential } from '@sphereon/ssi-sdk.credential-store' @@ -7,10 +11,14 @@ import { IIssuanceBranding } from '@sphereon/ssi-sdk.issuance-branding' import { IAgentContext, IDIDManager, IIdentifier, IResolver } from '@veramo/core' import { IDidAuthSiopOpAuthenticator } from '../IDidAuthSiopOpAuthenticator' import { Siopv2MachineContext, Siopv2MachineInterpreter, Siopv2MachineState } from '../machine' +import { Hasher } from '@sphereon/ssi-types' export type DidAuthSiopOpAuthenticatorOptions = { + presentationSignCallback?: PresentationSignCallback + customApprovals?: Record Promise> onContactIdentityCreated?: (args: OnContactIdentityCreatedArgs) => Promise onIdentifierCreated?: (args: OnIdentifierCreatedArgs) => Promise + hasher?: Hasher } export type GetMachineArgs = { @@ -19,12 +27,21 @@ export type GetMachineArgs = { stateNavigationListener?: (siopv2Machine: Siopv2MachineInterpreter, state: Siopv2MachineState, navigation?: any) => Promise } -export type CreateConfigArgs = Pick +export type CreateConfigArgs = { url: string } export type CreateConfigResult = Omit -export type GetSiopRequestArgs = Pick +export type GetSiopRequestArgs = { didAuthConfig?: Omit, url: string } +// FIXME it would be nicer if these function are not tied to a certain machine so that we can start calling them for anywhere export type RetrieveContactArgs = Pick +// FIXME it would be nicer if these function are not tied to a certain machine so that we can start calling them for anywhere export type AddIdentityArgs = Pick -export type SendResponseArgs = Pick +export type SendResponseArgs = { + didAuthConfig?: Omit, + authorizationRequestData?: Siopv2AuthorizationRequestData, + selectedCredentials: Array + idOpts?: ManagedIdentifierOptsOrResult + isFirstParty?: boolean +} +// FIXME it would be nicer if these function are not tied to a certain machine so that we can start calling them for anywhere export type GetSelectableCredentialsArgs = Pick export enum Siopv2HolderEvent { @@ -38,7 +55,7 @@ export enum SupportedLanguage { } export type Siopv2AuthorizationResponseData = { - body?: string + body?: string | Record url?: string queryParams?: Record } diff --git a/packages/siopv2-oid4vp-rp-auth/package.json b/packages/siopv2-oid4vp-rp-auth/package.json index ed78afd03..51bf9bdef 100644 --- a/packages/siopv2-oid4vp-rp-auth/package.json +++ b/packages/siopv2-oid4vp-rp-auth/package.json @@ -14,9 +14,9 @@ "build:clean": "tsc --build --clean && tsc --build" }, "dependencies": { - "@sphereon/did-auth-siop": "0.16.1-next.233", - "@sphereon/did-auth-siop-adapter": "0.16.1-next.233", - "@sphereon/oid4vc-common": "0.16.1-next.233", + "@sphereon/did-auth-siop": "0.16.1-next.339", + "@sphereon/did-auth-siop-adapter": "0.16.1-next.339", + "@sphereon/oid4vc-common": "0.16.1-next.339", "@sphereon/pex": "5.0.0-unstable.28", "@sphereon/ssi-sdk-ext.did-utils": "0.27.0", "@sphereon/ssi-sdk-ext.identifier-resolution": "0.27.0", diff --git a/packages/siopv2-oid4vp-rp-auth/src/functions.ts b/packages/siopv2-oid4vp-rp-auth/src/functions.ts index 23f19fcf4..61091fed6 100644 --- a/packages/siopv2-oid4vp-rp-auth/src/functions.ts +++ b/packages/siopv2-oid4vp-rp-auth/src/functions.ts @@ -64,7 +64,7 @@ export function getPresentationVerificationCallback( ): PresentationVerificationCallback { async function presentationVerificationCallback( args: any, // FIXME any - presentationSubmission: PresentationSubmission, + presentationSubmission?: PresentationSubmission, ): Promise { if (CredentialMapper.isSdJwtEncoded(args)) { const result: IVerifySdJwtPresentationResult = await context.agent.verifySdJwtPresentation({ @@ -80,6 +80,9 @@ export function getPresentationVerificationCallback( if (context.agent.mdocOid4vpRPVerify === undefined) { return Promise.reject('ImDLMdoc agent plugin must be enabled to support MsoMdoc types') } + if (!presentationSubmission) { + return Promise.reject('No presentationSubmission present') + } const verifyResult = await context.agent.mdocOid4vpRPVerify({ vp_token: args, presentation_submission: presentationSubmission, diff --git a/packages/siopv2-oid4vp-rp-rest-api/package.json b/packages/siopv2-oid4vp-rp-rest-api/package.json index 735a2d382..424f3f785 100644 --- a/packages/siopv2-oid4vp-rp-rest-api/package.json +++ b/packages/siopv2-oid4vp-rp-rest-api/package.json @@ -11,7 +11,7 @@ "start:dev": "ts-node __tests__/RestAPI.ts" }, "dependencies": { - "@sphereon/did-auth-siop": "0.16.1-next.233", + "@sphereon/did-auth-siop": "0.16.1-next.339", "@sphereon/ssi-express-support": "workspace:*", "@sphereon/ssi-sdk.core": "workspace:*", "@sphereon/ssi-sdk.credential-validation": "workspace:*", diff --git a/packages/siopv2-oid4vp-rp-rest-api/src/siop-api-functions.ts b/packages/siopv2-oid4vp-rp-rest-api/src/siop-api-functions.ts index 86a8dd18c..b8616170f 100644 --- a/packages/siopv2-oid4vp-rp-rest-api/src/siop-api-functions.ts +++ b/packages/siopv2-oid4vp-rp-rest-api/src/siop-api-functions.ts @@ -1,6 +1,7 @@ import { AuthorizationResponsePayload, PresentationDefinitionLocation } from '@sphereon/did-auth-siop' import { checkAuth, ISingleEndpointOpts, sendErrorResponse } from '@sphereon/ssi-express-support' import { CredentialMapper } from '@sphereon/ssi-types' +import { AuthorizationChallengeValidationResponse } from '@sphereon/ssi-sdk.siopv2-oid4vp-common' import { Request, Response, Router } from 'express' import { IRequiredContext } from './types' @@ -88,8 +89,15 @@ export function verifyAuthResponseSIOPv2Endpoint( // const credentialSubject = wrappedPresentation.presentation.verifiableCredential[0]?.credential?.credentialSubject // console.log(JSON.stringify(credentialSubject, null, 2)) console.log('PRESENTATION:' + JSON.stringify(wrappedPresentation.presentation, null, 2)) - const responseRedirectURI = await context.agent.siopGetRedirectURI({ correlationId, definitionId, state: verifiedResponse.state }) response.statusCode = 200 + + const authorizationChallengeValidationResponse: AuthorizationChallengeValidationResponse = { presentation_during_issuance_session: verifiedResponse.correlationId } + if (authorizationResponse.is_first_party) { + response.setHeader('Content-Type', 'application/json') + return response.send(JSON.stringify(authorizationChallengeValidationResponse)) + } + + const responseRedirectURI = await context.agent.siopGetRedirectURI({ correlationId, definitionId, state: verifiedResponse.state }) if (responseRedirectURI) { response.setHeader('Content-Type', 'application/json') return response.send(JSON.stringify({ redirect_uri: responseRedirectURI })) diff --git a/packages/w3c-vc-api/package.json b/packages/w3c-vc-api/package.json index 2669dce4a..8dc47b5c9 100644 --- a/packages/w3c-vc-api/package.json +++ b/packages/w3c-vc-api/package.json @@ -11,7 +11,7 @@ "start:dev": "ts-node __tests__/agent.ts" }, "dependencies": { - "@sphereon/did-auth-siop": "0.16.1-next.233", + "@sphereon/did-auth-siop": "0.16.1-next.339", "@sphereon/ssi-express-support": "workspace:*", "@sphereon/ssi-sdk.agent-config": "workspace:*", "@sphereon/ssi-sdk.core": "workspace:*", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6eb3118e2..6a77e27d2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -615,11 +615,11 @@ importers: specifier: ^5.7.0 version: 5.7.0 '@sphereon/did-auth-siop': - specifier: 0.16.1-next.233 - version: 0.16.1-next.233(encoding@0.1.13)(typescript@5.6.3) + specifier: 0.16.1-next.339 + version: 0.16.1-next.339(encoding@0.1.13)(typescript@5.6.3) '@sphereon/did-auth-siop-adapter': - specifier: 0.16.1-next.233 - version: 0.16.1-next.233(encoding@0.1.13)(typescript@5.6.3) + specifier: 0.16.1-next.339 + version: 0.16.1-next.339(encoding@0.1.13)(typescript@5.6.3) '@sphereon/pex': specifier: 5.0.0-unstable.28 version: 5.0.0-unstable.28 @@ -697,11 +697,11 @@ importers: version: 4.38.3 devDependencies: '@sphereon/oid4vci-client': - specifier: 0.16.1-next.233 - version: 0.16.1-next.233(encoding@0.1.13) + specifier: 0.16.1-next.339 + version: 0.16.1-next.339(encoding@0.1.13) '@sphereon/oid4vci-common': - specifier: 0.16.1-next.233 - version: 0.16.1-next.233(encoding@0.1.13) + specifier: 0.16.1-next.339 + version: 0.16.1-next.339(encoding@0.1.13) '@sphereon/ssi-express-support': specifier: workspace:* version: link:../ssi-express-support @@ -914,8 +914,8 @@ importers: packages/mdl-mdoc: dependencies: '@sphereon/did-auth-siop': - specifier: 0.16.1-next.233 - version: 0.16.1-next.233(encoding@0.1.13)(typescript@5.6.3) + specifier: 0.16.1-next.339 + version: 0.16.1-next.339(encoding@0.1.13)(typescript@5.6.3) '@sphereon/kmp-mdoc-core': specifier: 0.2.0-SNAPSHOT.26 version: 0.2.0-SNAPSHOT.26 @@ -972,11 +972,11 @@ importers: version: 9.0.1 devDependencies: '@sphereon/oid4vci-client': - specifier: 0.16.1-next.233 - version: 0.16.1-next.233(encoding@0.1.13) + specifier: 0.16.1-next.339 + version: 0.16.1-next.339(encoding@0.1.13) '@sphereon/oid4vci-common': - specifier: 0.16.1-next.233 - version: 0.16.1-next.233(encoding@0.1.13) + specifier: 0.16.1-next.339 + version: 0.16.1-next.339(encoding@0.1.13) '@sphereon/ssi-express-support': specifier: workspace:* version: link:../ssi-express-support @@ -1144,15 +1144,18 @@ importers: packages/oid4vci-holder: dependencies: + '@sphereon/did-auth-siop': + specifier: 0.16.1-next.339 + version: 0.16.1-next.339(encoding@0.1.13)(typescript@5.6.3) '@sphereon/kmp-mdoc-core': specifier: 0.2.0-SNAPSHOT.26 version: 0.2.0-SNAPSHOT.26 '@sphereon/oid4vci-client': - specifier: 0.16.1-next.233 - version: 0.16.1-next.233(encoding@0.1.13) + specifier: 0.16.1-next.339 + version: 0.16.1-next.339(encoding@0.1.13) '@sphereon/oid4vci-common': - specifier: 0.16.1-next.233 - version: 0.16.1-next.233(encoding@0.1.13) + specifier: 0.16.1-next.339 + version: 0.16.1-next.339(encoding@0.1.13) '@sphereon/ssi-sdk-ext.did-utils': specifier: 0.27.0 version: 0.27.0(encoding@0.1.13)(pg@8.13.1)(sqlite3@5.1.7)(ts-node@10.9.2(@types/node@20.17.9)(typescript@5.6.3)) @@ -1192,6 +1195,9 @@ importers: '@sphereon/ssi-sdk.sd-jwt': specifier: workspace:* version: link:../sd-jwt + '@sphereon/ssi-sdk.siopv2-oid4vp-op-auth': + specifier: workspace:* + version: link:../siopv2-oid4vp-op-auth '@sphereon/ssi-sdk.xstate-machine-persistence': specifier: workspace:* version: link:../xstate-persistence @@ -1227,11 +1233,14 @@ importers: version: 4.38.3 devDependencies: '@sphereon/oid4vc-common': - specifier: 0.16.1-next.187 - version: 0.16.1-next.187 + specifier: 0.16.1-next.339 + version: 0.16.1-next.339 '@sphereon/ssi-sdk-ext.did-resolver-jwk': specifier: 0.27.0 version: 0.27.0 + '@sphereon/ssi-sdk.siopv2-oid4vp-common': + specifier: workspace:* + version: link:../siopv2-oid4vp-common '@types/i18n-js': specifier: ^3.8.9 version: 3.8.9 @@ -1260,11 +1269,11 @@ importers: packages/oid4vci-issuer: dependencies: '@sphereon/oid4vci-common': - specifier: 0.16.1-next.233 - version: 0.16.1-next.233(encoding@0.1.13) + specifier: 0.16.1-next.339 + version: 0.16.1-next.339(encoding@0.1.13) '@sphereon/oid4vci-issuer': - specifier: 0.16.1-next.233 - version: 0.16.1-next.233(awesome-qr@2.1.5-rc.0(encoding@0.1.13))(encoding@0.1.13) + specifier: 0.16.1-next.339 + version: 0.16.1-next.339(awesome-qr@2.1.5-rc.0(encoding@0.1.13))(encoding@0.1.13) '@sphereon/ssi-sdk-ext.did-utils': specifier: 0.27.0 version: 0.27.0(encoding@0.1.13)(pg@8.13.1)(sqlite3@5.1.7)(ts-node@10.9.2(@types/node@20.17.9)(typescript@5.6.3)) @@ -1317,6 +1326,9 @@ importers: specifier: ^9.0.1 version: 9.0.1 devDependencies: + '@sphereon/did-auth-siop': + specifier: 0.16.1-next.339 + version: 0.16.1-next.339(encoding@0.1.13)(typescript@5.6.3) '@sphereon/did-uni-client': specifier: ^0.6.3 version: 0.6.3(encoding@0.1.13) @@ -1339,14 +1351,14 @@ importers: packages/oid4vci-issuer-rest-api: dependencies: '@sphereon/oid4vci-common': - specifier: 0.16.1-next.233 - version: 0.16.1-next.233(encoding@0.1.13) + specifier: 0.16.1-next.339 + version: 0.16.1-next.339(encoding@0.1.13) '@sphereon/oid4vci-issuer': - specifier: 0.16.1-next.233 - version: 0.16.1-next.233(awesome-qr@2.1.5-rc.0(encoding@0.1.13))(encoding@0.1.13) + specifier: 0.16.1-next.339 + version: 0.16.1-next.339(awesome-qr@2.1.5-rc.0(encoding@0.1.13))(encoding@0.1.13) '@sphereon/oid4vci-issuer-server': - specifier: 0.16.1-next.233 - version: 0.16.1-next.233(@noble/hashes@1.6.1)(awesome-qr@2.1.5-rc.0(encoding@0.1.13))(encoding@0.1.13)(passport-azure-ad@4.3.5)(passport-http-bearer@1.0.1) + specifier: 0.16.1-next.339 + version: 0.16.1-next.339(@noble/hashes@1.6.1)(awesome-qr@2.1.5-rc.0(encoding@0.1.13))(encoding@0.1.13)(passport-azure-ad@4.3.5)(passport-http-bearer@1.0.1) '@sphereon/ssi-express-support': specifier: workspace:* version: link:../ssi-express-support @@ -1505,8 +1517,8 @@ importers: packages/oid4vci-issuer-rest-client: dependencies: '@sphereon/oid4vci-common': - specifier: 0.16.1-next.233 - version: 0.16.1-next.233(encoding@0.1.13) + specifier: 0.16.1-next.339 + version: 0.16.1-next.339(encoding@0.1.13) '@sphereon/ssi-types': specifier: workspace:* version: link:../ssi-types @@ -1542,8 +1554,8 @@ importers: packages/oid4vci-issuer-store: dependencies: '@sphereon/oid4vci-common': - specifier: 0.16.1-next.233 - version: 0.16.1-next.233(encoding@0.1.13) + specifier: 0.16.1-next.339 + version: 0.16.1-next.339(encoding@0.1.13) '@sphereon/ssi-sdk-ext.did-utils': specifier: 0.27.0 version: 0.27.0(encoding@0.1.13)(pg@8.13.1)(sqlite3@5.1.7)(ts-node@10.9.2(@types/node@20.17.9)(typescript@5.6.3)) @@ -2240,8 +2252,8 @@ importers: packages/siopv2-oid4vp-common: dependencies: '@sphereon/did-auth-siop': - specifier: 0.16.1-next.233 - version: 0.16.1-next.233(encoding@0.1.13)(typescript@5.6.3) + specifier: 0.16.1-next.339 + version: 0.16.1-next.339(encoding@0.1.13)(typescript@5.6.3) '@sphereon/ssi-sdk.core': specifier: workspace:* version: link:../ssi-sdk-core @@ -2262,14 +2274,14 @@ importers: packages/siopv2-oid4vp-op-auth: dependencies: '@sphereon/did-auth-siop': - specifier: 0.16.1-next.233 - version: 0.16.1-next.233(encoding@0.1.13)(typescript@5.6.3) + specifier: 0.16.1-next.339 + version: 0.16.1-next.339(encoding@0.1.13)(typescript@5.6.3) '@sphereon/did-auth-siop-adapter': - specifier: 0.16.1-next.233 - version: 0.16.1-next.233(encoding@0.1.13)(typescript@5.6.3) + specifier: 0.16.1-next.339 + version: 0.16.1-next.339(encoding@0.1.13)(typescript@5.6.3) '@sphereon/oid4vc-common': - specifier: 0.16.1-next.233 - version: 0.16.1-next.233 + specifier: 0.16.1-next.339 + version: 0.16.1-next.339 '@sphereon/pex': specifier: 5.0.0-unstable.28 version: 5.0.0-unstable.28 @@ -2395,14 +2407,14 @@ importers: packages/siopv2-oid4vp-rp-auth: dependencies: '@sphereon/did-auth-siop': - specifier: 0.16.1-next.233 - version: 0.16.1-next.233(encoding@0.1.13)(typescript@5.6.3) + specifier: 0.16.1-next.339 + version: 0.16.1-next.339(encoding@0.1.13)(typescript@5.6.3) '@sphereon/did-auth-siop-adapter': - specifier: 0.16.1-next.233 - version: 0.16.1-next.233(encoding@0.1.13)(typescript@5.6.3) + specifier: 0.16.1-next.339 + version: 0.16.1-next.339(encoding@0.1.13)(typescript@5.6.3) '@sphereon/oid4vc-common': - specifier: 0.16.1-next.233 - version: 0.16.1-next.233 + specifier: 0.16.1-next.339 + version: 0.16.1-next.339 '@sphereon/pex': specifier: 5.0.0-unstable.28 version: 5.0.0-unstable.28 @@ -2483,8 +2495,8 @@ importers: packages/siopv2-oid4vp-rp-rest-api: dependencies: '@sphereon/did-auth-siop': - specifier: 0.16.1-next.233 - version: 0.16.1-next.233(encoding@0.1.13)(typescript@5.6.3) + specifier: 0.16.1-next.339 + version: 0.16.1-next.339(encoding@0.1.13)(typescript@5.6.3) '@sphereon/ssi-express-support': specifier: workspace:* version: link:../ssi-express-support @@ -3592,8 +3604,8 @@ importers: packages/w3c-vc-api: dependencies: '@sphereon/did-auth-siop': - specifier: 0.16.1-next.233 - version: 0.16.1-next.233(encoding@0.1.13)(typescript@5.6.3) + specifier: 0.16.1-next.339 + version: 0.16.1-next.339(encoding@0.1.13)(typescript@5.6.3) '@sphereon/ssi-express-support': specifier: workspace:* version: link:../ssi-express-support @@ -6001,12 +6013,12 @@ packages: '@sinonjs/fake-timers@8.1.0': resolution: {integrity: sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==} - '@sphereon/did-auth-siop-adapter@0.16.1-next.233': - resolution: {integrity: sha512-epkvRUSNyAMXvgDynbCQxV9xmrkLvZBgRmL+XVK65pri03TcpLfwt8q3q++l0b0JljcEvENZGXs6SrUNdxb7KQ==} + '@sphereon/did-auth-siop-adapter@0.16.1-next.339': + resolution: {integrity: sha512-HeBxuv4Q8b2p6act0TL/80F2tef5OpnBxcliakzdt/GsmPwBzwczW1ViFQiCdO76JMvmjza50RSzU6f0jFVY8Q==} engines: {node: '>=18'} - '@sphereon/did-auth-siop@0.16.1-next.233': - resolution: {integrity: sha512-E+NvyheYqZqkC4qUa9HVPVoQoJfAeKxqpL1cUPUNo/KqZL2v4g+6AruDutUNm5omvPNbr3DJBY/qHed/1ze7NA==} + '@sphereon/did-auth-siop@0.16.1-next.339': + resolution: {integrity: sha512-1gfsJBjzwVQcjceXEjTgxU3OZPX0C9cikR3tlogOz4pD4Yy9TL4bz+tIEk0FJup2U09LfQw/MdTNFWjfbAvYJQ==} engines: {node: '>=18'} '@sphereon/did-provider-oyd@0.27.0': @@ -6029,8 +6041,8 @@ packages: '@sphereon/react-native-argon2': ^2.0.9 react-native: '>=0.60.0' - '@sphereon/jarm@0.16.1-next.233': - resolution: {integrity: sha512-FJTzPcNMEzEL1pK8QEnKIbZ8EnbU3yv2R9YiuIMPbHrEQuROq/MduqWmjWTKGPjFeNhZDJ8JSebN2pefhowCRA==} + '@sphereon/jarm@0.16.1-next.339': + resolution: {integrity: sha512-PQABG/rZpK1ypZqfHRV3HuxVDxclRJnD41A8fnr8EQB5JFKElSQ/SWEIWi7DD1HeWqzZnRiLWt1boPuWjgphOQ==} engines: {node: '>=18'} '@sphereon/kmp-mdoc-core@0.2.0-SNAPSHOT.26': @@ -6040,24 +6052,20 @@ packages: '@sphereon/lto-did-ts@0.1.8-unstable.0': resolution: {integrity: sha512-3jzwwuYX/VYuze+T9/yg4PcsJ5iNNwAfTp4WfS4aSfPFBErDAfKXqn6kOb0wFYGkhejr3Jz+rljPC2iKZiHiGA==} - '@sphereon/oid4vc-common@0.16.1-next.187': - resolution: {integrity: sha512-OYOT3Z3moNb6JyGHYX9tcaOcrCAUn9opNKQ9wuKoiSyckXS6RZXinuDlGETISnMCf3cQZCWAHOc6ctfHv1eHDw==} + '@sphereon/oid4vc-common@0.16.1-next.339': + resolution: {integrity: sha512-Ttw49G8liVpR/qZTuBX9YVI8vYb3rbtWAUr8Ra5GRGQo8+xE22dT5gMK0BwTeg6CRik3zhYnW6L4LN/NRPuhIA==} engines: {node: '>=18'} - '@sphereon/oid4vc-common@0.16.1-next.233': - resolution: {integrity: sha512-dNoCOTgRek5d1gSTPrVXxP8WIoY/NJpQnpvXVHGvS4+o6W81nEmq0z5aIwPyQ4XbXExjCrlZd8LaN9GyZJwE7w==} + '@sphereon/oid4vci-client@0.16.1-next.339': + resolution: {integrity: sha512-bPkiQGauf8LjkfI8LXVBw494Z8ttCT439420PlzWohm5aYytcEw7DL/Sn+5+ME+4aHzzb4khXAVqjpSlrySSDA==} engines: {node: '>=18'} - '@sphereon/oid4vci-client@0.16.1-next.233': - resolution: {integrity: sha512-bv2AFdieXF5mhMrl/rBoBEJAUS+Ctd1zTg4vnz+eBVHa8zhL9t4wwH30QeKqB4UuBTTH5Ubk/or5Y/JS0OureA==} + '@sphereon/oid4vci-common@0.16.1-next.339': + resolution: {integrity: sha512-ZZGh6xxtmeiJy5+sic8F+YZkZucx1vuYQ4wqG0bhcu17zOkvyqvW5S54G2o1K8ZVYjPTuBkI402nuqAoMGgLGg==} engines: {node: '>=18'} - '@sphereon/oid4vci-common@0.16.1-next.233': - resolution: {integrity: sha512-X1cdipPhQwTkV/NRiG/4jl6BY0nJ5MQcrfbmfCIPcC4hHcKJzW1p9g9jfQfLGHkYmWB6EtECk+C+lquJsWOHGA==} - engines: {node: '>=18'} - - '@sphereon/oid4vci-issuer-server@0.16.1-next.233': - resolution: {integrity: sha512-mIp+OCL2xtVLH5ZX7K/jfuCYcryUKQo7BQfd0hvctq8ug1NVxjF1bakpkx1m6eRUFZzSvvvZqI+4UqDAzFHs7Q==} + '@sphereon/oid4vci-issuer-server@0.16.1-next.339': + resolution: {integrity: sha512-6Cbrs3PSPIY1tNTyCHYGLdQgLSu/ie38OIllyhrJLuCtCpxsdRR0AE0lQi06gJMpt6S90L1sKEPKbn2lLJF22Q==} engines: {node: '>=18'} peerDependencies: awesome-qr: ^2.1.5-rc.0 @@ -6065,8 +6073,8 @@ packages: awesome-qr: optional: true - '@sphereon/oid4vci-issuer@0.16.1-next.233': - resolution: {integrity: sha512-LVhHJiADjn3XKbV+QEXKLElwhuEsTH7E4AXr5VrwC1TlqvDo69v2dE2seUMJWSVhPXR4hzOaxzhQtoPtA/SE8A==} + '@sphereon/oid4vci-issuer@0.16.1-next.339': + resolution: {integrity: sha512-1iGdjtle4KjzqEdYL7CLG7lijwPGebagkksan6C7FA5NlSUjnS8jUt9CElrHZY/pbznCVh+qfr/dBVbhl6uuUg==} engines: {node: '>=18'} peerDependencies: awesome-qr: ^2.1.5-rc.0 @@ -8169,6 +8177,9 @@ packages: dayjs@1.11.13: resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} + dcql@0.2.19: + resolution: {integrity: sha512-/EvT8tArlg8zFsTQbRn6PijfeQ3nUwuEeCRDpptWcYqE8Wyt8J9Sb44gMPFzVCoIEb3R0M7Hl+XWkUMobC8jXQ==} + debug@4.3.7: resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} engines: {node: '>=6.0'} @@ -13294,6 +13305,14 @@ packages: typescript: optional: true + valibot@1.0.0-beta.8: + resolution: {integrity: sha512-OPAwJZtowb0j91b+bd77+ny7D1VVzsCzD7Jl9waLUlMprTsfI9Y3HHbW3hAQD7wKDKHsmGEesuiYWaYvcZL2wg==} + peerDependencies: + typescript: 5.6.3 + peerDependenciesMeta: + typescript: + optional: true + valid-url@1.0.9: resolution: {integrity: sha512-QQDsV8OnSf5Uc30CKSwG9lnhMPe6exHtTXLRYX8uMwKENy640pU+2BgBL0LRbDh/eYRahNCS7aewCx0wf3NYVA==} @@ -16982,11 +17001,11 @@ snapshots: dependencies: '@sinonjs/commons': 1.8.6 - '@sphereon/did-auth-siop-adapter@0.16.1-next.233(encoding@0.1.13)(typescript@5.6.3)': + '@sphereon/did-auth-siop-adapter@0.16.1-next.339(encoding@0.1.13)(typescript@5.6.3)': dependencies: - '@sphereon/did-auth-siop': 0.16.1-next.233(encoding@0.1.13)(typescript@5.6.3) + '@sphereon/did-auth-siop': 0.16.1-next.339(encoding@0.1.13)(typescript@5.6.3) '@sphereon/did-uni-client': 0.6.3(encoding@0.1.13) - '@sphereon/oid4vc-common': 0.16.1-next.233 + '@sphereon/oid4vc-common': 0.16.1-next.339 '@sphereon/wellknown-dids-client': 0.1.3(encoding@0.1.13) did-jwt: 6.11.6(patch_hash=afqywxnnjnsy6hwgax66dyyiey) did-resolver: 4.1.0 @@ -16995,15 +17014,16 @@ snapshots: - supports-color - typescript - '@sphereon/did-auth-siop@0.16.1-next.233(encoding@0.1.13)(typescript@5.6.3)': + '@sphereon/did-auth-siop@0.16.1-next.339(encoding@0.1.13)(typescript@5.6.3)': dependencies: '@astronautlabs/jsonpath': 1.1.2 - '@sphereon/jarm': 0.16.1-next.233(typescript@5.6.3) - '@sphereon/oid4vc-common': 0.16.1-next.233 + '@sphereon/jarm': 0.16.1-next.339(typescript@5.6.3) + '@sphereon/oid4vc-common': 0.16.1-next.339 '@sphereon/pex': 5.0.0-unstable.28 '@sphereon/pex-models': 2.3.2 '@sphereon/ssi-types': link:packages/ssi-types cross-fetch: 4.0.0(encoding@0.1.13) + dcql: 0.2.19(typescript@5.6.3) debug: 4.3.7 events: 3.3.0 jwt-decode: 4.0.0 @@ -17060,9 +17080,9 @@ snapshots: react-native: 0.76.3(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(encoding@0.1.13)(react@18.3.1) uint8arrays: 3.1.1 - '@sphereon/jarm@0.16.1-next.233(typescript@5.6.3)': + '@sphereon/jarm@0.16.1-next.339(typescript@5.6.3)': dependencies: - '@sphereon/oid4vc-common': 0.16.1-next.233 + '@sphereon/oid4vc-common': 0.16.1-next.339 valibot: 0.42.1(typescript@5.6.3) transitivePeerDependencies: - typescript @@ -17086,15 +17106,7 @@ snapshots: - encoding - typescript - '@sphereon/oid4vc-common@0.16.1-next.187': - dependencies: - '@sphereon/ssi-types': link:packages/ssi-types - jwt-decode: 4.0.0 - sha.js: 2.4.11 - uint8arrays: 3.1.1 - uuid: 9.0.1 - - '@sphereon/oid4vc-common@0.16.1-next.233': + '@sphereon/oid4vc-common@0.16.1-next.339': dependencies: '@sphereon/ssi-types': link:packages/ssi-types jwt-decode: 4.0.0 @@ -17102,10 +17114,10 @@ snapshots: uint8arrays: 3.1.1 uuid: 9.0.1 - '@sphereon/oid4vci-client@0.16.1-next.233(encoding@0.1.13)': + '@sphereon/oid4vci-client@0.16.1-next.339(encoding@0.1.13)': dependencies: - '@sphereon/oid4vc-common': 0.16.1-next.233 - '@sphereon/oid4vci-common': 0.16.1-next.233(encoding@0.1.13) + '@sphereon/oid4vc-common': 0.16.1-next.339 + '@sphereon/oid4vci-common': 0.16.1-next.339(encoding@0.1.13) '@sphereon/ssi-types': link:packages/ssi-types cross-fetch: 3.1.8(encoding@0.1.13) debug: 4.3.7 @@ -17113,9 +17125,9 @@ snapshots: - encoding - supports-color - '@sphereon/oid4vci-common@0.16.1-next.233(encoding@0.1.13)': + '@sphereon/oid4vci-common@0.16.1-next.339(encoding@0.1.13)': dependencies: - '@sphereon/oid4vc-common': 0.16.1-next.233 + '@sphereon/oid4vc-common': 0.16.1-next.339 '@sphereon/ssi-types': link:packages/ssi-types cross-fetch: 3.1.8(encoding@0.1.13) debug: 4.3.7 @@ -17126,11 +17138,11 @@ snapshots: - encoding - supports-color - '@sphereon/oid4vci-issuer-server@0.16.1-next.233(@noble/hashes@1.6.1)(awesome-qr@2.1.5-rc.0(encoding@0.1.13))(encoding@0.1.13)(passport-azure-ad@4.3.5)(passport-http-bearer@1.0.1)': + '@sphereon/oid4vci-issuer-server@0.16.1-next.339(@noble/hashes@1.6.1)(awesome-qr@2.1.5-rc.0(encoding@0.1.13))(encoding@0.1.13)(passport-azure-ad@4.3.5)(passport-http-bearer@1.0.1)': dependencies: - '@sphereon/oid4vc-common': 0.16.1-next.233 - '@sphereon/oid4vci-common': 0.16.1-next.233(encoding@0.1.13) - '@sphereon/oid4vci-issuer': 0.16.1-next.233(awesome-qr@2.1.5-rc.0(encoding@0.1.13))(encoding@0.1.13) + '@sphereon/oid4vc-common': 0.16.1-next.339 + '@sphereon/oid4vci-common': 0.16.1-next.339(encoding@0.1.13) + '@sphereon/oid4vci-issuer': 0.16.1-next.339(awesome-qr@2.1.5-rc.0(encoding@0.1.13))(encoding@0.1.13) '@sphereon/ssi-express-support': 0.30.2-feature.mdoc.funke2.367(@noble/hashes@1.6.1)(passport-azure-ad@4.3.5)(passport-http-bearer@1.0.1) '@sphereon/ssi-types': link:packages/ssi-types body-parser: 1.20.3 @@ -17149,10 +17161,10 @@ snapshots: - passport-http-bearer - supports-color - '@sphereon/oid4vci-issuer@0.16.1-next.233(awesome-qr@2.1.5-rc.0(encoding@0.1.13))(encoding@0.1.13)': + '@sphereon/oid4vci-issuer@0.16.1-next.339(awesome-qr@2.1.5-rc.0(encoding@0.1.13))(encoding@0.1.13)': dependencies: - '@sphereon/oid4vc-common': 0.16.1-next.233 - '@sphereon/oid4vci-common': 0.16.1-next.233(encoding@0.1.13) + '@sphereon/oid4vc-common': 0.16.1-next.339 + '@sphereon/oid4vci-common': 0.16.1-next.339(encoding@0.1.13) '@sphereon/ssi-types': link:packages/ssi-types uuid: 9.0.1 optionalDependencies: @@ -20729,6 +20741,12 @@ snapshots: dayjs@1.11.13: {} + dcql@0.2.19(typescript@5.6.3): + dependencies: + valibot: 1.0.0-beta.8(typescript@5.6.3) + transitivePeerDependencies: + - typescript + debug@4.3.7: dependencies: ms: 2.1.3 @@ -27165,6 +27183,10 @@ snapshots: optionalDependencies: typescript: 5.6.3 + valibot@1.0.0-beta.8(typescript@5.6.3): + optionalDependencies: + typescript: 5.6.3 + valid-url@1.0.9: {} validate-npm-package-license@3.0.4: