Skip to content

Commit

Permalink
refactor: Moved Dcql.ts functions into a Dcql class and made them sta…
Browse files Browse the repository at this point in the history
…tic.
  • Loading branch information
zoemaas committed Jan 14, 2025
1 parent 05d39b7 commit ec6a13e
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { parseJWT } from '@sphereon/oid4vc-common'
import { DcqlQuery } from 'dcql'

import { PresentationDefinitionWithLocation } from '../authorization-response'
import { findValidDcqlQuery } from '../authorization-response'
import { Dcql } from '../authorization-response'
import { PresentationExchange } from '../authorization-response/PresentationExchange'
import { fetchByReferenceOrUseByValue, removeNullUndefined } from '../helpers'
import { authorizationRequestVersionDiscovery } from '../helpers/SIOPSpecVersion'
Expand Down Expand Up @@ -206,7 +206,7 @@ export class AuthorizationRequest {
await this.getSupportedVersion(),
)

const dcqlQuery = await findValidDcqlQuery(mergedPayload)
const dcqlQuery = await Dcql.findValidDcqlQuery(mergedPayload)

return {
jwt,
Expand Down Expand Up @@ -291,6 +291,6 @@ export class AuthorizationRequest {
}

public async getDcqlQuery(): Promise<DcqlQuery | undefined> {
return await findValidDcqlQuery(await this.mergedPayloads())
return await Dcql.findValidDcqlQuery(await this.mergedPayloads())
}
}
4 changes: 2 additions & 2 deletions packages/siop-oid4vp/lib/authorization-request/URI.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { parseJWT } from '@sphereon/oid4vc-common'

