@sphereon/oid4vci-client 0.10.3 → 0.10.4-next.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -0
- package/dist/AccessTokenClient.d.ts.map +1 -1
- package/dist/AccessTokenClient.js +11 -16
- package/dist/AccessTokenClient.js.map +1 -1
- package/dist/CredentialOfferClient.d.ts.map +1 -1
- package/dist/CredentialOfferClient.js +8 -7
- package/dist/CredentialOfferClient.js.map +1 -1
- package/dist/CredentialRequestClient.d.ts +14 -3
- package/dist/CredentialRequestClient.d.ts.map +1 -1
- package/dist/CredentialRequestClient.js +18 -19
- package/dist/CredentialRequestClient.js.map +1 -1
- package/dist/CredentialRequestClientBuilder.d.ts +3 -1
- package/dist/CredentialRequestClientBuilder.d.ts.map +1 -1
- package/dist/CredentialRequestClientBuilder.js +4 -0
- package/dist/CredentialRequestClientBuilder.js.map +1 -1
- package/dist/MetadataClient.d.ts.map +1 -1
- package/dist/MetadataClient.js +1 -2
- package/dist/MetadataClient.js.map +1 -1
- package/dist/OpenID4VCIClient.d.ts +7 -2
- package/dist/OpenID4VCIClient.d.ts.map +1 -1
- package/dist/OpenID4VCIClient.js +15 -1
- package/dist/OpenID4VCIClient.js.map +1 -1
- package/dist/ProofOfPossessionBuilder.d.ts.map +1 -1
- package/dist/ProofOfPossessionBuilder.js +1 -2
- package/dist/ProofOfPossessionBuilder.js.map +1 -1
- package/dist/functions/index.d.ts +1 -3
- package/dist/functions/index.d.ts.map +1 -1
- package/dist/functions/index.js +1 -3
- package/dist/functions/index.js.map +1 -1
- package/dist/functions/notifications.d.ts +4 -0
- package/dist/functions/notifications.d.ts.map +1 -0
- package/dist/functions/notifications.js +41 -0
- package/dist/functions/notifications.js.map +1 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +5 -0
- package/dist/types/index.js.map +1 -1
- package/lib/AccessTokenClient.ts +12 -13
- package/lib/CredentialOfferClient.ts +7 -5
- package/lib/CredentialRequestClient.ts +26 -7
- package/lib/CredentialRequestClientBuilder.ts +7 -0
- package/lib/MetadataClient.ts +1 -2
- package/lib/OpenID4VCIClient.ts +25 -2
- package/lib/ProofOfPossessionBuilder.ts +1 -2
- package/lib/__tests__/CredentialRequestClient.spec.ts +5 -1
- package/lib/__tests__/HttpUtils.spec.ts +1 -1
- package/lib/__tests__/IT.spec.ts +42 -3
- package/lib/__tests__/IssuanceInitiation.spec.ts +22 -0
- package/lib/__tests__/ProofOfPossessionBuilder.spec.ts +1 -1
- package/lib/__tests__/SdJwt.spec.ts +3 -1
- package/lib/__tests__/SphereonE2E.spec.test.ts +2 -1
- package/lib/functions/index.ts +1 -3
- package/lib/functions/notifications.ts +32 -0
- package/lib/types/index.ts +6 -0
- package/package.json +4 -4
- package/dist/functions/ProofUtil.d.ts +0 -30
- package/dist/functions/ProofUtil.d.ts.map +0 -1
- package/dist/functions/ProofUtil.js +0 -106
- package/dist/functions/ProofUtil.js.map +0 -1
- package/lib/functions/ProofUtil.ts +0 -128
package/lib/OpenID4VCIClient.ts
CHANGED
|
@@ -11,12 +11,15 @@ import {
|
|
|
11
11
|
CredentialSupported,
|
|
12
12
|
DefaultURISchemes,
|
|
13
13
|
EndpointMetadataResult,
|
|
14
|
+
ExperimentalSubjectIssuance,
|
|
14
15
|
getClientIdFromCredentialOfferPayload,
|
|
15
16
|
getIssuerFromCredentialOfferPayload,
|
|
16
17
|
getSupportedCredentials,
|
|
17
18
|
getTypesFromCredentialSupported,
|
|
18
19
|
JWK,
|
|
19
20
|
KID_JWK_X5C_ERROR,
|
|
21
|
+
NotificationRequest,
|
|
22
|
+
NotificationResult,
|
|
20
23
|
OID4VCICredentialFormat,
|
|
21
24
|
OpenId4VCIVersion,
|
|
22
25
|
PKCEOpts,
|
|
@@ -29,10 +32,12 @@ import Debug from 'debug';
|
|
|
29
32
|
import { AccessTokenClient } from './AccessTokenClient';
|
|
30
33
|
import { createAuthorizationRequestUrl } from './AuthorizationCodeClient';
|
|
31
34
|
import { CredentialOfferClient } from './CredentialOfferClient';
|
|
35
|
+
import { CredentialRequestOpts } from './CredentialRequestClient';
|
|
32
36
|
import { CredentialRequestClientBuilder } from './CredentialRequestClientBuilder';
|
|
33
37
|
import { MetadataClient } from './MetadataClient';
|
|
34
38
|
import { ProofOfPossessionBuilder } from './ProofOfPossessionBuilder';
|
|
35
39
|
import { generateMissingPKCEOpts } from './functions/AuthorizationUtil';
|
|
40
|
+
import { sendNotification } from './functions/notifications';
|
|
36
41
|
|
|
37
42
|
const debug = Debug('sphereon:oid4vci');
|
|
38
43
|
|
|
@@ -339,7 +344,8 @@ export class OpenID4VCIClient {
|
|
|
339
344
|
jti?: string;
|
|
340
345
|
deferredCredentialAwait?: boolean;
|
|
341
346
|
deferredCredentialIntervalInMS?: number;
|
|
342
|
-
|
|
347
|
+
experimentalHolderIssuanceSupported?: boolean;
|
|
348
|
+
}): Promise<CredentialResponse & { access_token: string }> {
|
|
343
349
|
if ([jwk, kid].filter((v) => v !== undefined).length > 1) {
|
|
344
350
|
throw new Error(KID_JWK_X5C_ERROR + `. jwk: ${jwk !== undefined}, kid: ${kid !== undefined}`);
|
|
345
351
|
}
|
|
@@ -362,6 +368,7 @@ export class OpenID4VCIClient {
|
|
|
362
368
|
|
|
363
369
|
requestBuilder.withTokenFromResponse(this.accessTokenResponse);
|
|
364
370
|
requestBuilder.withDeferredCredentialAwait(deferredCredentialAwait ?? false, deferredCredentialIntervalInMS);
|
|
371
|
+
let subjectIssuance: ExperimentalSubjectIssuance | undefined;
|
|
365
372
|
if (this.endpointMetadata?.credentialIssuerMetadata) {
|
|
366
373
|
const metadata = this.endpointMetadata.credentialIssuerMetadata;
|
|
367
374
|
const types = Array.isArray(credentialTypes) ? credentialTypes : [credentialTypes];
|
|
@@ -376,6 +383,9 @@ export class OpenID4VCIClient {
|
|
|
376
383
|
(types.length === 1 && (types[0] === supportedCredential.id || subTypes.includes(types[0])))
|
|
377
384
|
) {
|
|
378
385
|
typeSupported = true;
|
|
386
|
+
if (supportedCredential.credential_subject_issuance) {
|
|
387
|
+
subjectIssuance = { credential_subject_issuance: supportedCredential.credential_subject_issuance };
|
|
388
|
+
}
|
|
379
389
|
}
|
|
380
390
|
});
|
|
381
391
|
|
|
@@ -391,6 +401,10 @@ export class OpenID4VCIClient {
|
|
|
391
401
|
}
|
|
392
402
|
// todo: Format check? We might end up with some disjoint type / format combinations supported by the server
|
|
393
403
|
}
|
|
404
|
+
if (subjectIssuance) {
|
|
405
|
+
requestBuilder.withSubjectIssuance(subjectIssuance);
|
|
406
|
+
}
|
|
407
|
+
|
|
394
408
|
const credentialRequestClient = requestBuilder.build();
|
|
395
409
|
const proofBuilder = ProofOfPossessionBuilder.fromAccessTokenResponse({
|
|
396
410
|
accessTokenResponse: this.accessTokenResponse,
|
|
@@ -418,6 +432,7 @@ export class OpenID4VCIClient {
|
|
|
418
432
|
credentialTypes,
|
|
419
433
|
context,
|
|
420
434
|
format,
|
|
435
|
+
subjectIssuance,
|
|
421
436
|
});
|
|
422
437
|
if (response.errorBody) {
|
|
423
438
|
debug(`Credential request error:\r\n${JSON.stringify(response.errorBody)}`);
|
|
@@ -434,7 +449,7 @@ export class OpenID4VCIClient {
|
|
|
434
449
|
} for issuer ${this.getIssuer()} failed as there was no success response body`,
|
|
435
450
|
);
|
|
436
451
|
}
|
|
437
|
-
return response.successBody;
|
|
452
|
+
return { ...response.successBody, access_token: response.access_token };
|
|
438
453
|
}
|
|
439
454
|
|
|
440
455
|
public async exportState(): Promise<string> {
|
|
@@ -457,6 +472,14 @@ export class OpenID4VCIClient {
|
|
|
457
472
|
});
|
|
458
473
|
}
|
|
459
474
|
|
|
475
|
+
public async sendNotification(
|
|
476
|
+
credentialRequestOpts: CredentialRequestOpts,
|
|
477
|
+
request: NotificationRequest,
|
|
478
|
+
accessToken?: string,
|
|
479
|
+
): Promise<NotificationResult> {
|
|
480
|
+
return sendNotification(credentialRequestOpts, request, accessToken ?? this.accessTokenResponse.access_token);
|
|
481
|
+
}
|
|
482
|
+
|
|
460
483
|
getCredentialOfferTypes(): string[][] {
|
|
461
484
|
if (!this.credentialOffer) {
|
|
462
485
|
return [];
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AccessTokenResponse,
|
|
3
3
|
Alg,
|
|
4
|
+
createProofOfPossession,
|
|
4
5
|
EndpointMetadata,
|
|
5
6
|
JWK,
|
|
6
7
|
Jwt,
|
|
@@ -12,8 +13,6 @@ import {
|
|
|
12
13
|
Typ,
|
|
13
14
|
} from '@sphereon/oid4vci-common';
|
|
14
15
|
|
|
15
|
-
import { createProofOfPossession } from './functions';
|
|
16
|
-
|
|
17
16
|
export class ProofOfPossessionBuilder<DIDDoc> {
|
|
18
17
|
private readonly proof?: ProofOfPossession;
|
|
19
18
|
private readonly callbacks?: ProofOfPossessionCallbacks<DIDDoc>;
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// Walt uses a self signed cert
|
|
2
|
+
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
|
|
3
|
+
|
|
1
4
|
import { KeyObject } from 'crypto';
|
|
2
5
|
|
|
3
6
|
import {
|
|
@@ -164,7 +167,8 @@ describe('Credential Request Client with Walt.id ', () => {
|
|
|
164
167
|
afterEach(() => {
|
|
165
168
|
nock.cleanAll();
|
|
166
169
|
});
|
|
167
|
-
|
|
170
|
+
// Walt id has cert issue
|
|
171
|
+
it.skip('should have correct metadata endpoints', async function () {
|
|
168
172
|
nock.cleanAll();
|
|
169
173
|
const WALT_IRR_URI =
|
|
170
174
|
'openid-initiate-issuance://?issuer=https%3A%2F%2Fjff.walt.id%2Fissuer-api%2Foidc%2F&credential_type=OpenBadgeCredential&pre-authorized_code=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhOTUyZjUxNi1jYWVmLTQ4YjMtODIxYy00OTRkYzgyNjljZjAiLCJwcmUtYXV0aG9yaXplZCI6dHJ1ZX0.YE5DlalcLC2ChGEg47CQDaN1gTxbaQqSclIVqsSAUHE&user_pin_required=false';
|
package/lib/__tests__/IT.spec.ts
CHANGED
|
@@ -50,7 +50,13 @@ describe('OID4VCI-Client should', () => {
|
|
|
50
50
|
const INITIATE_QR =
|
|
51
51
|
'openid-initiate-issuance://?issuer=https%3A%2F%2Fissuer.research.identiproof.io&credential_type=OpenBadgeCredentialUrl&pre-authorized_code=4jLs9xZHEfqcoow0kHE7d1a8hUk6Sy-5bVSV2MqBUGUgiFFQi-ImL62T-FmLIo8hKA1UdMPH0lM1xAgcFkJfxIw9L-lI3mVs0hRT8YVwsEM1ma6N3wzuCdwtMU4bcwKp&user_pin_required=true';
|
|
52
52
|
const OFFER_QR =
|
|
53
|
-
'openid-credential-offer
|
|
53
|
+
'openid-credential-offer://?credential_offer=%7B%22credential_issuer%22%3A%22https%3A%2F%2Fissuer.research.identiproof.io%22%2C%22credentials%22%3A%5B%7B%22format%22%3A%22jwt_vc_json%22%2C%22types%22%3A%5B%22VerifiableCredential%22%2C%22UniversityDegreeCredential%22%5D%7D%5D%2C%22grants%22%3A%7B%22urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Apre-authorized_code%22%3A%7B%22pre-authorized_code%22%3A%22adhjhdjajkdkhjhdj%22%2C%22user_pin_required%22%3Atrue%7D%7D%7D';
|
|
54
|
+
const HTTPS_INITIATE_QR =
|
|
55
|
+
'https://issuer.research.identiproof.io?issuer=https%3A%2F%2Fissuer.research.identiproof.io&credential_type=OpenBadgeCredentialUrl&pre-authorized_code=4jLs9xZHEfqcoow0kHE7d1a8hUk6Sy-5bVSV2MqBUGUgiFFQi-ImL62T-FmLIo8hKA1UdMPH0lM1xAgcFkJfxIw9L-lI3mVs0hRT8YVwsEM1ma6N3wzuCdwtMU4bcwKp&user_pin_required=true';
|
|
56
|
+
const HTTPS_OFFER_QR_AUTHORIZATION_CODE =
|
|
57
|
+
'https://issuer.research.identiproof.io?credential_offer=%7B%22credential_issuer%22%3A%22https%3A%2F%2Fissuer.research.identiproof.io%22%2C%22credentials%22%3A%5B%7B%22format%22%3A%22jwt_vc_json%22%2C%22types%22%3A%5B%22VerifiableCredential%22%2C%22UniversityDegreeCredential%22%5D%7D%5D%2C%22grants%22%3A%7B%22authorization_code%22%3A%7B%22issuer_state%22%3A%22eyJhbGciOiJSU0Et...FYUaBy%22%7D%7D%7D';
|
|
58
|
+
const HTTPS_OFFER_QR_PRE_AUTHORIZED =
|
|
59
|
+
'https://issuer.research.identiproof.io?credential_offer=%7B%22credential_issuer%22%3A%22https%3A%2F%2Fissuer.research.identiproof.io%22%2C%22credentials%22%3A%5B%7B%22format%22%3A%22jwt_vc_json%22%2C%22types%22%3A%5B%22VerifiableCredential%22%2C%22UniversityDegreeCredential%22%5D%7D%5D%2C%22grants%22%3A%7B%22urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Apre-authorized_code%22%3A%7B%22pre-authorized_code%22%3A%22adhjhdjajkdkhjhdj%22%2C%22user_pin_required%22%3Atrue%7D%7D%7D';
|
|
54
60
|
|
|
55
61
|
function succeedWithAFullFlowWithClientSetup() {
|
|
56
62
|
nock(IDENTIPROOF_ISSUER_URL).get('/.well-known/openid-credential-issuer').reply(200, JSON.stringify(IDENTIPROOF_OID4VCI_METADATA));
|
|
@@ -78,7 +84,7 @@ describe('OID4VCI-Client should', () => {
|
|
|
78
84
|
await assertionOfsucceedWithAFullFlowWithClient(client);
|
|
79
85
|
});
|
|
80
86
|
|
|
81
|
-
|
|
87
|
+
it('succeed with a full flow with the client using OpenID4VCI version 11 and deeplink', async () => {
|
|
82
88
|
succeedWithAFullFlowWithClientSetup();
|
|
83
89
|
const client = await OpenID4VCIClient.fromURI({
|
|
84
90
|
uri: OFFER_QR,
|
|
@@ -89,6 +95,39 @@ describe('OID4VCI-Client should', () => {
|
|
|
89
95
|
await assertionOfsucceedWithAFullFlowWithClient(client);
|
|
90
96
|
});
|
|
91
97
|
|
|
98
|
+
it('succeed with a full flow with the client using OpenID4VCI draft < 9 and https', async () => {
|
|
99
|
+
succeedWithAFullFlowWithClientSetup();
|
|
100
|
+
const client = await OpenID4VCIClient.fromURI({
|
|
101
|
+
uri: HTTPS_INITIATE_QR,
|
|
102
|
+
kid: 'did:example:ebfeb1f712ebc6f1c276e12ec21/keys/1',
|
|
103
|
+
alg: Alg.ES256,
|
|
104
|
+
clientId: 'test-clientId',
|
|
105
|
+
});
|
|
106
|
+
await assertionOfsucceedWithAFullFlowWithClient(client);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should succeed with a full flow with the client using OpenID4VCI draft > 11, https and authorization_code flow', async () => {
|
|
110
|
+
succeedWithAFullFlowWithClientSetup();
|
|
111
|
+
const client = await OpenID4VCIClient.fromURI({
|
|
112
|
+
uri: HTTPS_OFFER_QR_AUTHORIZATION_CODE,
|
|
113
|
+
kid: 'did:example:ebfeb1f712ebc6f1c276e12ec21/keys/1',
|
|
114
|
+
alg: Alg.ES256,
|
|
115
|
+
clientId: 'test-clientId',
|
|
116
|
+
});
|
|
117
|
+
await assertionOfsucceedWithAFullFlowWithClient(client);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('should succeed with a full flow with the client using OpenID4VCI draft > 11, https and preauthorized_code flow', async () => {
|
|
121
|
+
succeedWithAFullFlowWithClientSetup();
|
|
122
|
+
const client = await OpenID4VCIClient.fromURI({
|
|
123
|
+
uri: HTTPS_OFFER_QR_PRE_AUTHORIZED,
|
|
124
|
+
kid: 'did:example:ebfeb1f712ebc6f1c276e12ec21/keys/1',
|
|
125
|
+
alg: Alg.ES256,
|
|
126
|
+
clientId: 'test-clientId',
|
|
127
|
+
});
|
|
128
|
+
await assertionOfsucceedWithAFullFlowWithClient(client);
|
|
129
|
+
});
|
|
130
|
+
|
|
92
131
|
async function assertionOfsucceedWithAFullFlowWithClient(client: OpenID4VCIClient) {
|
|
93
132
|
expect(client.credentialOffer).toBeDefined();
|
|
94
133
|
expect(client.endpointMetadata).toBeDefined();
|
|
@@ -96,7 +135,7 @@ describe('OID4VCI-Client should', () => {
|
|
|
96
135
|
expect(client.getCredentialEndpoint()).toEqual('https://issuer.research.identiproof.io/credential');
|
|
97
136
|
expect(client.getAccessTokenEndpoint()).toEqual('https://auth.research.identiproof.io/oauth2/token');
|
|
98
137
|
|
|
99
|
-
const accessToken = await client.acquireAccessToken({ pin: '1234' });
|
|
138
|
+
const accessToken = await client.acquireAccessToken({ pin: '1234', code: 'ABCD' });
|
|
100
139
|
expect(accessToken).toEqual(mockedAccessTokenResponse);
|
|
101
140
|
|
|
102
141
|
const credentialResponse = await client.acquireCredentials({
|
|
@@ -58,4 +58,26 @@ describe('Issuance Initiation', () => {
|
|
|
58
58
|
expect(client.credential_offer.credential_issuer).toEqual('https://launchpad.vii.electron.mattrlabs.io');
|
|
59
59
|
expect(client.preAuthorizedCode).toEqual('UPZohaodPlLBnGsqB02n2tIupCIg8nKRRUEUHWA665X');
|
|
60
60
|
});
|
|
61
|
+
|
|
62
|
+
it('Should take an https url as input and return a Credential Offer', async () => {
|
|
63
|
+
const client = await CredentialOfferClient.fromURI(
|
|
64
|
+
'https://launchpad.vii.electron.mattrlabs.io?credential_offer=%7B%22credential_issuer%22%3A%22https%3A%2F%2Flaunchpad.vii.electron.mattrlabs.io%22%2C%22credentials%22%3A%5B%7B%22format%22%3A%22ldp_vc%22%2C%22types%22%3A%5B%22OpenBadgeCredential%22%5D%7D%5D%2C%22grants%22%3A%7B%22urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Apre-authorized_code%22%3A%7B%22pre-authorized_code%22%3A%22UPZohaodPlLBnGsqB02n2tIupCIg8nKRRUEUHWA665X%22%7D%7D%7D',
|
|
65
|
+
);
|
|
66
|
+
expect(client.version).toEqual(OpenId4VCIVersion.VER_1_0_11);
|
|
67
|
+
expect(client.baseUrl).toEqual('https://launchpad.vii.electron.mattrlabs.io');
|
|
68
|
+
expect(client.scheme).toEqual('https');
|
|
69
|
+
expect(client.credential_offer.credential_issuer).toEqual('https://launchpad.vii.electron.mattrlabs.io');
|
|
70
|
+
expect(client.preAuthorizedCode).toEqual('UPZohaodPlLBnGsqB02n2tIupCIg8nKRRUEUHWA665X');
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('Should take an http url as input and return a Credential Offer', async () => {
|
|
74
|
+
const client = await CredentialOfferClient.fromURI(
|
|
75
|
+
'http://launchpad.vii.electron.mattrlabs.io?credential_offer=%7B%22credential_issuer%22%3A%22http%3A%2F%2Flaunchpad.vii.electron.mattrlabs.io%22%2C%22credentials%22%3A%5B%7B%22format%22%3A%22ldp_vc%22%2C%22types%22%3A%5B%22OpenBadgeCredential%22%5D%7D%5D%2C%22grants%22%3A%7B%22urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Apre-authorized_code%22%3A%7B%22pre-authorized_code%22%3A%22UPZohaodPlLBnGsqB02n2tIupCIg8nKRRUEUHWA665X%22%7D%7D%7D',
|
|
76
|
+
);
|
|
77
|
+
expect(client.version).toEqual(OpenId4VCIVersion.VER_1_0_11);
|
|
78
|
+
expect(client.baseUrl).toEqual('http://launchpad.vii.electron.mattrlabs.io');
|
|
79
|
+
expect(client.scheme).toEqual('http');
|
|
80
|
+
expect(client.credential_offer.credential_issuer).toEqual('http://launchpad.vii.electron.mattrlabs.io');
|
|
81
|
+
expect(client.preAuthorizedCode).toEqual('UPZohaodPlLBnGsqB02n2tIupCIg8nKRRUEUHWA665X');
|
|
82
|
+
})
|
|
61
83
|
});
|
|
@@ -9,7 +9,7 @@ import { IDENTIPROOF_ISSUER_URL } from './MetadataMocks';
|
|
|
9
9
|
|
|
10
10
|
const jwt: Jwt = {
|
|
11
11
|
header: { alg: Alg.ES256, kid: 'did:example:ebfeb1f712ebc6f1c276e12ec21/keys/1', typ: 'jwt' },
|
|
12
|
-
payload: { iss: 'sphereon:wallet', nonce: 'tZignsnFbp', jti: 'tZignsnFbp223', aud: IDENTIPROOF_ISSUER_URL, iat: Date.now()/1000 },
|
|
12
|
+
payload: { iss: 'sphereon:wallet', nonce: 'tZignsnFbp', jti: 'tZignsnFbp223', aud: IDENTIPROOF_ISSUER_URL, iat: Date.now() / 1000 },
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
const kid = 'did:example:ebfeb1f712ebc6f1c276e12ec21/keys/1';
|
|
@@ -43,7 +43,7 @@ const vcIssuer = new VcIssuerBuilder()
|
|
|
43
43
|
},
|
|
44
44
|
payload: {
|
|
45
45
|
aud: issuerMetadata.credential_issuer,
|
|
46
|
-
iat: +new Date()/1000,
|
|
46
|
+
iat: +new Date() / 1000,
|
|
47
47
|
nonce: 'a-c-nonce',
|
|
48
48
|
},
|
|
49
49
|
},
|
|
@@ -152,6 +152,8 @@ describe('sd-jwt vc', () => {
|
|
|
152
152
|
});
|
|
153
153
|
|
|
154
154
|
expect(credentials).toEqual({
|
|
155
|
+
notification_id: expect.any(String),
|
|
156
|
+
access_token: 'ey.val.ue',
|
|
155
157
|
c_nonce: 'new-c-nonce',
|
|
156
158
|
c_nonce_expires_in: 300,
|
|
157
159
|
credential: 'sd-jwt',
|
|
@@ -115,7 +115,8 @@ async function proofOfPossessionCallbackFunction(args: Jwt, kid?: string): Promi
|
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
describe('ismapolis bug report #63, https://github.com/Sphereon-Opensource/OID4VC-demo/issues/63, should', () => {
|
|
118
|
-
|
|
118
|
+
// Sphereon infra is not working currently
|
|
119
|
+
it.skip('work as expected provided a correct JWT is supplied', async () => {
|
|
119
120
|
debug.enable('*');
|
|
120
121
|
const { uri } = await getCredentialOffer('jwt_vc_json');
|
|
121
122
|
const client = await OpenID4VCIClient.fromURI({ uri: uri, clientId: 'test-clientID' });
|
package/lib/functions/index.ts
CHANGED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { NotificationErrorResponse, NotificationRequest, NotificationResult, post } from '@sphereon/oid4vci-common';
|
|
2
|
+
|
|
3
|
+
import { CredentialRequestOpts } from '../CredentialRequestClient';
|
|
4
|
+
import { LOG } from '../types';
|
|
5
|
+
|
|
6
|
+
export async function sendNotification(
|
|
7
|
+
credentialRequestOpts: CredentialRequestOpts,
|
|
8
|
+
request: NotificationRequest,
|
|
9
|
+
accessToken?: string,
|
|
10
|
+
): Promise<NotificationResult> {
|
|
11
|
+
LOG.info(`Sending status notification event '${request.event}' for id ${request.notification_id}`);
|
|
12
|
+
if (!credentialRequestOpts.notificationEndpoint) {
|
|
13
|
+
throw Error(`Cannot send notification when no notification endpoint is provided`);
|
|
14
|
+
}
|
|
15
|
+
const token = accessToken ?? credentialRequestOpts.token;
|
|
16
|
+
const response = await post<NotificationErrorResponse>(credentialRequestOpts.notificationEndpoint, JSON.stringify(request), {
|
|
17
|
+
bearerToken: token,
|
|
18
|
+
});
|
|
19
|
+
const error = response.errorBody?.error !== undefined;
|
|
20
|
+
const result = {
|
|
21
|
+
error,
|
|
22
|
+
response: error ? await response.errorBody?.json() : undefined,
|
|
23
|
+
};
|
|
24
|
+
if (error) {
|
|
25
|
+
LOG.warning(
|
|
26
|
+
`Notification endpoint returned an error for event '${request.event}' and id ${request.notification_id}: ${await response.errorBody?.json()}`,
|
|
27
|
+
);
|
|
28
|
+
} else {
|
|
29
|
+
LOG.debug(`Notification endpoint returned success for event '${request.event}' and id ${request.notification_id}`);
|
|
30
|
+
}
|
|
31
|
+
return result;
|
|
32
|
+
}
|
package/lib/types/index.ts
CHANGED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { VCI_LOGGERS } from '@sphereon/oid4vci-common';
|
|
2
|
+
import { ISimpleLogger, LogMethod } from '@sphereon/ssi-types';
|
|
3
|
+
|
|
4
|
+
export const LOG: ISimpleLogger<string> = VCI_LOGGERS.options('sphereon:oid4vci:client', { methods: [LogMethod.EVENT, LogMethod.DEBUG_PKG] }).get(
|
|
5
|
+
'sphereon:oid4vci:client',
|
|
6
|
+
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sphereon/oid4vci-client",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.4-next.17+126f976",
|
|
4
4
|
"description": "OpenID for Verifiable Credential Issuance (OpenID4VCI) client",
|
|
5
5
|
"source": "lib/index.ts",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
"build": "tsc"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@sphereon/oid4vci-common": "0.10.
|
|
19
|
-
"@sphereon/ssi-types": "
|
|
18
|
+
"@sphereon/oid4vci-common": "0.10.4-next.17+126f976",
|
|
19
|
+
"@sphereon/ssi-types": "0.24.1-unstable.112",
|
|
20
20
|
"cross-fetch": "^3.1.8",
|
|
21
21
|
"debug": "^4.3.4"
|
|
22
22
|
},
|
|
@@ -69,5 +69,5 @@
|
|
|
69
69
|
"OIDC4VCI",
|
|
70
70
|
"OID4VCI"
|
|
71
71
|
],
|
|
72
|
-
"gitHead": "
|
|
72
|
+
"gitHead": "126f97644a247d3c86faea8b1bd96e68f8bcf52c"
|
|
73
73
|
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { JWK, Jwt, ProofOfPossession, ProofOfPossessionCallbacks, Typ } from '@sphereon/oid4vci-common';
|
|
2
|
-
/**
|
|
3
|
-
*
|
|
4
|
-
* - proofOfPossessionCallback: JWTSignerCallback
|
|
5
|
-
* Mandatory if you want to create (sign) ProofOfPossession
|
|
6
|
-
* - proofOfPossessionVerifierCallback?: JWTVerifyCallback
|
|
7
|
-
* If exists, verifies the ProofOfPossession
|
|
8
|
-
* - proofOfPossessionCallbackArgs: ProofOfPossessionCallbackArgs
|
|
9
|
-
* arguments needed for signing ProofOfPossession
|
|
10
|
-
* @param callbacks:
|
|
11
|
-
* - proofOfPossessionCallback: JWTSignerCallback
|
|
12
|
-
* Mandatory to create (sign) ProofOfPossession
|
|
13
|
-
* - proofOfPossessionVerifierCallback?: JWTVerifyCallback
|
|
14
|
-
* If exists, verifies the ProofOfPossession
|
|
15
|
-
* @param jwtProps
|
|
16
|
-
* @param existingJwt
|
|
17
|
-
* - Optional, clientId of the party requesting the credential
|
|
18
|
-
*/
|
|
19
|
-
export declare const createProofOfPossession: <DIDDoc>(callbacks: ProofOfPossessionCallbacks<DIDDoc>, jwtProps?: JwtProps, existingJwt?: Jwt) => Promise<ProofOfPossession>;
|
|
20
|
-
export interface JwtProps {
|
|
21
|
-
typ?: Typ;
|
|
22
|
-
kid?: string;
|
|
23
|
-
jwk?: JWK;
|
|
24
|
-
issuer?: string;
|
|
25
|
-
clientId?: string;
|
|
26
|
-
alg?: string;
|
|
27
|
-
jti?: string;
|
|
28
|
-
nonce?: string;
|
|
29
|
-
}
|
|
30
|
-
//# sourceMappingURL=ProofUtil.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ProofUtil.d.ts","sourceRoot":"","sources":["../../lib/functions/ProofUtil.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,GAAG,EAEH,GAAG,EAGH,iBAAiB,EACjB,0BAA0B,EAC1B,GAAG,EACJ,MAAM,0BAA0B,CAAC;AAKlC;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,uBAAuB,qEAEvB,QAAQ,gBACL,GAAG,KAChB,QAAQ,iBAAiB,CA0B3B,CAAC;AAQF,MAAM,WAAW,QAAQ;IACvB,GAAG,CAAC,EAAE,GAAG,CAAC;IACV,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,GAAG,CAAC;IACV,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB"}
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
-
};
|
|
14
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.createProofOfPossession = void 0;
|
|
16
|
-
const oid4vci_common_1 = require("@sphereon/oid4vci-common");
|
|
17
|
-
const debug_1 = __importDefault(require("debug"));
|
|
18
|
-
const debug = (0, debug_1.default)('sphereon:openid4vci:token');
|
|
19
|
-
/**
|
|
20
|
-
*
|
|
21
|
-
* - proofOfPossessionCallback: JWTSignerCallback
|
|
22
|
-
* Mandatory if you want to create (sign) ProofOfPossession
|
|
23
|
-
* - proofOfPossessionVerifierCallback?: JWTVerifyCallback
|
|
24
|
-
* If exists, verifies the ProofOfPossession
|
|
25
|
-
* - proofOfPossessionCallbackArgs: ProofOfPossessionCallbackArgs
|
|
26
|
-
* arguments needed for signing ProofOfPossession
|
|
27
|
-
* @param callbacks:
|
|
28
|
-
* - proofOfPossessionCallback: JWTSignerCallback
|
|
29
|
-
* Mandatory to create (sign) ProofOfPossession
|
|
30
|
-
* - proofOfPossessionVerifierCallback?: JWTVerifyCallback
|
|
31
|
-
* If exists, verifies the ProofOfPossession
|
|
32
|
-
* @param jwtProps
|
|
33
|
-
* @param existingJwt
|
|
34
|
-
* - Optional, clientId of the party requesting the credential
|
|
35
|
-
*/
|
|
36
|
-
const createProofOfPossession = (callbacks, jwtProps, existingJwt) => __awaiter(void 0, void 0, void 0, function* () {
|
|
37
|
-
if (!callbacks.signCallback) {
|
|
38
|
-
debug(`no jwt signer callback or arguments supplied!`);
|
|
39
|
-
throw new Error(oid4vci_common_1.BAD_PARAMS);
|
|
40
|
-
}
|
|
41
|
-
const signerArgs = createJWT(jwtProps, existingJwt);
|
|
42
|
-
const jwt = yield callbacks.signCallback(signerArgs, signerArgs.header.kid);
|
|
43
|
-
const proof = {
|
|
44
|
-
proof_type: 'jwt',
|
|
45
|
-
jwt,
|
|
46
|
-
};
|
|
47
|
-
try {
|
|
48
|
-
partiallyValidateJWS(jwt);
|
|
49
|
-
if (callbacks.verifyCallback) {
|
|
50
|
-
debug(`Calling supplied verify callback....`);
|
|
51
|
-
yield callbacks.verifyCallback({ jwt, kid: signerArgs.header.kid });
|
|
52
|
-
debug(`Supplied verify callback return success result`);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
catch (_a) {
|
|
56
|
-
debug(`JWS was not valid`);
|
|
57
|
-
throw new Error(oid4vci_common_1.JWS_NOT_VALID);
|
|
58
|
-
}
|
|
59
|
-
debug(`Proof of Possession JWT:\r\n${jwt}`);
|
|
60
|
-
return proof;
|
|
61
|
-
});
|
|
62
|
-
exports.createProofOfPossession = createProofOfPossession;
|
|
63
|
-
const partiallyValidateJWS = (jws) => {
|
|
64
|
-
if (jws.split('.').length !== 3 || !jws.startsWith('ey')) {
|
|
65
|
-
throw new Error(oid4vci_common_1.JWS_NOT_VALID);
|
|
66
|
-
}
|
|
67
|
-
};
|
|
68
|
-
const createJWT = (jwtProps, existingJwt) => {
|
|
69
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
70
|
-
const aud = getJwtProperty('aud', true, jwtProps === null || jwtProps === void 0 ? void 0 : jwtProps.issuer, (_a = existingJwt === null || existingJwt === void 0 ? void 0 : existingJwt.payload) === null || _a === void 0 ? void 0 : _a.aud);
|
|
71
|
-
const iss = getJwtProperty('iss', false, jwtProps === null || jwtProps === void 0 ? void 0 : jwtProps.clientId, (_b = existingJwt === null || existingJwt === void 0 ? void 0 : existingJwt.payload) === null || _b === void 0 ? void 0 : _b.iss);
|
|
72
|
-
const jti = getJwtProperty('jti', false, jwtProps === null || jwtProps === void 0 ? void 0 : jwtProps.jti, (_c = existingJwt === null || existingJwt === void 0 ? void 0 : existingJwt.payload) === null || _c === void 0 ? void 0 : _c.jti);
|
|
73
|
-
const typ = getJwtProperty('typ', true, jwtProps === null || jwtProps === void 0 ? void 0 : jwtProps.typ, (_d = existingJwt === null || existingJwt === void 0 ? void 0 : existingJwt.header) === null || _d === void 0 ? void 0 : _d.typ, 'jwt');
|
|
74
|
-
const nonce = getJwtProperty('nonce', false, jwtProps === null || jwtProps === void 0 ? void 0 : jwtProps.nonce, (_e = existingJwt === null || existingJwt === void 0 ? void 0 : existingJwt.payload) === null || _e === void 0 ? void 0 : _e.nonce); // Officially this is required, but some implementations don't have it
|
|
75
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
76
|
-
const alg = getJwtProperty('alg', false, jwtProps === null || jwtProps === void 0 ? void 0 : jwtProps.alg, (_f = existingJwt === null || existingJwt === void 0 ? void 0 : existingJwt.header) === null || _f === void 0 ? void 0 : _f.alg, 'ES256');
|
|
77
|
-
const kid = getJwtProperty('kid', false, jwtProps === null || jwtProps === void 0 ? void 0 : jwtProps.kid, (_g = existingJwt === null || existingJwt === void 0 ? void 0 : existingJwt.header) === null || _g === void 0 ? void 0 : _g.kid);
|
|
78
|
-
const jwk = getJwtProperty('jwk', false, jwtProps === null || jwtProps === void 0 ? void 0 : jwtProps.jwk, (_h = existingJwt === null || existingJwt === void 0 ? void 0 : existingJwt.header) === null || _h === void 0 ? void 0 : _h.jwk);
|
|
79
|
-
const jwt = existingJwt ? existingJwt : {};
|
|
80
|
-
const now = +new Date();
|
|
81
|
-
const jwtPayload = Object.assign(Object.assign({ aud, iat: (_k = (_j = jwt.payload) === null || _j === void 0 ? void 0 : _j.iat) !== null && _k !== void 0 ? _k : Math.round(now / 1000 - 60), exp: (_m = (_l = jwt.payload) === null || _l === void 0 ? void 0 : _l.exp) !== null && _m !== void 0 ? _m : Math.round(now / 1000 + 10 * 60), nonce }, (iss ? { iss } : {})), (jti ? { jti } : {}));
|
|
82
|
-
const jwtHeader = {
|
|
83
|
-
typ,
|
|
84
|
-
alg,
|
|
85
|
-
kid,
|
|
86
|
-
jwk,
|
|
87
|
-
};
|
|
88
|
-
return {
|
|
89
|
-
payload: Object.assign(Object.assign({}, jwt.payload), jwtPayload),
|
|
90
|
-
header: Object.assign(Object.assign({}, jwt.header), jwtHeader),
|
|
91
|
-
};
|
|
92
|
-
};
|
|
93
|
-
const getJwtProperty = (propertyName, required, option, jwtProperty, defaultValue) => {
|
|
94
|
-
if (typeof option === 'string' && option && jwtProperty && option !== jwtProperty) {
|
|
95
|
-
throw Error(`Cannot have a property '${propertyName}' with value '${option}' and different JWT value '${jwtProperty}' at the same time`);
|
|
96
|
-
}
|
|
97
|
-
let result = (jwtProperty ? jwtProperty : option);
|
|
98
|
-
if (!result) {
|
|
99
|
-
if (required) {
|
|
100
|
-
throw Error(`No ${propertyName} property provided either in a JWT or as option`);
|
|
101
|
-
}
|
|
102
|
-
result = defaultValue;
|
|
103
|
-
}
|
|
104
|
-
return result;
|
|
105
|
-
};
|
|
106
|
-
//# sourceMappingURL=ProofUtil.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ProofUtil.js","sourceRoot":"","sources":["../../lib/functions/ProofUtil.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
BAD_PARAMS,
|
|
3
|
-
BaseJWK,
|
|
4
|
-
JWK,
|
|
5
|
-
JWS_NOT_VALID,
|
|
6
|
-
Jwt,
|
|
7
|
-
JWTHeader,
|
|
8
|
-
JWTPayload,
|
|
9
|
-
ProofOfPossession,
|
|
10
|
-
ProofOfPossessionCallbacks,
|
|
11
|
-
Typ,
|
|
12
|
-
} from '@sphereon/oid4vci-common';
|
|
13
|
-
import Debug from 'debug';
|
|
14
|
-
|
|
15
|
-
const debug = Debug('sphereon:openid4vci:token');
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
*
|
|
19
|
-
* - proofOfPossessionCallback: JWTSignerCallback
|
|
20
|
-
* Mandatory if you want to create (sign) ProofOfPossession
|
|
21
|
-
* - proofOfPossessionVerifierCallback?: JWTVerifyCallback
|
|
22
|
-
* If exists, verifies the ProofOfPossession
|
|
23
|
-
* - proofOfPossessionCallbackArgs: ProofOfPossessionCallbackArgs
|
|
24
|
-
* arguments needed for signing ProofOfPossession
|
|
25
|
-
* @param callbacks:
|
|
26
|
-
* - proofOfPossessionCallback: JWTSignerCallback
|
|
27
|
-
* Mandatory to create (sign) ProofOfPossession
|
|
28
|
-
* - proofOfPossessionVerifierCallback?: JWTVerifyCallback
|
|
29
|
-
* If exists, verifies the ProofOfPossession
|
|
30
|
-
* @param jwtProps
|
|
31
|
-
* @param existingJwt
|
|
32
|
-
* - Optional, clientId of the party requesting the credential
|
|
33
|
-
*/
|
|
34
|
-
export const createProofOfPossession = async <DIDDoc>(
|
|
35
|
-
callbacks: ProofOfPossessionCallbacks<DIDDoc>,
|
|
36
|
-
jwtProps?: JwtProps,
|
|
37
|
-
existingJwt?: Jwt,
|
|
38
|
-
): Promise<ProofOfPossession> => {
|
|
39
|
-
if (!callbacks.signCallback) {
|
|
40
|
-
debug(`no jwt signer callback or arguments supplied!`);
|
|
41
|
-
throw new Error(BAD_PARAMS);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const signerArgs = createJWT(jwtProps, existingJwt);
|
|
45
|
-
const jwt = await callbacks.signCallback(signerArgs, signerArgs.header.kid);
|
|
46
|
-
const proof = {
|
|
47
|
-
proof_type: 'jwt',
|
|
48
|
-
jwt,
|
|
49
|
-
} as ProofOfPossession;
|
|
50
|
-
|
|
51
|
-
try {
|
|
52
|
-
partiallyValidateJWS(jwt);
|
|
53
|
-
if (callbacks.verifyCallback) {
|
|
54
|
-
debug(`Calling supplied verify callback....`);
|
|
55
|
-
await callbacks.verifyCallback({ jwt, kid: signerArgs.header.kid });
|
|
56
|
-
debug(`Supplied verify callback return success result`);
|
|
57
|
-
}
|
|
58
|
-
} catch {
|
|
59
|
-
debug(`JWS was not valid`);
|
|
60
|
-
throw new Error(JWS_NOT_VALID);
|
|
61
|
-
}
|
|
62
|
-
debug(`Proof of Possession JWT:\r\n${jwt}`);
|
|
63
|
-
return proof;
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
const partiallyValidateJWS = (jws: string): void => {
|
|
67
|
-
if (jws.split('.').length !== 3 || !jws.startsWith('ey')) {
|
|
68
|
-
throw new Error(JWS_NOT_VALID);
|
|
69
|
-
}
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
export interface JwtProps {
|
|
73
|
-
typ?: Typ;
|
|
74
|
-
kid?: string;
|
|
75
|
-
jwk?: JWK;
|
|
76
|
-
issuer?: string;
|
|
77
|
-
clientId?: string;
|
|
78
|
-
alg?: string;
|
|
79
|
-
jti?: string;
|
|
80
|
-
nonce?: string;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const createJWT = (jwtProps?: JwtProps, existingJwt?: Jwt): Jwt => {
|
|
84
|
-
const aud = getJwtProperty<string | string[]>('aud', true, jwtProps?.issuer, existingJwt?.payload?.aud);
|
|
85
|
-
const iss = getJwtProperty<string>('iss', false, jwtProps?.clientId, existingJwt?.payload?.iss);
|
|
86
|
-
const jti = getJwtProperty<string>('jti', false, jwtProps?.jti, existingJwt?.payload?.jti);
|
|
87
|
-
const typ = getJwtProperty<string>('typ', true, jwtProps?.typ, existingJwt?.header?.typ, 'jwt');
|
|
88
|
-
const nonce = getJwtProperty<string>('nonce', false, jwtProps?.nonce, existingJwt?.payload?.nonce); // Officially this is required, but some implementations don't have it
|
|
89
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
90
|
-
const alg = getJwtProperty<string>('alg', false, jwtProps?.alg, existingJwt?.header?.alg, 'ES256')!;
|
|
91
|
-
const kid = getJwtProperty<string>('kid', false, jwtProps?.kid, existingJwt?.header?.kid);
|
|
92
|
-
const jwk = getJwtProperty<BaseJWK>('jwk', false, jwtProps?.jwk, existingJwt?.header?.jwk);
|
|
93
|
-
const jwt: Partial<Jwt> = existingJwt ? existingJwt : {};
|
|
94
|
-
const now = +new Date();
|
|
95
|
-
const jwtPayload: Partial<JWTPayload> = {
|
|
96
|
-
aud,
|
|
97
|
-
iat: jwt.payload?.iat ?? Math.round(now / 1000 - 60), // Let's ensure we subtract 60 seconds for potential time offsets
|
|
98
|
-
exp: jwt.payload?.exp ?? Math.round(now / 1000 + 10 * 60),
|
|
99
|
-
nonce,
|
|
100
|
-
...(iss ? { iss } : {}),
|
|
101
|
-
...(jti ? { jti } : {}),
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
const jwtHeader: JWTHeader = {
|
|
105
|
-
typ,
|
|
106
|
-
alg,
|
|
107
|
-
kid,
|
|
108
|
-
jwk,
|
|
109
|
-
};
|
|
110
|
-
return {
|
|
111
|
-
payload: { ...jwt.payload, ...jwtPayload },
|
|
112
|
-
header: { ...jwt.header, ...jwtHeader },
|
|
113
|
-
};
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
const getJwtProperty = <T>(propertyName: string, required: boolean, option?: string | JWK, jwtProperty?: T, defaultValue?: T): T | undefined => {
|
|
117
|
-
if (typeof option === 'string' && option && jwtProperty && option !== jwtProperty) {
|
|
118
|
-
throw Error(`Cannot have a property '${propertyName}' with value '${option}' and different JWT value '${jwtProperty}' at the same time`);
|
|
119
|
-
}
|
|
120
|
-
let result = (jwtProperty ? jwtProperty : option) as T | undefined;
|
|
121
|
-
if (!result) {
|
|
122
|
-
if (required) {
|
|
123
|
-
throw Error(`No ${propertyName} property provided either in a JWT or as option`);
|
|
124
|
-
}
|
|
125
|
-
result = defaultValue;
|
|
126
|
-
}
|
|
127
|
-
return result;
|
|
128
|
-
};
|