Skip to content

Commit 8bd61e8

Browse files
committed
chore: updated credential_offer_uri endpoint & enabled JSON fetch in CredentialOfferClientV1_0_13 & CredentialOfferClientV
1 parent f179c55 commit 8bd61e8

8 files changed

Lines changed: 86 additions & 31 deletions

File tree

packages/client/lib/CredentialOfferClient.ts

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,18 @@ import {
66
CredentialOfferPayloadV1_0_09,
77
CredentialOfferRequestWithBaseUrl,
88
CredentialOfferV1_0_11,
9-
CredentialOfferV1_0_13,
9+
CredentialOfferV1_0_13, decodeJsonProperties,
1010
determineSpecVersionFromURI,
11-
getClientIdFromCredentialOfferPayload,
11+
getClientIdFromCredentialOfferPayload, getURIComponentsAsArray,
1212
OpenId4VCIVersion,
1313
PRE_AUTH_CODE_LITERAL,
1414
PRE_AUTH_GRANT_LITERAL,
15-
toUniformCredentialOfferRequest,
16-
} from '@sphereon/oid4vci-common';
15+
toUniformCredentialOfferRequest
16+
} from '@sphereon/oid4vci-common'
1717
import Debug from 'debug';
1818

1919
import { LOG } from './types';
20+
import { fetch } from 'cross-fetch'
2021

2122
const debug = Debug('sphereon:oid4vci:offer');
2223

@@ -43,13 +44,27 @@ export class CredentialOfferClient {
4344
credential_offer: credentialOfferPayload,
4445
};
4546
} else {
46-
credentialOffer = convertURIToJsonObject(uri, {
47-
// It must have the '=' sign after credential_offer otherwise the uri will get split at openid_credential_offer
48-
arrayTypeProperties: uri.includes('credential_offer_uri=') ? ['credential_offer_uri='] : ['credential_offer='],
49-
requiredProperties: uri.includes('credential_offer_uri=') ? ['credential_offer_uri='] : ['credential_offer='],
50-
}) as CredentialOfferV1_0_11 | CredentialOfferV1_0_13;
47+
if (uri.includes('credential_offer_uri')) {
48+
const uriObj = getURIComponentsAsArray(uri) as unknown as Record<string, string> // FIXME
49+
const credentialOfferUri = uriObj['credential_offer_uri']
50+
const response = await fetch(credentialOfferUri)
51+
if (!(response && response.status >= 200 && response.status < 400)) {
52+
return Promise.reject(`the credential offer URI endpoint call was not successful. http code ${response.status} - reason ${response.statusText}`)
53+
}
54+
55+
if (response.headers.get('Content-Type')?.startsWith('application/json') === false) {
56+
return Promise.reject('the credential offer URI endpoint did not return content type application/json')
57+
}
58+
credentialOffer = decodeJsonProperties(await response.json()) as CredentialOfferV1_0_11 | CredentialOfferV1_0_13
59+
} else {
60+
credentialOffer = convertURIToJsonObject(uri, {
61+
// It must have the '=' sign after credential_offer otherwise the uri will get split at openid_credential_offer
62+
arrayTypeProperties: uri.includes('credential_offer_uri=') ? ['credential_offer_uri='] : ['credential_offer='],
63+
requiredProperties: uri.includes('credential_offer_uri=') ? ['credential_offer_uri='] : ['credential_offer=']
64+
}) as CredentialOfferV1_0_11 | CredentialOfferV1_0_13
65+
}
5166
if (credentialOffer?.credential_offer_uri === undefined && !credentialOffer?.credential_offer) {
52-
throw Error('Either a credential_offer or credential_offer_uri should be present in ' + uri);
67+
throw Error('Either a credential_offer or credential_offer_uri should be present in ' + uri)
5368
}
5469
}
5570

