Skip to content

Commit 4654cf4

Browse files
committed
feat: add support for 1.0 final for both oid4vp (was already mostly there) and OID4VCI
1 parent 2c3badd commit 4654cf4

41 files changed

Lines changed: 1621 additions & 106 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

packages/callback-example/lib/__tests__/issuerCallback.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ describe('issuerCallback', () => {
169169
const nonces = new MemoryStates<CNonceState>()
170170
await nonces.set('test_value', { cNonce: 'test_value', createdAt: +new Date() })
171171
vcIssuer = new VcIssuerBuilder()
172+
.withVersion(OpenId4VCIVersion.VER_1_0_15)
172173
.withAuthorizationServers('https://authorization-server')
173174
.withCredentialEndpoint('https://credential-endpoint')
174175
.withCredentialIssuer(IDENTIPROOF_ISSUER_URL)

packages/client/lib/CredentialRequestClient.ts

Lines changed: 51 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ import { createDPoP, CreateDPoPClientOpts, getCreateDPoPOptions } from '@sphereo
22
import {
33
acquireDeferredCredential,
44
AuthorizationDetailsV1_0_15,
5+
AuthorizationDetailsV1_0,
56
CredentialRequest,
67
CredentialRequestV1_0_15,
8+
CredentialRequestV1_0,
79
CredentialResponse,
810
DPoPResponseParams,
911
ExperimentalSubjectIssuance,
@@ -20,6 +22,7 @@ import {
2022
import { CredentialFormat, Loggers } from '@sphereon/ssi-types'
2123

2224
import { CredentialRequestClientBuilderV1_0_15 } from './CredentialRequestClientBuilderV1_0_15'
25+
import { CredentialRequestClientBuilderV1_0 } from './CredentialRequestClientBuilderV1_0'
2326
import { ProofOfPossessionBuilder } from './ProofOfPossessionBuilder'
2427
import { shouldRetryResourceRequestWithDPoPNonce } from './functions/dpopUtil'
2528

@@ -32,14 +35,15 @@ export interface CredentialRequestOpts {
3235
notificationEndpoint?: string
3336
deferredCredentialEndpoint?: string
3437
credentialTypes?: string[]
35-
credentialIdentifier?: string
38+
credentialIdentifier?: string // d15: singular
39+
credentialIdentifiers?: string[] // 1.0 final: array
3640
credentialConfigurationId?: string
3741
proof: ProofOfPossession
3842
token: string
3943
version: OpenId4VCIVersion
4044
subjectIssuance?: ExperimentalSubjectIssuance
4145
issuerState?: string
42-
authorizationDetails?: AuthorizationDetailsV1_0_15[]
46+
authorizationDetails?: (AuthorizationDetailsV1_0_15 | AuthorizationDetailsV1_0)[]
4347
}
4448

4549
export type CreateCredentialRequestOpts = {
@@ -134,7 +138,7 @@ export class CredentialRequestClient {
134138
return this.credentialRequestOpts.deferredCredentialEndpoint
135139
}
136140

137-
public constructor(builder: CredentialRequestClientBuilderV1_0_15) {
141+
public constructor(builder: CredentialRequestClientBuilderV1_0_15 | CredentialRequestClientBuilderV1_0) {
138142
this._credentialRequestOpts = { ...builder }
139143
}
140144

@@ -304,41 +308,74 @@ export class CredentialRequestClient {
304308
})
305309
}
306310

307-
public async createCredentialRequestWithoutProof(opts: CreateCredentialRequestOpts): Promise<CredentialRequestV1_0_15> {
311+
public async createCredentialRequestWithoutProof(opts: CreateCredentialRequestOpts): Promise<CredentialRequestV1_0_15 | CredentialRequestV1_0> {
308312
return await this.createCredentialRequestImpl(opts)
309313
}
310314

311315
public async createCredentialRequest(
312316
opts: CreateCredentialRequestOpts & {
313317
proofInput: ProofOfPossessionBuilder | ProofOfPossession
314318
},
315-
): Promise<CredentialRequestV1_0_15> {
319+
): Promise<CredentialRequestV1_0_15 | CredentialRequestV1_0> {
316320
return await this.createCredentialRequestImpl(opts)
317321
}
318322

319323
private async createCredentialRequestImpl(
320324
opts: CreateCredentialRequestOpts & {
321325
proofInput?: ProofOfPossessionBuilder | ProofOfPossession
322326
},
323-
): Promise<CredentialRequestV1_0_15> {
327+
): Promise<CredentialRequestV1_0_15 | CredentialRequestV1_0> {
324328
const { proofInput, credentialIdentifier, credentialConfigurationId } = opts
325329
let proof: ProofOfPossession | undefined = undefined
326330
if (proofInput) {
327331
proof = await buildProof(proofInput, opts)
328332
}
329333

330-
// For v15, handle authorization details from token response
331-
if (this.version() >= OpenId4VCIVersion.VER_1_0_15) {
332-
const authDetail = findAuthorizationDetail(this.credentialRequestOpts.authorizationDetails, credentialConfigurationId ?? credentialIdentifier)
334+
const issuer_state = this.credentialRequestOpts.issuerState
335+
const commonBody = {
336+
...(issuer_state && { issuer_state }),
337+
...(proof && { proof }),
338+
...opts.subjectIssuance,
339+
}
333340

334-
const issuer_state = this.credentialRequestOpts.issuerState
341+
// 1.0 final: credential_configuration_id is REQUIRED, credential_identifiers is OPTIONAL array
342+
if (this.version() >= OpenId4VCIVersion.VER_1_0) {
343+
const authDetail = findAuthorizationDetail(
344+
this.credentialRequestOpts.authorizationDetails as AuthorizationDetailsV1_0_15[],
345+
credentialConfigurationId ?? credentialIdentifier,
346+
)
347+
const authDetailObj = authDetail && typeof authDetail === 'object' ? (authDetail as any) : null
335348

336-
const commonBody = {
337-
...(issuer_state && { issuer_state }),
338-
...(proof && { proof }),
339-
...opts.subjectIssuance,
349+
const configId =
350+
credentialConfigurationId ?? authDetailObj?.credential_configuration_id ?? this._credentialRequestOpts.credentialConfigurationId
351+
352+
if (!configId) {
353+
return Promise.reject(Error('credential_configuration_id is required for 1.0 final credential request'))
340354
}
341355

356+
// Build credential_identifiers array from various sources
357+
const identifiers: string[] | undefined =
358+
this._credentialRequestOpts.credentialIdentifiers ??
359+
(authDetailObj?.credential_identifiers && authDetailObj.credential_identifiers.length > 0
360+
? authDetailObj.credential_identifiers
361+
: credentialIdentifier
362+
? [credentialIdentifier]
363+
: undefined)
364+
365+
const request: CredentialRequestV1_0 = {
366+
credential_configuration_id: configId,
367+
...(identifiers && identifiers.length > 0 && { credential_identifiers: identifiers }),
368+
...commonBody,
369+
}
370+
return request
371+
}
372+
373+
// Draft 15: credential_identifier (singular) OR credential_configuration_id
374+
if (this.version() >= OpenId4VCIVersion.VER_1_0_15) {
375+
const authDetail = findAuthorizationDetail(
376+
this.credentialRequestOpts.authorizationDetails as AuthorizationDetailsV1_0_15[],
377+
credentialConfigurationId ?? credentialIdentifier,
378+
)
342379
const authDetailObj = authDetail && typeof authDetail === 'object' ? (authDetail as any) : null
343380

344381
if (authDetailObj?.credential_identifier) {
@@ -374,7 +411,6 @@ export class CredentialRequestClient {
374411
return Promise.reject(Error('No credential_identifier or credential_configuration_id available for v1.0-15 request'))
375412
}
376413

377-
// Since you only support V15+, this should never execute
378414
throw new Error(`Unsupported version: ${this.version()}`)
379415
}
380416

packages/client/lib/CredentialRequestClientBuilder.ts

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,26 @@ import {
55
CredentialOfferRequestWithBaseUrl,
66
EndpointMetadata,
77
EndpointMetadataResultV1_0_15,
8+
EndpointMetadataResultV1_0,
89
ExperimentalSubjectIssuance,
910
OpenId4VCIVersion,
1011
UniformCredentialOfferRequest,
1112
} from '@sphereon/oid4vci-common'
1213

1314
import { CredentialOfferClient } from './CredentialOfferClient'
1415
import { CredentialRequestClientBuilderV1_0_15 } from './CredentialRequestClientBuilderV1_0_15'
16+
import { CredentialRequestClientBuilderV1_0 } from './CredentialRequestClientBuilderV1_0'
1517

16-
type CredentialRequestClientBuilderVersionSpecific = CredentialRequestClientBuilderV1_0_15
18+
type CredentialRequestClientBuilderVersionSpecific = CredentialRequestClientBuilderV1_0_15 | CredentialRequestClientBuilderV1_0
1719

1820
function isV1_0_15(builder: CredentialRequestClientBuilderVersionSpecific): builder is CredentialRequestClientBuilderV1_0_15 {
1921
return (builder as CredentialRequestClientBuilderV1_0_15).withCredentialIdentifier !== undefined
2022
}
2123

24+
function isV1_0(builder: CredentialRequestClientBuilderVersionSpecific): builder is CredentialRequestClientBuilderV1_0 {
25+
return (builder as CredentialRequestClientBuilderV1_0).withCredentialIdentifiers !== undefined
26+
}
27+
2228
export class CredentialRequestClientBuilder {
2329
private _builder: CredentialRequestClientBuilderVersionSpecific
2430

@@ -31,26 +37,35 @@ export class CredentialRequestClientBuilder {
3137
metadata,
3238
version,
3339
credentialIdentifier,
40+
credentialIdentifiers,
3441
credentialTypes,
3542
}: {
3643
credentialIssuer: string
3744
metadata?: EndpointMetadata
3845
version?: OpenId4VCIVersion
3946
credentialIdentifier?: string
47+
credentialIdentifiers?: string[]
4048
credentialTypes?: string | string[]
4149
}): CredentialRequestClientBuilder {
42-
// const specVersion = version ?? OpenId4VCIVersion.VER_1_0_15
43-
let builder
44-
const metadataV15 = metadata as EndpointMetadataResultV1_0_15
45-
// if (specVersion >= OpenId4VCIVersion.VER_1_0_15) {
46-
builder = CredentialRequestClientBuilderV1_0_15.fromCredentialIssuer({
47-
credentialIssuer,
48-
metadata: metadataV15,
49-
version,
50-
credentialIdentifier,
51-
credentialTypes,
52-
})
53-
// }
50+
const specVersion = version ?? OpenId4VCIVersion.VER_1_0
51+
let builder: CredentialRequestClientBuilderVersionSpecific
52+
if (specVersion >= OpenId4VCIVersion.VER_1_0) {
53+
builder = CredentialRequestClientBuilderV1_0.fromCredentialIssuer({
54+
credentialIssuer,
55+
metadata: metadata as EndpointMetadataResultV1_0,
56+
version: specVersion,
57+
credentialIdentifiers: credentialIdentifiers ?? (credentialIdentifier ? [credentialIdentifier] : undefined),
58+
credentialTypes,
59+
})
60+
} else {
61+
builder = CredentialRequestClientBuilderV1_0_15.fromCredentialIssuer({
62+
credentialIssuer,
63+
metadata: metadata as EndpointMetadataResultV1_0_15,
64+
version: specVersion,
65+
credentialIdentifier,
66+
credentialTypes,
67+
})
68+
}
5469

5570
return new CredentialRequestClientBuilder(builder)
5671
}
@@ -138,7 +153,21 @@ export class CredentialRequestClientBuilder {
138153
if (this._builder.version === undefined || this._builder.version < OpenId4VCIVersion.VER_1_0_15) {
139154
throw new Error('Version of spec should be equal or higher than v1_0_15')
140155
}
141-
;(this._builder as CredentialRequestClientBuilderV1_0_15).withCredentialIdentifier(credentialIdentifier)
156+
if (isV1_0(this._builder)) {
157+
this._builder.withCredentialIdentifiers([credentialIdentifier])
158+
} else if (isV1_0_15(this._builder)) {
159+
this._builder.withCredentialIdentifier(credentialIdentifier)
160+
}
161+
return this
162+
}
163+
164+
public withCredentialIdentifiers(credentialIdentifiers: string[]): this {
165+
if (isV1_0(this._builder)) {
166+
this._builder.withCredentialIdentifiers(credentialIdentifiers)
167+
} else if (isV1_0_15(this._builder) && credentialIdentifiers.length > 0) {
168+
// d15 only supports singular, use the first one
169+
this._builder.withCredentialIdentifier(credentialIdentifiers[0])
170+
}
142171
return this
143172
}
144173

0 commit comments

Comments
 (0)