@sphereon/oid4vci-client 0.8.1 → 0.8.2-next.26
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 +9 -8
- package/dist/AccessTokenClient.d.ts.map +1 -1
- package/dist/AccessTokenClient.js +8 -8
- package/dist/AccessTokenClient.js.map +1 -1
- package/dist/AuthorizationDetailsBuilder.d.ts.map +1 -1
- package/dist/CredentialRequestClient.d.ts.map +1 -1
- package/dist/CredentialRequestClient.js +36 -36
- package/dist/CredentialRequestClient.js.map +1 -1
- package/dist/CredentialRequestClientBuilder.d.ts +6 -0
- package/dist/CredentialRequestClientBuilder.d.ts.map +1 -1
- package/dist/CredentialRequestClientBuilder.js +10 -1
- package/dist/CredentialRequestClientBuilder.js.map +1 -1
- package/dist/OpenID4VCIClient.d.ts +15 -5
- package/dist/OpenID4VCIClient.d.ts.map +1 -1
- package/dist/OpenID4VCIClient.js +111 -41
- package/dist/OpenID4VCIClient.js.map +1 -1
- package/dist/ProofOfPossessionBuilder.d.ts +3 -1
- package/dist/ProofOfPossessionBuilder.d.ts.map +1 -1
- package/dist/ProofOfPossessionBuilder.js +5 -0
- package/dist/ProofOfPossessionBuilder.js.map +1 -1
- package/dist/functions/ProofUtil.d.ts +2 -1
- package/dist/functions/ProofUtil.d.ts.map +1 -1
- package/dist/functions/ProofUtil.js +6 -4
- package/dist/functions/ProofUtil.js.map +1 -1
- package/lib/AccessTokenClient.ts +10 -8
- package/lib/AuthorizationDetailsBuilder.ts +2 -2
- package/lib/CredentialRequestClient.ts +42 -38
- package/lib/CredentialRequestClientBuilder.ts +21 -1
- package/lib/OpenID4VCIClient.ts +140 -42
- package/lib/ProofOfPossessionBuilder.ts +8 -0
- package/lib/__tests__/CredentialRequestClient.spec.ts +21 -9
- package/lib/__tests__/EBSIE2E.spec.test.ts +138 -0
- package/lib/__tests__/MattrE2E.spec.test.ts +1 -1
- package/lib/__tests__/MetadataClient.spec.ts +4 -1
- package/lib/__tests__/OpenID4VCIClient.spec.ts +5 -0
- package/lib/__tests__/SdJwt.spec.ts +161 -0
- package/lib/__tests__/SphereonE2E.spec.test.ts +169 -0
- package/lib/__tests__/data/VciDataFixtures.ts +14 -13
- package/lib/functions/ProofUtil.ts +18 -4
- package/package.json +10 -5
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { Alg, CodeChallengeMethod, Jwt } from '@sphereon/oid4vci-common';
|
|
2
|
+
import { toJwk } from '@sphereon/ssi-sdk-ext.key-utils';
|
|
3
|
+
import { CredentialMapper } from '@sphereon/ssi-types';
|
|
4
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
5
|
+
//@ts-ignore
|
|
6
|
+
import { from } from '@trust/keyto';
|
|
7
|
+
import { fetch } from 'cross-fetch';
|
|
8
|
+
import debug from 'debug';
|
|
9
|
+
import { base64url, importJWK, JWK, SignJWT } from 'jose';
|
|
10
|
+
import * as u8a from 'uint8arrays';
|
|
11
|
+
|
|
12
|
+
import { OpenID4VCIClient } from '..';
|
|
13
|
+
|
|
14
|
+
export const UNIT_TEST_TIMEOUT = 30000;
|
|
15
|
+
|
|
16
|
+
const ISSUER_URL = 'https://conformance-test.ebsi.eu/conformance/v3/issuer-mock';
|
|
17
|
+
const AUTH_URL = 'https://conformance-test.ebsi.eu/conformance/v3/auth-mock';
|
|
18
|
+
|
|
19
|
+
const jwk: JWK = {
|
|
20
|
+
alg: 'ES256',
|
|
21
|
+
use: 'sig',
|
|
22
|
+
kty: 'EC',
|
|
23
|
+
crv: 'P-256',
|
|
24
|
+
x: 'hUWYK06qFvdudydiqnEhVJhZ-73jcLtuzH8kIyNOSHE',
|
|
25
|
+
y: 'UZf7oUkJdo65SQekMD5ssiRclEimG2SmlsjXf3QwQJo',
|
|
26
|
+
d: 'zDeeo3K0Pk8dofeKcajvJYxMZ1vijx_cVDJQl1IpbAM',
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
console.log(`JWK (private/orig): ${JSON.stringify(jwk, null, 2)}`);
|
|
30
|
+
|
|
31
|
+
const privateKey = from(jwk, 'jwk').toString('blk', 'private');
|
|
32
|
+
const publicKey = from(jwk, 'jwk').toString('blk', 'public');
|
|
33
|
+
console.log(`Private key: ${privateKey}`);
|
|
34
|
+
console.log(`Public key: ${publicKey}`);
|
|
35
|
+
console.log(`Private key (b64): ${base64url.encode(u8a.fromString(privateKey, 'base16'))}`);
|
|
36
|
+
console.log(`JWK (private 2) ${JSON.stringify(toJwk(privateKey, 'Secp256r1', { isPrivateKey: true }))}`);
|
|
37
|
+
console.log(`JWK (public 2) ${JSON.stringify(toJwk(publicKey, 'Secp256r1', { isPrivateKey: false }))}`);
|
|
38
|
+
|
|
39
|
+
// const DID_METHOD = 'did:key'
|
|
40
|
+
const DID =
|
|
41
|
+
'did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9Kbrm54tL4pRrDDhR1QJ5RHPMXUq5MzYpZL2k35vya5eMiNxschNy9AJ74CC3MmcYiZJGZfyhWQ6qDgTVcDSHdquwPYvLDut383JbrgYdZYYSC2merTMgmQtUi3huYhaky1qE';
|
|
42
|
+
const DID_URL_ENCODED =
|
|
43
|
+
'did%3Akey%3Az2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9Kbrm54tL4pRrDDhR1QJ5RHPMXUq5MzYpZL2k35vya5eMiNxschNy9AJ74CC3MmcYiZJGZfyhWQ6qDgTVcDSHdquwPYvLDut383JbrgYdZYYSC2merTMgmQtUi3huYhaky1qE';
|
|
44
|
+
// const PRIVATE_KEY_HEX = '7dd923e40f4615ac496119f7e793cc2899e99b64b88ca8603db986700089532b'
|
|
45
|
+
|
|
46
|
+
// const PUBLIC_KEY_HEX =
|
|
47
|
+
// '04a23cb4c83901acc2eb0f852599610de0caeac260bf8ed05e7f902eaac0f9c8d74dd4841b94d13424d32af8ec0e9976db9abfa7e3a59e10d565c5d4d901b4be63'
|
|
48
|
+
|
|
49
|
+
// pub hex: 35e03477cb29f3ac518770dccd4e26e703cd21b9741c24b038170c377b0d99d9
|
|
50
|
+
// priv hex: 913466d1a38d1d8c0d3c0fb0fc3b633075085a31372bbd2a8022215a88d9d1e5
|
|
51
|
+
// const did = `did:key:z6Mki5ZwZKN1dBQprfJTikUvkDxrHijiiQngkWviMF5gw2Hv`;
|
|
52
|
+
const kid = `${DID}#z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9Kbrm54tL4pRrDDhR1QJ5RHPMXUq5MzYpZL2k35vya5eMiNxschNy9AJ74CC3MmcYiZJGZfyhWQ6qDgTVcDSHdquwPYvLDut383JbrgYdZYYSC2merTMgmQtUi3huYhaky1qE`;
|
|
53
|
+
|
|
54
|
+
// const jw = jose.importKey()
|
|
55
|
+
describe('OID4VCI-Client using Sphereon issuer should', () => {
|
|
56
|
+
async function test(credentialType: 'CTWalletCrossPreAuthorisedInTime' | 'CTWalletCrossAuthorisedInTime') {
|
|
57
|
+
debug.enable('*');
|
|
58
|
+
const offer = await getCredentialOffer(credentialType);
|
|
59
|
+
const client = await OpenID4VCIClient.fromURI({
|
|
60
|
+
uri: offer,
|
|
61
|
+
kid,
|
|
62
|
+
alg: Alg.ES256,
|
|
63
|
+
clientId: DID_URL_ENCODED,
|
|
64
|
+
});
|
|
65
|
+
expect(client.credentialOffer).toBeDefined();
|
|
66
|
+
expect(client.endpointMetadata).toBeDefined();
|
|
67
|
+
expect(client.getCredentialEndpoint()).toEqual(`${ISSUER_URL}/credential`);
|
|
68
|
+
expect(client.getAccessTokenEndpoint()).toEqual(`${AUTH_URL}/token`);
|
|
69
|
+
|
|
70
|
+
if (credentialType !== 'CTWalletCrossPreAuthorisedInTime') {
|
|
71
|
+
const url = client.createAuthorizationRequestUrl({
|
|
72
|
+
redirectUri: 'openid4vc%3A',
|
|
73
|
+
codeChallenge: 'mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs',
|
|
74
|
+
codeChallengeMethod: CodeChallengeMethod.SHA256,
|
|
75
|
+
});
|
|
76
|
+
const result = await fetch(url);
|
|
77
|
+
console.log(result.text());
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const accessToken = await client.acquireAccessToken({ pin: '0891' });
|
|
81
|
+
// console.log(accessToken);
|
|
82
|
+
expect(accessToken).toMatchObject({
|
|
83
|
+
expires_in: 86400,
|
|
84
|
+
// scope: 'GuestCredential',
|
|
85
|
+
token_type: 'Bearer',
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const format = 'jwt_vc';
|
|
89
|
+
const credentialResponse = await client.acquireCredentials({
|
|
90
|
+
credentialTypes: client.getCredentialOfferTypes()[0],
|
|
91
|
+
format,
|
|
92
|
+
proofCallbacks: {
|
|
93
|
+
signCallback: proofOfPossessionCallbackFunction,
|
|
94
|
+
},
|
|
95
|
+
kid,
|
|
96
|
+
});
|
|
97
|
+
console.log(JSON.stringify(credentialResponse, null, 2));
|
|
98
|
+
expect(credentialResponse.credential).toBeDefined();
|
|
99
|
+
const wrappedVC = CredentialMapper.toWrappedVerifiableCredential(credentialResponse.credential!);
|
|
100
|
+
expect(format.startsWith(wrappedVC.format)).toEqual(true);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Current conformance tests is not stable as changes are being applied it seems
|
|
104
|
+
|
|
105
|
+
it.skip(
|
|
106
|
+
'succeed in a full flow with the client using OpenID4VCI version 11 and jwt_vc_json',
|
|
107
|
+
async () => {
|
|
108
|
+
await test('CTWalletCrossPreAuthorisedInTime');
|
|
109
|
+
// await test('CTWalletCrossAuthorisedInTime');
|
|
110
|
+
},
|
|
111
|
+
UNIT_TEST_TIMEOUT,
|
|
112
|
+
);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
async function getCredentialOffer(credentialType: 'CTWalletCrossPreAuthorisedInTime' | 'CTWalletCrossAuthorisedInTime'): Promise<string> {
|
|
116
|
+
const credentialOffer = await fetch(
|
|
117
|
+
`https://conformance-test.ebsi.eu/conformance/v3/issuer-mock/initiate-credential-offer?credential_type=${credentialType}&client_id=${DID_URL_ENCODED}&credential_offer_endpoint=openid-credential-offer%3A%2F%2F`,
|
|
118
|
+
{
|
|
119
|
+
method: 'GET',
|
|
120
|
+
headers: {
|
|
121
|
+
Accept: 'application/json',
|
|
122
|
+
'Content-Type': 'application/json',
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
return await credentialOffer.text();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function proofOfPossessionCallbackFunction(args: Jwt, kid?: string): Promise<string> {
|
|
131
|
+
const importedJwk = await importJWK(jwk);
|
|
132
|
+
return await new SignJWT({ ...args.payload })
|
|
133
|
+
.setProtectedHeader({ ...args.header, kid: kid! })
|
|
134
|
+
.setIssuer(DID)
|
|
135
|
+
.setIssuedAt()
|
|
136
|
+
.setExpirationTime('2m')
|
|
137
|
+
.sign(importedJwk);
|
|
138
|
+
}
|
|
@@ -20,7 +20,7 @@ const jwk: JWK = {
|
|
|
20
20
|
// priv hex: 913466d1a38d1d8c0d3c0fb0fc3b633075085a31372bbd2a8022215a88d9d1e5
|
|
21
21
|
const did = `did:key:z6Mki5ZwZKN1dBQprfJTikUvkDxrHijiiQngkWviMF5gw2Hv`;
|
|
22
22
|
const kid = `${did}#z6Mki5ZwZKN1dBQprfJTikUvkDxrHijiiQngkWviMF5gw2Hv`;
|
|
23
|
-
describe('OID4VCI-Client using Mattr issuer should', () => {
|
|
23
|
+
describe.skip('OID4VCI-Client using Mattr issuer should', () => {
|
|
24
24
|
async function test(format: 'ldp_vc' | 'jwt_vc_json') {
|
|
25
25
|
const offer = await getCredentialOffer(format);
|
|
26
26
|
const client = await OpenID4VCIClient.fromURI({
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { getIssuerFromCredentialOfferPayload, WellKnownEndpoints } from '@sphereon/oid4vci-common';
|
|
2
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
3
|
+
// @ts-ignore
|
|
2
4
|
import nock from 'nock';
|
|
3
5
|
|
|
4
6
|
import { CredentialOfferClient } from '../CredentialOfferClient';
|
|
@@ -211,7 +213,8 @@ describe('Metadataclient with Walt-id should', () => {
|
|
|
211
213
|
});
|
|
212
214
|
});
|
|
213
215
|
|
|
214
|
-
|
|
216
|
+
// Spruce gives back 404's these days, so test is disabled
|
|
217
|
+
describe.skip('Metadataclient with SpruceId should', () => {
|
|
215
218
|
beforeAll(() => {
|
|
216
219
|
nock.cleanAll();
|
|
217
220
|
});
|
|
@@ -68,6 +68,11 @@ describe('OpenID4VCIClient should', () => {
|
|
|
68
68
|
expect(scope?.[0]).toBe('openid');
|
|
69
69
|
});
|
|
70
70
|
it('throw an error if no scope and no authorization_details is provided', async () => {
|
|
71
|
+
nock(MOCK_URL).get(/.*/).reply(200, {});
|
|
72
|
+
nock(MOCK_URL).get(WellKnownEndpoints.OAUTH_AS).reply(404, {});
|
|
73
|
+
nock(MOCK_URL).get(WellKnownEndpoints.OPENID_CONFIGURATION).reply(404, {});
|
|
74
|
+
// Use a client with issuer only to trigger the error
|
|
75
|
+
client = await OpenID4VCIClient.fromCredentialIssuer({ credentialIssuer: 'https://server.example.com' });
|
|
71
76
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
72
77
|
// @ts-ignore
|
|
73
78
|
client._endpointMetadata?.credentialIssuerMetadata.authorization_endpoint = `${MOCK_URL}v1/auth/authorize`;
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { AccessTokenRequest, CredentialRequestV1_0_11, CredentialSupportedSdJwtVc } from '@sphereon/oid4vci-common';
|
|
2
|
+
import nock from 'nock';
|
|
3
|
+
|
|
4
|
+
import { OpenID4VCIClient } from '..';
|
|
5
|
+
import { createAccessTokenResponse, IssuerMetadataBuilderV1_11, VcIssuerBuilder } from '../../../issuer';
|
|
6
|
+
|
|
7
|
+
export const UNIT_TEST_TIMEOUT = 30000;
|
|
8
|
+
|
|
9
|
+
const alg = 'ES256';
|
|
10
|
+
const jwk = { kty: 'EC', crv: 'P-256', x: 'zQOowIC1gWJtdddB5GAt4lau6Lt8Ihy771iAfam-1pc', y: 'cjD_7o3gdQ1vgiQy3_sMGs7WrwCMU9FQYimA3HxnMlw' };
|
|
11
|
+
|
|
12
|
+
const issuerMetadata = new IssuerMetadataBuilderV1_11()
|
|
13
|
+
.withCredentialIssuer('https://example.com')
|
|
14
|
+
.withCredentialEndpoint('https://credenital-endpoint.example.com')
|
|
15
|
+
.withTokenEndpoint('https://token-endpoint.example.com')
|
|
16
|
+
.addSupportedCredential({
|
|
17
|
+
format: 'vc+sd-jwt',
|
|
18
|
+
vct: 'SdJwtCredential',
|
|
19
|
+
id: 'SdJwtCredentialId',
|
|
20
|
+
})
|
|
21
|
+
.build();
|
|
22
|
+
|
|
23
|
+
const vcIssuer = new VcIssuerBuilder()
|
|
24
|
+
.withIssuerMetadata(issuerMetadata)
|
|
25
|
+
.withInMemoryCNonceState()
|
|
26
|
+
.withInMemoryCredentialOfferState()
|
|
27
|
+
.withInMemoryCredentialOfferURIState()
|
|
28
|
+
// TODO: see if we can construct an sd-jwt vc based on the input
|
|
29
|
+
.withCredentialSignerCallback(async () => {
|
|
30
|
+
return 'sd-jwt';
|
|
31
|
+
})
|
|
32
|
+
.withJWTVerifyCallback(() =>
|
|
33
|
+
Promise.resolve({
|
|
34
|
+
alg,
|
|
35
|
+
jwk,
|
|
36
|
+
jwt: {
|
|
37
|
+
header: {
|
|
38
|
+
typ: 'openid4vci-proof+jwt',
|
|
39
|
+
alg,
|
|
40
|
+
jwk,
|
|
41
|
+
},
|
|
42
|
+
payload: {
|
|
43
|
+
aud: issuerMetadata.credential_issuer,
|
|
44
|
+
iat: +new Date(),
|
|
45
|
+
nonce: 'a-c-nonce',
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
}),
|
|
49
|
+
)
|
|
50
|
+
.build();
|
|
51
|
+
|
|
52
|
+
describe('sd-jwt vc', () => {
|
|
53
|
+
beforeEach(() => {
|
|
54
|
+
nock.cleanAll();
|
|
55
|
+
});
|
|
56
|
+
afterEach(() => {
|
|
57
|
+
nock.cleanAll();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it(
|
|
61
|
+
'succeed with a full flow',
|
|
62
|
+
async () => {
|
|
63
|
+
const offerUri = await vcIssuer.createCredentialOfferURI({
|
|
64
|
+
grants: {
|
|
65
|
+
'urn:ietf:params:oauth:grant-type:pre-authorized_code': {
|
|
66
|
+
'pre-authorized_code': '123',
|
|
67
|
+
user_pin_required: false,
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
credentials: ['SdJwtCredentialId'],
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
nock(vcIssuer.issuerMetadata.credential_issuer).get('/.well-known/openid-credential-issuer').reply(200, JSON.stringify(issuerMetadata));
|
|
74
|
+
nock(vcIssuer.issuerMetadata.credential_issuer).get('/.well-known/openid-configuration').reply(404);
|
|
75
|
+
nock(vcIssuer.issuerMetadata.credential_issuer).get('/.well-known/oauth-authorization-server').reply(404);
|
|
76
|
+
|
|
77
|
+
expect(offerUri.uri).toEqual(
|
|
78
|
+
'openid-credential-offer://?credential_offer=%7B%22grants%22%3A%7B%22urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Apre-authorized_code%22%3A%7B%22pre-authorized_code%22%3A%22123%22%2C%22user_pin_required%22%3Afalse%7D%7D%2C%22credentials%22%3A%5B%22SdJwtCredentialId%22%5D%2C%22credential_issuer%22%3A%22https%3A%2F%2Fexample.com%22%7D',
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const client = await OpenID4VCIClient.fromURI({
|
|
82
|
+
uri: offerUri.uri,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
expect(client.credentialOffer?.credential_offer).toEqual({
|
|
86
|
+
credential_issuer: 'https://example.com',
|
|
87
|
+
credentials: ['SdJwtCredentialId'],
|
|
88
|
+
grants: {
|
|
89
|
+
'urn:ietf:params:oauth:grant-type:pre-authorized_code': {
|
|
90
|
+
'pre-authorized_code': '123',
|
|
91
|
+
user_pin_required: false,
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const supported = client.getCredentialsSupported(true, 'vc+sd-jwt');
|
|
97
|
+
expect(supported).toEqual([
|
|
98
|
+
{
|
|
99
|
+
vct: 'SdJwtCredential',
|
|
100
|
+
format: 'vc+sd-jwt',
|
|
101
|
+
id: 'SdJwtCredentialId',
|
|
102
|
+
},
|
|
103
|
+
]);
|
|
104
|
+
|
|
105
|
+
const offered = supported[0] as CredentialSupportedSdJwtVc;
|
|
106
|
+
|
|
107
|
+
nock(issuerMetadata.token_endpoint as string)
|
|
108
|
+
.post('/')
|
|
109
|
+
.reply(200, async (_, body: string) => {
|
|
110
|
+
const parsedBody = Object.fromEntries(body.split('&').map((x) => x.split('=')));
|
|
111
|
+
return createAccessTokenResponse(parsedBody as AccessTokenRequest, {
|
|
112
|
+
credentialOfferSessions: vcIssuer.credentialOfferSessions,
|
|
113
|
+
accessTokenIssuer: 'https://issuer.example.com',
|
|
114
|
+
cNonces: vcIssuer.cNonces,
|
|
115
|
+
cNonce: 'a-c-nonce',
|
|
116
|
+
accessTokenSignerCallback: async () => 'ey.val.ue',
|
|
117
|
+
tokenExpiresIn: 500,
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
await client.acquireAccessToken({});
|
|
122
|
+
|
|
123
|
+
nock(issuerMetadata.credential_endpoint as string)
|
|
124
|
+
.post('/')
|
|
125
|
+
.reply(200, async (_, body) =>
|
|
126
|
+
vcIssuer.issueCredential({
|
|
127
|
+
credentialRequest: body as CredentialRequestV1_0_11,
|
|
128
|
+
credential: {
|
|
129
|
+
vct: 'Hello',
|
|
130
|
+
iss: 'did:example:123',
|
|
131
|
+
iat: 123,
|
|
132
|
+
// Defines what can be disclosed (optional)
|
|
133
|
+
__disclosureFrame: {
|
|
134
|
+
name: true,
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
newCNonce: 'new-c-nonce',
|
|
138
|
+
}),
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
const credentials = await client.acquireCredentials({
|
|
142
|
+
credentialTypes: [offered.vct],
|
|
143
|
+
format: 'vc+sd-jwt',
|
|
144
|
+
alg,
|
|
145
|
+
jwk,
|
|
146
|
+
proofCallbacks: {
|
|
147
|
+
// When using sd-jwt for real, this jwt should include a jwk
|
|
148
|
+
signCallback: async () => 'ey.ja.ja',
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
expect(credentials).toEqual({
|
|
153
|
+
c_nonce: 'new-c-nonce',
|
|
154
|
+
c_nonce_expires_in: 300000,
|
|
155
|
+
credential: 'sd-jwt',
|
|
156
|
+
format: 'vc+sd-jwt',
|
|
157
|
+
});
|
|
158
|
+
},
|
|
159
|
+
UNIT_TEST_TIMEOUT,
|
|
160
|
+
);
|
|
161
|
+
});
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import * as crypto from 'crypto';
|
|
2
|
+
|
|
3
|
+
import { Alg, Jwt, ProofOfPossessionCallbacks } from '@sphereon/oid4vci-common';
|
|
4
|
+
import { CredentialMapper } from '@sphereon/ssi-types';
|
|
5
|
+
import * as didts from '@transmute/did-key.js';
|
|
6
|
+
import { fetch } from 'cross-fetch';
|
|
7
|
+
import debug from 'debug';
|
|
8
|
+
import { importJWK, JWK, SignJWT } from 'jose';
|
|
9
|
+
import { v4 } from 'uuid';
|
|
10
|
+
|
|
11
|
+
import { OpenID4VCIClient } from '..';
|
|
12
|
+
|
|
13
|
+
export const UNIT_TEST_TIMEOUT = 30000;
|
|
14
|
+
|
|
15
|
+
const ISSUER_URL = 'https://ssi.sphereon.com/pf3';
|
|
16
|
+
|
|
17
|
+
const jwk: JWK = {
|
|
18
|
+
crv: 'Ed25519',
|
|
19
|
+
d: 'kTRm0aONHYwNPA-w_DtjMHUIWjE3K70qgCIhWojZ0eU',
|
|
20
|
+
x: 'NeA0d8sp86xRh3DczU4m5wPNIbl0HCSwOBcMN3sNmdk',
|
|
21
|
+
kty: 'OKP',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// pub hex: 35e03477cb29f3ac518770dccd4e26e703cd21b9741c24b038170c377b0d99d9
|
|
25
|
+
// priv hex: 913466d1a38d1d8c0d3c0fb0fc3b633075085a31372bbd2a8022215a88d9d1e5
|
|
26
|
+
const did = `did:key:z6Mki5ZwZKN1dBQprfJTikUvkDxrHijiiQngkWviMF5gw2Hv`;
|
|
27
|
+
const kid = `${did}#z6Mki5ZwZKN1dBQprfJTikUvkDxrHijiiQngkWviMF5gw2Hv`;
|
|
28
|
+
describe('OID4VCI-Client using Sphereon issuer should', () => {
|
|
29
|
+
async function test(format: 'ldp_vc' | 'jwt_vc_json') {
|
|
30
|
+
debug.enable('*');
|
|
31
|
+
const offer = await getCredentialOffer(format);
|
|
32
|
+
const client = await OpenID4VCIClient.fromURI({
|
|
33
|
+
uri: offer.uri,
|
|
34
|
+
kid,
|
|
35
|
+
alg: Alg.EdDSA,
|
|
36
|
+
});
|
|
37
|
+
expect(client.credentialOffer).toBeDefined();
|
|
38
|
+
expect(client.endpointMetadata).toBeDefined();
|
|
39
|
+
expect(client.getCredentialEndpoint()).toEqual(`${ISSUER_URL}/credentials`);
|
|
40
|
+
expect(client.getAccessTokenEndpoint()).toEqual(`${ISSUER_URL}/token`);
|
|
41
|
+
|
|
42
|
+
const accessToken = await client.acquireAccessToken();
|
|
43
|
+
// console.log(accessToken);
|
|
44
|
+
expect(accessToken).toMatchObject({
|
|
45
|
+
expires_in: 300,
|
|
46
|
+
// scope: 'GuestCredential',
|
|
47
|
+
token_type: 'bearer',
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const credentialResponse = await client.acquireCredentials({
|
|
51
|
+
credentialTypes: 'GuestCredential',
|
|
52
|
+
format,
|
|
53
|
+
proofCallbacks: {
|
|
54
|
+
signCallback: proofOfPossessionCallbackFunction,
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
expect(credentialResponse.credential).toBeDefined();
|
|
58
|
+
const wrappedVC = CredentialMapper.toWrappedVerifiableCredential(credentialResponse.credential!);
|
|
59
|
+
expect(format.startsWith(wrappedVC.format)).toEqual(true);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
xit(
|
|
63
|
+
'succeed in a full flow with the client using OpenID4VCI version 11 and ldp_vc',
|
|
64
|
+
async () => {
|
|
65
|
+
await test('ldp_vc');
|
|
66
|
+
},
|
|
67
|
+
UNIT_TEST_TIMEOUT,
|
|
68
|
+
);
|
|
69
|
+
it(
|
|
70
|
+
'succeed in a full flow with the client using OpenID4VCI version 11 and jwt_vc_json',
|
|
71
|
+
async () => {
|
|
72
|
+
await test('jwt_vc_json');
|
|
73
|
+
},
|
|
74
|
+
UNIT_TEST_TIMEOUT,
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
interface CreateCredentialOfferResponse {
|
|
79
|
+
uri: string;
|
|
80
|
+
userPinRequired: boolean;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function getCredentialOffer(format: 'ldp_vc' | 'jwt_vc_json'): Promise<CreateCredentialOfferResponse> {
|
|
84
|
+
const credentialOffer = await fetch('https://ssi.sphereon.com/pf3/webapp/credential-offers', {
|
|
85
|
+
method: 'post',
|
|
86
|
+
headers: {
|
|
87
|
+
Accept: 'application/json',
|
|
88
|
+
'Content-Type': 'application/json',
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
//make sure to serialize your JSON body
|
|
92
|
+
body: JSON.stringify({
|
|
93
|
+
credentials: ['GuestCredential'],
|
|
94
|
+
grants: {
|
|
95
|
+
'urn:ietf:params:oauth:grant-type:pre-authorized_code': {
|
|
96
|
+
'pre-authorized_code': v4().substring(0, 10),
|
|
97
|
+
user_pin_required: false,
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
credentialDataSupplierInput: { firstName: 'Hello', lastName: 'World', email: 'hello.world@example.com' },
|
|
101
|
+
}),
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
return (await credentialOffer.json()) as CreateCredentialOfferResponse;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function proofOfPossessionCallbackFunction(args: Jwt, kid?: string): Promise<string> {
|
|
108
|
+
const importedJwk = await importJWK(jwk, 'EdDSA');
|
|
109
|
+
return await new SignJWT({ ...args.payload })
|
|
110
|
+
.setProtectedHeader({ ...args.header, kid: kid! })
|
|
111
|
+
.setIssuer(kid!)
|
|
112
|
+
.setIssuedAt()
|
|
113
|
+
.setExpirationTime('2h')
|
|
114
|
+
.sign(importedJwk);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
describe('ismapolis bug report #63, https://github.com/Sphereon-Opensource/OID4VC-demo/issues/63, should', () => {
|
|
118
|
+
it('work as expected provided a correct JWT is supplied', async () => {
|
|
119
|
+
debug.enable('*');
|
|
120
|
+
const { uri } = await getCredentialOffer('jwt_vc_json');
|
|
121
|
+
const client = await OpenID4VCIClient.fromURI({ uri: uri, clientId: 'test-clientID' });
|
|
122
|
+
const metadata = await client.retrieveServerMetadata();
|
|
123
|
+
console.log(JSON.stringify(metadata));
|
|
124
|
+
|
|
125
|
+
//2. Adquire acces token from authorization server endpoint
|
|
126
|
+
|
|
127
|
+
const accessToken = await client.acquireAccessToken({});
|
|
128
|
+
console.log(`Access token: ${JSON.stringify(accessToken)}`);
|
|
129
|
+
|
|
130
|
+
//3. Create DID needed for later proof of possession
|
|
131
|
+
const { keys, didDocument } = await didts.jwk.generate({
|
|
132
|
+
type: 'secp256k1', // 'P-256', 'P-384', 'X25519', 'secp256k1'
|
|
133
|
+
accept: 'application/did+json',
|
|
134
|
+
secureRandom: () => {
|
|
135
|
+
return crypto.randomBytes(32);
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
const edPrivateKey = await importJWK(keys[0].privateKeyJwk);
|
|
139
|
+
|
|
140
|
+
async function signCallback(args: Jwt, kid?: string): Promise<string> {
|
|
141
|
+
if (!args.payload.aud) {
|
|
142
|
+
throw Error('aud required');
|
|
143
|
+
} else if (!kid) {
|
|
144
|
+
throw Error('kid required');
|
|
145
|
+
}
|
|
146
|
+
return await new SignJWT({ ...args.payload })
|
|
147
|
+
.setProtectedHeader({ alg: args.header.alg, kid, typ: 'openid4vci-proof+jwt' })
|
|
148
|
+
.setIssuedAt()
|
|
149
|
+
.setIssuer(kid)
|
|
150
|
+
.setAudience(args.payload.aud)
|
|
151
|
+
.setExpirationTime('2h')
|
|
152
|
+
.sign(edPrivateKey);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const callbacks: ProofOfPossessionCallbacks<never> = {
|
|
156
|
+
signCallback: signCallback,
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const credentialResponse = await client.acquireCredentials({
|
|
160
|
+
credentialTypes: 'GuestCredential',
|
|
161
|
+
proofCallbacks: callbacks,
|
|
162
|
+
format: 'jwt_vc_json',
|
|
163
|
+
alg: Alg.ES256K,
|
|
164
|
+
kid: didDocument.verificationMethod[0].id,
|
|
165
|
+
jti: v4(),
|
|
166
|
+
});
|
|
167
|
+
console.log(JSON.stringify(credentialResponse.credential));
|
|
168
|
+
});
|
|
169
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CredentialSupportedFormatV1_0_08, IssuerCredentialSubjectDisplay, IssuerMetadataV1_0_08 } from '@sphereon/oid4vci-common';
|
|
2
2
|
import { ICredentialStatus, W3CVerifiableCredential } from '@sphereon/ssi-types';
|
|
3
3
|
|
|
4
4
|
export function getMockData(issuerName: string): IssuerMockData | null {
|
|
@@ -42,7 +42,8 @@ export interface IssuerMockData {
|
|
|
42
42
|
url: string;
|
|
43
43
|
deeplink: string;
|
|
44
44
|
request: {
|
|
45
|
-
types
|
|
45
|
+
types?: [string];
|
|
46
|
+
type?: string;
|
|
46
47
|
format: 'jwt_vc' | 'ldp_vc' | 'jwt_vc_json-ld' | string;
|
|
47
48
|
proof: {
|
|
48
49
|
proof_type: 'jwt' | string;
|
|
@@ -110,8 +111,8 @@ const mockData: VciMockDataStructure = {
|
|
|
110
111
|
deeplink:
|
|
111
112
|
'openid-initiate-issuance://?issuer=https%3A%2F%2Fngi%2Doidc4vci%2Dtest%2Espruceid%2Exyz&credential_type=OpenBadgeCredential&pre-authorized_code=eyJhbGciOiJFUzI1NiJ9.eyJjcmVkZW50aWFsX3R5cGUiOlsiT3BlbkJhZGdlQ3JlZGVudGlhbCJdLCJleHAiOiIyMDIzLTA0LTIwVDA5OjA0OjM2WiIsIm5vbmNlIjoibWFibmVpT0VSZVB3V3BuRFFweEt3UnRsVVRFRlhGUEwifQ.qOZRPN8sTv_knhp7WaWte2-aDULaPZX--2i9unF6QDQNUllqDhvxgIHMDCYHCV8O2_Gj-T2x1J84fDMajE3asg&user_pin_required=false',
|
|
112
113
|
request: {
|
|
113
|
-
|
|
114
|
-
format: '
|
|
114
|
+
type: 'OpenBadgeCredential',
|
|
115
|
+
format: 'jwt_vc',
|
|
115
116
|
proof: {
|
|
116
117
|
proof_type: 'jwt',
|
|
117
118
|
jwt: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksiLCJraWQiOiJkaWQ6andrOmV5SmhiR2NpT2lKRlV6STFOa3NpTENKMWMyVWlPaUp6YVdjaUxDSnJkSGtpT2lKRlF5SXNJbU55ZGlJNkluTmxZM0F5TlRack1TSXNJbmdpT2lKclpuVmpTa0V0VEhKck9VWjBPRmx5TFVkMlQzSmpia3N3YjNkc2RqUlhNblUwU3pJeFNHZHZTVlIzSWl3aWVTSTZJalozY0ZCUE1rOUNRVXBTU0ZFMVRXdEtXVlJaV0dsQlJFUXdOMU5OTlV0amVXcDNYMkUzVUUxWmVGa2lmUSMwIn0.eyJhdWQiOiJodHRwczovL25naS1vaWRjNHZjaS10ZXN0LnNwcnVjZWlkLnh5eiIsImlhdCI6MTY4MTkxMTA2MC45NDIsImV4cCI6MTY4MTkxMTcyMC45NDIsImlzcyI6InNwaGVyZW9uOnNzaS13YWxsZXQiLCJqdGkiOiJhNjA4MzMxZi02ZmE0LTQ0ZjAtYWNkZWY5NmFjMjdmNmQ3MCJ9.NwF3_41gwnlIdd_6Uk9CczeQHzIQt6UcvTT5Cxv72j9S1vNwiY9annA2kLsjsTiR5-WMBdUhJCO7wYCtZ15mxw',
|
|
@@ -357,7 +358,7 @@ const mockData: VciMockDataStructure = {
|
|
|
357
358
|
url: 'https://jff.walt.id/issuer-api/default/oidc/credential',
|
|
358
359
|
request: {
|
|
359
360
|
types: ['OpenBadgeCredential'],
|
|
360
|
-
format: '
|
|
361
|
+
format: 'jwt_vc',
|
|
361
362
|
proof: {
|
|
362
363
|
proof_type: 'jwt',
|
|
363
364
|
jwt: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksiLCJraWQiOiJkaWQ6andrOmV5SmhiR2NpT2lKRlV6STFOa3NpTENKMWMyVWlPaUp6YVdjaUxDSnJkSGtpT2lKRlF5SXNJbU55ZGlJNkluTmxZM0F5TlRack1TSXNJbmdpT2lKclpuVmpTa0V0VEhKck9VWjBPRmx5TFVkMlQzSmpia3N3YjNkc2RqUlhNblUwU3pJeFNHZHZTVlIzSWl3aWVTSTZJalozY0ZCUE1rOUNRVXBTU0ZFMVRXdEtXVlJaV0dsQlJFUXdOMU5OTlV0amVXcDNYMkUzVUUxWmVGa2lmUSMwIn0.eyJhdWQiOiJodHRwczovL2pmZi53YWx0LmlkL2lzc3Vlci1hcGkvZGVmYXVsdC9vaWRjLyIsImlhdCI6MTY4MTkxMTk0Mi4yMzgsImV4cCI6MTY4MTkxMjYwMi4yMzgsIm5vbmNlIjoiZjA2YTMxMDUtYTJlZC00NGZjLTk1NGItNGEyNTk3MDM0OTNiIiwiaXNzIjoic3BoZXJlb246c3NpLXdhbGxldCIsImp0aSI6IjA1OWM3ODA5LTlmOGYtNGE3ZS1hZDI4YTNhMTNhMGIzNmViIn0.RfiWyybxpe3nkx3b0yIsqDHQtvB1WwhDW4t0X-kijy2dsSfv2cYhSEmAzs1shg7OV4EW8fSzt_Te79xiVl6jCw',
|
|
@@ -514,7 +515,7 @@ const mockData: VciMockDataStructure = {
|
|
|
514
515
|
types: ['PermanentResidentCard'],
|
|
515
516
|
binding_methods_supported: ['did'],
|
|
516
517
|
cryptographic_suites_supported: ['Ed25519Signature2018'],
|
|
517
|
-
} as
|
|
518
|
+
} as CredentialSupportedFormatV1_0_08,
|
|
518
519
|
},
|
|
519
520
|
},
|
|
520
521
|
AcademicAward: {
|
|
@@ -525,7 +526,7 @@ const mockData: VciMockDataStructure = {
|
|
|
525
526
|
types: ['AcademicAward'],
|
|
526
527
|
binding_methods_supported: ['did'],
|
|
527
528
|
cryptographic_suites_supported: ['Ed25519Signature2018'],
|
|
528
|
-
} as
|
|
529
|
+
} as CredentialSupportedFormatV1_0_08,
|
|
529
530
|
},
|
|
530
531
|
},
|
|
531
532
|
LearnerProfile: {
|
|
@@ -536,7 +537,7 @@ const mockData: VciMockDataStructure = {
|
|
|
536
537
|
types: ['LearnerProfile'],
|
|
537
538
|
binding_methods_supported: ['did'],
|
|
538
539
|
cryptographic_suites_supported: ['Ed25519Signature2018'],
|
|
539
|
-
} as
|
|
540
|
+
} as CredentialSupportedFormatV1_0_08,
|
|
540
541
|
},
|
|
541
542
|
},
|
|
542
543
|
OpenBadgeCredential: {
|
|
@@ -547,7 +548,7 @@ const mockData: VciMockDataStructure = {
|
|
|
547
548
|
types: ['OpenBadgeCredential'],
|
|
548
549
|
binding_methods_supported: ['did'],
|
|
549
550
|
cryptographic_suites_supported: ['Ed25519Signature2018'],
|
|
550
|
-
} as
|
|
551
|
+
} as CredentialSupportedFormatV1_0_08,
|
|
551
552
|
},
|
|
552
553
|
},
|
|
553
554
|
},
|
|
@@ -573,8 +574,8 @@ const mockData: VciMockDataStructure = {
|
|
|
573
574
|
'openid-initiate-issuance://?issuer=https://launchpad.mattrlabs.com&credential_type=OpenBadgeCredential&pre-authorized_code=g0UCOj6RAN5AwHU6gczm_GzB4_lH6GW39Z0Dl2DOOiO',
|
|
574
575
|
url: 'https://launchpad.vii.electron.mattrlabs.io/oidc/v1/auth/credential',
|
|
575
576
|
request: {
|
|
576
|
-
|
|
577
|
-
format: '
|
|
577
|
+
type: 'OpenBadgeCredential',
|
|
578
|
+
format: 'ldp_vc',
|
|
578
579
|
proof: {
|
|
579
580
|
proof_type: 'jwt',
|
|
580
581
|
jwt: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImRpZDprZXk6ejZNa3AxM3N6QUFMVFN0cDV1OGtMcnl5YW5vYWtrVWtFUGZXazdvOHY3dms0RW1KI3o2TWtwMTNzekFBTFRTdHA1dThrTHJ5eWFub2Fra1VrRVBmV2s3bzh2N3ZrNEVtSiJ9.eyJhdWQiOiJodHRwczovL2xhdW5jaHBhZC5tYXR0cmxhYnMuY29tIiwiaWF0IjoxNjgxOTE0NDgyLjUxOSwiZXhwIjoxNjgxOTE1MTQyLjUxOSwiaXNzIjoic3BoZXJlb246c3NpLXdhbGxldCIsImp0aSI6ImI5NDY1ZGE5LTY4OGYtNDdjNi04MjUwNDA0ZGNiOWI5Y2E5In0.uQ8ewOfIjy_1p_Gk6PjeEWccBJnjOca1pwbTWiCAFMQX9wlIsfeUdGtXUoHjH5_PQtpwytodx7WU456_CT9iBQ',
|
|
@@ -687,8 +688,8 @@ const mockData: VciMockDataStructure = {
|
|
|
687
688
|
'openid-initiate-issuance://?issuer=https://oidc4vc.diwala.io&credential_type=OpenBadgeCredential&pre-authorized_code=eyJhbGciOiJIUzI1NiJ9.eyJjcmVkZW50aWFsX3R5cGUiOiJPcGVuQmFkZ2VDcmVkZW50aWFsIiwiZXhwIjoxNjgxOTg0NDY3fQ.fEAHKz2nuWfiYHw406iNxr-81pWkNkbi31bWsYSf6Ng',
|
|
688
689
|
url: 'https://oidc4vc.diwala.io/credential',
|
|
689
690
|
request: {
|
|
690
|
-
|
|
691
|
-
format: '
|
|
691
|
+
type: 'OpenBadgeCredential',
|
|
692
|
+
format: 'ldp_vc',
|
|
692
693
|
proof: {
|
|
693
694
|
proof_type: 'jwt',
|
|
694
695
|
jwt: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImRpZDprZXk6ejZNa3AxM3N6QUFMVFN0cDV1OGtMcnl5YW5vYWtrVWtFUGZXazdvOHY3dms0RW1KI3o2TWtwMTNzekFBTFRTdHA1dThrTHJ5eWFub2Fra1VrRVBmV2s3bzh2N3ZrNEVtSiJ9.eyJhdWQiOiJodHRwczovL29pZGM0dmMuZGl3YWxhLmlvIiwiaWF0IjoxNjgxOTE1MDk1LjIwMiwiZXhwIjoxNjgxOTE1NzU1LjIwMiwiaXNzIjoic3BoZXJlb246c3NpLXdhbGxldCIsImp0aSI6IjYxN2MwM2EzLTM3MTUtNGJlMy1hYjkxNzM4MTlmYzYxNTYzIn0.KA-cHjecaYp9FSaWHkz5cqtNyhBIVT_0I7cJnpHn03T4UWFvdhjhn8Hpe-BU247enFyWOWJ6v3NQZyZgle7xBA',
|
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
import {
|
|
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';
|
|
2
13
|
import Debug from 'debug';
|
|
3
14
|
|
|
4
15
|
const debug = Debug('sphereon:openid4vci:token');
|
|
@@ -61,6 +72,7 @@ const partiallyValidateJWS = (jws: string): void => {
|
|
|
61
72
|
export interface JwtProps {
|
|
62
73
|
typ?: Typ;
|
|
63
74
|
kid?: string;
|
|
75
|
+
jwk?: JWK;
|
|
64
76
|
issuer?: string;
|
|
65
77
|
clientId?: string;
|
|
66
78
|
alg?: string;
|
|
@@ -76,7 +88,8 @@ const createJWT = (jwtProps?: JwtProps, existingJwt?: Jwt): Jwt => {
|
|
|
76
88
|
const nonce = getJwtProperty<string>('nonce', false, jwtProps?.nonce, existingJwt?.payload?.nonce); // Officially this is required, but some implementations don't have it
|
|
77
89
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
78
90
|
const alg = getJwtProperty<string>('alg', false, jwtProps?.alg, existingJwt?.header?.alg, 'ES256')!;
|
|
79
|
-
const kid = getJwtProperty<string>('kid',
|
|
91
|
+
const kid = getJwtProperty<string>('kid', false, jwtProps?.kid, existingJwt?.header?.kid);
|
|
92
|
+
const jwk = getJwtProperty<BaseJWK>('jwk', false, jwtProps?.jwk, existingJwt?.header?.jwk);
|
|
80
93
|
const jwt: Partial<Jwt> = existingJwt ? existingJwt : {};
|
|
81
94
|
const now = +new Date();
|
|
82
95
|
const jwtPayload: Partial<JWTPayload> = {
|
|
@@ -92,6 +105,7 @@ const createJWT = (jwtProps?: JwtProps, existingJwt?: Jwt): Jwt => {
|
|
|
92
105
|
typ,
|
|
93
106
|
alg,
|
|
94
107
|
kid,
|
|
108
|
+
jwk,
|
|
95
109
|
};
|
|
96
110
|
return {
|
|
97
111
|
payload: { ...jwt.payload, ...jwtPayload },
|
|
@@ -99,8 +113,8 @@ const createJWT = (jwtProps?: JwtProps, existingJwt?: Jwt): Jwt => {
|
|
|
99
113
|
};
|
|
100
114
|
};
|
|
101
115
|
|
|
102
|
-
const getJwtProperty = <T>(propertyName: string, required: boolean, option?: string, jwtProperty?: T, defaultValue?: T): T | undefined => {
|
|
103
|
-
if (option && jwtProperty && option !== jwtProperty) {
|
|
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) {
|
|
104
118
|
throw Error(`Cannot have a property '${propertyName}' with value '${option}' and different JWT value '${jwtProperty}' at the same time`);
|
|
105
119
|
}
|
|
106
120
|
let result = (jwtProperty ? jwtProperty : option) as T | undefined;
|