Skip to content

Commit

Permalink
fix: dcql alpha
Browse files Browse the repository at this point in the history
  • Loading branch information
auer-martin committed Nov 21, 2024
1 parent dc1c318 commit b9ec577
Show file tree
Hide file tree
Showing 10 changed files with 64 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -337,9 +337,7 @@ describe('presentation exchange manager tests', () => {
const payload = await getPayloadVID1Val()
// eslint-disable-next-line @typescript-eslint/no-explicit-any
;(payload.claims?.vp_token as any).presentation_definition_uri = EXAMPLE_PD_URL
await expect(PresentationExchange.findValidPresentationDefinitions(payload)).rejects.toThrow(
SIOPErrors.REQUEST_CLAIMS_PRESENTATION_DEFINITION_BY_REF_AND_VALUE_NON_EXCLUSIVE,
)
await expect(PresentationExchange.findValidPresentationDefinitions(payload)).rejects.toThrow(SIOPErrors.REQUEST_CLAIMS_PRESENTATION_NON_EXCLUSIVE)
})

it('validatePresentationAgainstDefinition: should pass if provided VP match the PD', async function () {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,16 @@ export class AuthorizationResponse {
}

const verifiedIdToken = await this.idToken?.verify(verifyOpts)
const oid4vp = await verifyPresentations(this, verifyOpts)
if (this.payload.vp_token && !verifyOpts.presentationDefinitions && !verifyOpts.dcqlQuery) {
throw Promise.reject(Error('vp_token is present, but no presentation definitions or dcql query provided'))
}

const emptyPresentationDefinitions = Array.isArray(verifyOpts.presentationDefinitions) && verifyOpts.presentationDefinitions.length === 0
if (!this.payload.vp_token && ((verifyOpts.presentationDefinitions && !emptyPresentationDefinitions) || verifyOpts.dcqlQuery)) {
throw Promise.reject(Error('Presentation definitions or dcql query provided, but no vp_token present'))
}

const oid4vp = this.payload.vp_token ? await verifyPresentations(this, verifyOpts) : undefined

// Gather all nonces
const allNonces = new Set<string>()
Expand Down Expand Up @@ -227,7 +236,7 @@ export class AuthorizationResponse {
presentations = extractPresentationsFromVpToken(this._payload.vp_token, opts)
}

if (presentations && Array.isArray(presentations) && presentations.length === 0) {
if (!presentations || (Array.isArray(presentations) && presentations.length === 0)) {
return Promise.reject(Error('missing presentation(s)'))
}
const presentationsArray = Array.isArray(presentations) ? presentations : [presentations]
Expand Down
15 changes: 5 additions & 10 deletions packages/siop-oid4vp/lib/authorization-response/Dcql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,29 +12,24 @@ import { AuthorizationRequestPayload, SIOPErrors } from '../types'
*/
export const findValidDcqlQuery = async (authorizationRequestPayload: AuthorizationRequestPayload): Promise<DcqlQuery | undefined> => {
const dcqlQuery: string[] = extractDataFromPath(authorizationRequestPayload, '$.dcql_query').map((d) => d.value)
const dcqlQueryList: string[] = extractDataFromPath(authorizationRequestPayload, '$.dcql_query[*]').map((d) => d.value)
const definitions = extractDataFromPath(authorizationRequestPayload, '$.presentation_definition')
const definitionsFromList = extractDataFromPath(authorizationRequestPayload, '$.presentation_definition[*]')
const definitionRefs = extractDataFromPath(authorizationRequestPayload, '$.presentation_definition_uri')
const definitionRefsFromList = extractDataFromPath(authorizationRequestPayload, '$.presentation_definition_uri[*]')

const hasPD = (definitions && definitions.length > 0) || (definitionsFromList && definitionsFromList.length > 0)
const hasPdRef = (definitionRefs && definitionRefs.length > 0) || (definitionRefsFromList && definitionRefsFromList.length > 0)
const hasDcql = (dcqlQuery && dcqlQuery.length > 0) || (dcqlQueryList && dcqlQueryList.length > 0)
const hasDcql = dcqlQuery && dcqlQuery.length > 0

if ([hasPD, hasPdRef, hasDcql].filter(Boolean).length > 1) {
throw new Error(SIOPErrors.REQUEST_CLAIMS_PRESENTATION_NON_EXCLUSIVE)
}

if (dcqlQuery.length > 1 || dcqlQueryList.length > 1) {
if (dcqlQuery.length === 0) return undefined

if (dcqlQuery.length > 1) {
throw new Error('Found multiple dcql_query in vp_token. Only one is allowed')
}

const encoded = dcqlQuery.length ? dcqlQuery[0] : dcqlQueryList[0]
if (!encoded) return undefined

const parsedDcqlQuery = DcqlQuery.parse(JSON.parse(encoded))
DcqlQuery.validate(parsedDcqlQuery)

return parsedDcqlQuery
return DcqlQuery.parse(JSON.parse(dcqlQuery[0]))
}
28 changes: 15 additions & 13 deletions packages/siop-oid4vp/lib/authorization-response/OpenID4VP.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,6 @@ export const verifyPresentations = async (
authorizationResponse: AuthorizationResponse,
verifyOpts: VerifyAuthorizationResponseOpts,
): Promise<VerifiedOpenID4VPSubmission | VerifiedOpenID4VPSubmissionDcql | null> => {
if (!authorizationResponse.payload.vp_token) return null
if (
authorizationResponse.payload.vp_token &&
Array.isArray(authorizationResponse.payload.vp_token) &&
authorizationResponse.payload.vp_token.length === 0
) {
return Promise.reject(Error('the payload is missing a vp_token'))
}

let idPayload: IDTokenPayload | undefined
if (authorizationResponse.idToken) {
idPayload = await authorizationResponse.idToken.payload()
Expand All @@ -98,8 +89,6 @@ export const verifyPresentations = async (
let dcqlQuery = verifyOpts.dcqlQuery ?? authorizationResponse?.authorizationRequest?.payload.dcql_query
if (dcqlQuery) {
dcqlQuery = DcqlQuery.parse(dcqlQuery)
DcqlQuery.validate(dcqlQuery)

wrappedPresentations = extractPresentationsFromDcqlVpToken(authorizationResponse.payload.vp_token as string, { hasher: verifyOpts.hasher })

const verifiedPresentations = await Promise.all(
Expand Down Expand Up @@ -170,12 +159,25 @@ export const verifyPresentations = async (
}
}

export const extractPresentationRecordFromDcqlVpToken = (
vpToken: DcqlVpToken.Input | string,
opts?: { hasher?: Hasher },
): Record<string, WrappedVerifiablePresentation> => {
const presentationRecord = Object.fromEntries(
Object.entries(DcqlVpToken.parse(vpToken)).map(([credentialQueryId, vp]) => [
credentialQueryId,
CredentialMapper.toWrappedVerifiablePresentation(vp as W3CVerifiablePresentation | CompactSdJwtVc | string, { hasher: opts.hasher }),
]),
)

return presentationRecord
}

export const extractPresentationsFromDcqlVpToken = (
vpToken: DcqlVpToken.Input | string,
opts?: { hasher?: Hasher },
): WrappedVerifiablePresentation[] => {
const presentations = Object.values(DcqlVpToken.parse(vpToken)) as Array<W3CVerifiablePresentation | CompactSdJwtVc | string>
return presentations.map((vp) => CredentialMapper.toWrappedVerifiablePresentation(vp, { hasher: opts.hasher }))
return Object.values(extractPresentationRecordFromDcqlVpToken(vpToken, opts))
}

export const extractPresentationsFromVpToken = (
Expand Down
4 changes: 3 additions & 1 deletion packages/siop-oid4vp/lib/authorization-response/Payload.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { DcqlVpToken } from 'dcql'

import { AuthorizationRequest } from '../authorization-request'
import { IDToken } from '../id-token'
import { RequestObject } from '../request-object'
Expand Down Expand Up @@ -29,7 +31,7 @@ export const createResponsePayload = async (

// vp tokens
if (responseOpts.dcqlQuery) {
responsePayload.vp_token = JSON.stringify(responseOpts.dcqlQuery.credentialQueryIdToPresentation)
responsePayload.vp_token = DcqlVpToken.encode(responseOpts.dcqlQuery.encodedPresentationRecord)
} else {
await putPresentationSubmissionInLocation(authorizationRequest, responsePayload, responseOpts, idTokenPayload)
}
Expand Down
6 changes: 1 addition & 5 deletions packages/siop-oid4vp/lib/authorization-response/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,7 @@ export interface PresentationExchangeResponseOpts {
}

export interface DcqlQueryResponseOpts {
credentialQueryIdToPresentation: Record<string, Record<string, unknown> | string>
}

export interface PresentationExchangeRequestOpts {
presentationVerificationCallback?: PresentationVerificationCallback
encodedPresentationRecord: Record<string, Record<string, unknown> | string>
}

export interface PresentationDefinitionPayloadOpts {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2342,7 +2342,7 @@ export const AuthorizationResponseOptsSchemaObj = {
"DcqlQueryResponseOpts": {
"type": "object",
"properties": {
"credentialQueryIdToPresentation": {
"encodedPresentationRecord": {
"type": "object",
"additionalProperties": {
"anyOf": [
Expand All @@ -2358,7 +2358,7 @@ export const AuthorizationResponseOptsSchemaObj = {
}
},
"required": [
"credentialQueryIdToPresentation"
"encodedPresentationRecord"
],
"additionalProperties": false
}
Expand Down
4 changes: 2 additions & 2 deletions packages/siop-oid4vp/lib/types/SIOP.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ export interface IDTokenPayload extends JWTPayload {
}
}

export type DcqlQueryVpToken = string
export type EcodedDcqlQueryVpToken = string

export interface AuthorizationResponsePayload {
access_token?: string
Expand All @@ -181,7 +181,7 @@ export interface AuthorizationResponsePayload {
| W3CVerifiablePresentation
| CompactSdJwtVc
| MdocOid4vpMdocVpToken
| DcqlQueryVpToken
| EcodedDcqlQueryVpToken
presentation_submission?: PresentationSubmission
verifiedData?: IPresentation | AdditionalClaims
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down
2 changes: 1 addition & 1 deletion packages/siop-oid4vp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"@sphereon/jarm": "workspace:*",
"@sphereon/oid4vc-common": "workspace:*",
"@sphereon/pex": "5.0.0-unstable.24",
"dcql": "link:../../../dcql/dcql",
"dcql": "^0.2.4",
"@sphereon/pex-models": "^2.3.1",
"@sphereon/ssi-types": "0.30.2-next.129",
"cross-fetch": "^4.0.0",
Expand Down
25 changes: 23 additions & 2 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit b9ec577

Please sign in to comment.