import { findValidDcqlQuery } from '../authorization-response/Dcql'
import { Dcql } from '../authorization-response'
import { PresentationExchange } from '../authorization-response/PresentationExchange'
import { decodeUriAsJson, encodeJsonAsURI, fetchByReferenceOrUseByValue } from '../helpers'
import { assertValidRequestObjectPayload, RequestObject } from '../request-object'
Expand Down Expand Up @@ -166,7 +166,7 @@ export class URI implements AuthorizationRequestURI {
if (requestObjectPayload) {
// Only used to validate if the request object contains presentation definition(s) | a dcql query
await PresentationExchange.findValidPresentationDefinitions({ ...authorizationRequestPayload, ...requestObjectPayload })
await findValidDcqlQuery({ ...authorizationRequestPayload, ...requestObjectPayload })
await Dcql.findValidDcqlQuery({ ...authorizationRequestPayload, ...requestObjectPayload })

assertValidRequestObjectPayload(requestObjectPayload)
if (requestObjectPayload.registration) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { assertValidVerifyAuthorizationRequestOpts } from '../authorization-requ
import { IDToken } from '../id-token'
import { AuthorizationResponsePayload, ResponseType, SIOPErrors, VerifiedAuthorizationRequest, VerifiedAuthorizationResponse } from '../types'

import { assertValidDcqlPresentationResult } from './Dcql'
import { Dcql } from './Dcql'
import {
assertValidVerifiablePresentations,
extractNonceFromWrappedVerifiablePresentation,
Expand Down Expand Up @@ -145,7 +145,7 @@ export class AuthorizationResponse {
},
})
} else if (verifiedAuthorizationRequest.dcqlQuery) {
assertValidDcqlPresentationResult(responseOpts.dcqlQuery.dcqlPresentation as DcqlPresentation, verifiedAuthorizationRequest.dcqlQuery, {
await Dcql.assertValidDcqlPresentationResult(responseOpts.dcqlQuery.dcqlPresentation as DcqlPresentation, verifiedAuthorizationRequest.dcqlQuery, {
hasher: verifyOpts.hasher,
})
} else {
Expand Down
96 changes: 56 additions & 40 deletions packages/siop-oid4vp/lib/authorization-response/Dcql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,50 +13,66 @@ import { extractDcqlPresentationFromDcqlVpToken } from './OpenID4VP'
* @param authorizationRequestPayload object that can have a dcql_query inside
* @param version
*/
export const findValidDcqlQuery = async (authorizationRequestPayload: AuthorizationRequestPayload): Promise<DcqlQuery | undefined> => {
const dcqlQuery: 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

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

if (dcqlQuery.length === 0) return undefined
export class Dcql {

static findValidDcqlQuery = async (authorizationRequestPayload: AuthorizationRequestPayload): Promise<DcqlQuery | undefined> => {
const dcqlQuery: 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

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

if (dcqlQuery.length === 0) return undefined

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

return DcqlQuery.parse(JSON.parse(dcqlQuery[0]))
}

return DcqlQuery.parse(JSON.parse(dcqlQuery[0]))
}
static getDcqlPresentationResult = (record: DcqlPresentation | string, dcqlQuery: DcqlQuery, opts: {
hasher?: Hasher
}) => {
const dcqlPresentation = Object.fromEntries(
Object.entries(extractDcqlPresentationFromDcqlVpToken(record, opts)).map(([queryId, p]) => {
if (p.format === 'mso_mdoc') {
return [
queryId,
{
credential_format: 'mso_mdoc',
doctype: p.vcs[0].credential.toJson().docType,
namespaces: p.vcs[0].decoded
} satisfies DcqlMdocCredential,
]
} else if (p.format === 'vc+sd-jwt') {
return [queryId, {
credential_format: 'vc+sd-jwt',
vct: p.vcs[0].decoded.vct,
claims: p.vcs[0].decoded
} satisfies DcqlSdJwtVcCredential]
} else {
throw new Error('DcqlPresentation atm only supports mso_mdoc and vc+sd-jwt')
}
}),
)

export const getDcqlPresentationResult = (record: DcqlPresentation | string, dcqlQuery: DcqlQuery, opts: { hasher?: Hasher }) => {
const dcqlPresentation = Object.fromEntries(
Object.entries(extractDcqlPresentationFromDcqlVpToken(record, opts)).map(([queryId, p]) => {
if (p.format === 'mso_mdoc') {
return [
queryId,
{ credential_format: 'mso_mdoc', doctype: p.vcs[0].credential.toJson().docType, namespaces: p.vcs[0].decoded } satisfies DcqlMdocCredential,
]
} else if (p.format === 'vc+sd-jwt') {
return [queryId, { credential_format: 'vc+sd-jwt', vct: p.vcs[0].decoded.vct, claims: p.vcs[0].decoded } satisfies DcqlSdJwtVcCredential]
} else {
throw new Error('DcqlPresentation atm only supports mso_mdoc and vc+sd-jwt')
}
}),
)

return DcqlPresentationResult.fromDcqlPresentation(dcqlPresentation, { dcqlQuery })
}
return DcqlPresentationResult.fromDcqlPresentation(dcqlPresentation, { dcqlQuery })
}

export const assertValidDcqlPresentationResult = async (record: DcqlPresentation | string, dcqlQuery: DcqlQuery, opts: { hasher?: Hasher }) => {
const result = getDcqlPresentationResult(record, dcqlQuery, opts)
return DcqlPresentationResult.validate(result)
static assertValidDcqlPresentationResult = async (record: DcqlPresentation | string, dcqlQuery: DcqlQuery, opts: {
hasher?: Hasher
}) => {
const result = Dcql.getDcqlPresentationResult(record, dcqlQuery, opts)
return DcqlPresentationResult.validate(result)
}
}
4 changes: 2 additions & 2 deletions packages/siop-oid4vp/lib/authorization-response/OpenID4VP.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
} from '../types'

import { AuthorizationResponse } from './AuthorizationResponse'
import { assertValidDcqlPresentationResult } from './Dcql'
import { Dcql } from './Dcql'
import { PresentationExchange } from './PresentationExchange'
import {
AuthorizationResponseOpts,
Expand Down Expand Up @@ -96,7 +96,7 @@ export const verifyPresentations = async (
),
)

assertValidDcqlPresentationResult(authorizationResponse.payload.vp_token as string, dcqlQuery, { hasher: verifyOpts.hasher })
await Dcql.assertValidDcqlPresentationResult(authorizationResponse.payload.vp_token as string, dcqlQuery, { hasher: verifyOpts.hasher })

if (verifiedPresentations.some((verified) => !verified)) {
const message = verifiedPresentations
Expand Down

0 comments on commit ec6a13e

Please sign in to comment.