packages/client/lib/CredentialOfferClientV1_0_13.ts

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
import {
22
convertJsonToURI,
3-
convertURIToJsonObject,
4-
CredentialOfferRequestWithBaseUrl,
5-
CredentialOfferV1_0_13,
3+
convertURIToJsonObject, CredentialOffer,
4+
CredentialOfferRequestWithBaseUrl, CredentialOfferV1_0_11,
5+
CredentialOfferV1_0_13, decodeJsonProperties,
66
determineSpecVersionFromURI,
7-
getClientIdFromCredentialOfferPayload,
7+
getClientIdFromCredentialOfferPayload, getURIComponentsAsArray,
88
OpenId4VCIVersion,
99
PRE_AUTH_CODE_LITERAL,
1010
PRE_AUTH_GRANT_LITERAL,
11-
toUniformCredentialOfferRequest,
12-
} from '@sphereon/oid4vci-common';
11+
toUniformCredentialOfferRequest
12+
} from '@sphereon/oid4vci-common'
1313
import Debug from 'debug';
14+
import { fetch } from 'cross-fetch'
1415

1516
const debug = Debug('sphereon:oid4vci:offer');
1617

@@ -23,16 +24,31 @@ export class CredentialOfferClientV1_0_13 {
2324
}
2425
const scheme = uri.split('://')[0];
2526
const baseUrl = uri.split('?')[0];
26-
const version = determineSpecVersionFromURI(uri);
27-
const credentialOffer = convertURIToJsonObject(uri, {
28-
// It must have the '=' sign after credential_offer otherwise the uri will get split at openid_credential_offer
29-
arrayTypeProperties: uri.includes('credential_offer_uri=')
30-
? ['credential_configuration_ids', 'credential_offer_uri=']
31-
: ['credential_configuration_ids', 'credential_offer='],
32-
requiredProperties: uri.includes('credential_offer_uri=') ? ['credential_offer_uri='] : ['credential_offer='],
33-
}) as CredentialOfferV1_0_13;
27+
const version = determineSpecVersionFromURI(uri)
28+
let credentialOffer: CredentialOffer
29+
if (uri.includes('credential_offer_uri')) { // FIXME deduplicate
30+
const uriObj = getURIComponentsAsArray(uri) as unknown as Record<string, string> // FIXME
31+
const credentialOfferUri = uriObj['credential_offer_uri']
32+
const response = await fetch(credentialOfferUri)
33+
if (!(response && response.status >= 200 && response.status < 400)) {
34+
return Promise.reject(`the credential offer URI endpoint call was not successful. http code ${response.status} - reason ${response.statusText}`)
35+
}
36+
37+
if (response.headers.get('Content-Type')?.startsWith('application/json') === false) {
38+
return Promise.reject('the credential offer URI endpoint did not return content type application/json')
39+
}
40+
credentialOffer = decodeJsonProperties(await response.json()) as CredentialOfferV1_0_11 | CredentialOfferV1_0_13
41+
} else {
42+
credentialOffer = convertURIToJsonObject(uri, {
43+
// It must have the '=' sign after credential_offer otherwise the uri will get split at openid_credential_offer
44+
arrayTypeProperties: uri.includes('credential_offer_uri=')
45+
? ['credential_configuration_ids', 'credential_offer_uri=']
46+
: ['credential_configuration_ids', 'credential_offer='],
47+
requiredProperties: uri.includes('credential_offer_uri=') ? ['credential_offer_uri='] : ['credential_offer=']
48+
}) as CredentialOfferV1_0_13
49+
}
3450
if (credentialOffer?.credential_offer_uri === undefined && !credentialOffer?.credential_offer) {
35-
throw Error('Either a credential_offer or credential_offer_uri should be present in ' + uri);
51+
throw Error('Either a credential_offer or credential_offer_uri should be present in ' + uri)
3652
}
3753

3854
const request = await toUniformCredentialOfferRequest(credentialOffer, {

packages/issuer-rest/lib/oid4vci-api-functions.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,11 +97,11 @@ export function getIssueStatusEndpoint<DIDDoc extends object>(router: Router, is
9797
}
9898

9999
export function getIssuePayloadEndpoint<DIDDoc extends object>(router: Router, issuer: VcIssuer<DIDDoc>, opts: IGetIssueStatusEndpointOpts) : string {
100-
const path = determinePath(opts.baseUrl, opts?.path ?? '/webapp/credential-offer-payload', { stripBasePath: true })
100+
const path = determinePath(opts.baseUrl, opts?.path ?? '/credential-offers/:id', { stripBasePath: true })
101101
LOG.log(`[OID4VCI] getIssuePayloadEndpoint endpoint enabled at ${path}`)
102102
router.get(path, async (request: Request, response: Response) => {
103103
try {
104-
const { id } = request.query
104+
const { id } = request.params
105105
if (!id) {
106106
return sendErrorResponse(response, 404, {
107107
error: 'invalid_request',

packages/issuer/lib/VcIssuer.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ import {
6666
CredentialSignerCallback
6767
} from './types'
6868

69+
import uuid from 'short-uuid'
70+
6971
import { LOG } from './index'
7072

7173
export class VcIssuer<DIDDoc extends object> {
@@ -228,12 +230,14 @@ export class VcIssuer<DIDDoc extends object> {
228230
if (!this.uris) {
229231
throw Error('No URI state manager set, whilst apparently credential offer URIs are being used')
230232
}
231-
credentialOfferObject.credential_offer_uri = opts.credentialOfferUri ?? `${issuerPayloadUri}?id=${preAuthorizedCode}` // TODO how is this going to work with auth code flow?
233+
const credentialOfferCorrelationId = uuid.uuid() // TODO allow to be supplied
234+
credentialOfferObject.credential_offer_uri = opts.credentialOfferUri ?? `${issuerPayloadUri}/${credentialOfferCorrelationId}` // TODO how is this going to work with auth code flow?
232235
await this.uris.set(credentialOfferObject.credential_offer_uri, {
233236
uri: credentialOfferObject.credential_offer_uri,
234237
createdAt: createdAt,
235238
preAuthorizedCode,
236239
issuerState,
240+
credentialOfferCorrelationId
237241
})
238242
}
239243

packages/issuer/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
"@sphereon/oid4vc-common": "workspace:*",
1414
"@sphereon/oid4vci-common": "workspace:*",
1515
"@sphereon/ssi-types": "0.32.1-next.113",
16-
"uuid": "^9.0.0"
16+
"uuid": "^9.0.0",
17+
"short-uuid": "^4.2.2"
1718
},
1819
"peerDependencies": {
1920
"awesome-qr": "^2.1.5-rc.0"

packages/oid4vci-common/lib/functions/Encoding.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ export function convertURIToJsonObject(uri: string, opts?: DecodeURIAsJsonOpts):
9797
return decodeJsonProperties(uriComponents);
9898
}
9999

100-
function decodeJsonProperties(parts: string[] | string[][]): unknown {
100+
export function decodeJsonProperties(parts: string[] | string[][]): unknown {
101101
const json: { [s: string]: unknown } | ArrayLike<unknown> = {};
102102
for (const key in parts) {
103103
const value = parts[key];
@@ -133,7 +133,7 @@ function decodeJsonProperties(parts: string[] | string[][]): unknown {
133133
* @param {string} uri uri
134134
* @param {string[]} [arrayTypes] array of string containing array like keys
135135
*/
136-
function getURIComponentsAsArray(uri: string, arrayTypes?: string[]): string[] | string[][] {
136+
export function getURIComponentsAsArray(uri: string, arrayTypes?: string[]): string[] | string[][] {
137137
const parts = uri.includes('?') ? uri.split('?')[1] : uri.includes('://') ? uri.split('://')[1] : uri;
138138
const json: string[] | string[][] = [];
139139
const dict: string[] = parts.split('&');

packages/oid4vci-common/lib/types/StateManager.types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export interface URIState extends StateType {
4343
issuerState?: string; //todo: Probably good to hash it here, since it would come in from the client and we could match the hash and thus use the client value
4444
preAuthorizedCode?: string; //todo: Probably good to hash it here, since it would come in from the client and we could match the hash and thus use the client value
4545
uri: string; //todo: Probably good to hash it here, since it would come in from the client and we could match the hash and thus use the client value
46+
credentialOfferCorrelationId?: string;
4647
}
4748

4849
export interface IssueStatusResponse {

pnpm-lock.yaml

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)