@sphereon/oid4vci-client 0.2.0 → 0.4.1-next.285
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/LICENSE +201 -201
- package/README.md +494 -371
- package/dist/AccessTokenClient.d.ts +30 -0
- package/dist/AccessTokenClient.d.ts.map +1 -0
- package/dist/AccessTokenClient.js +222 -0
- package/dist/AccessTokenClient.js.map +1 -0
- package/dist/AuthorizationDetailsBuilder.d.ts +11 -0
- package/dist/AuthorizationDetailsBuilder.d.ts.map +1 -0
- package/dist/AuthorizationDetailsBuilder.js +44 -0
- package/dist/AuthorizationDetailsBuilder.js.map +1 -0
- package/dist/CredentialOfferClient.d.ts +10 -0
- package/dist/CredentialOfferClient.d.ts.map +1 -0
- package/dist/CredentialOfferClient.js +101 -0
- package/dist/CredentialOfferClient.js.map +1 -0
- package/dist/CredentialRequestClient.d.ts +33 -0
- package/dist/CredentialRequestClient.d.ts.map +1 -0
- package/dist/CredentialRequestClient.js +118 -0
- package/dist/CredentialRequestClient.js.map +1 -0
- package/dist/CredentialRequestClientBuilder.d.ts +34 -0
- package/dist/CredentialRequestClientBuilder.d.ts.map +1 -0
- package/dist/CredentialRequestClientBuilder.js +87 -0
- package/dist/CredentialRequestClientBuilder.js.map +1 -0
- package/dist/{main/lib/MetadataClient.d.ts → MetadataClient.d.ts} +39 -38
- package/dist/MetadataClient.d.ts.map +1 -0
- package/dist/MetadataClient.js +148 -0
- package/dist/MetadataClient.js.map +1 -0
- package/dist/OpenID4VCIClient.d.ts +75 -0
- package/dist/OpenID4VCIClient.d.ts.map +1 -0
- package/dist/OpenID4VCIClient.js +403 -0
- package/dist/OpenID4VCIClient.js.map +1 -0
- package/dist/ProofOfPossessionBuilder.d.ts +38 -0
- package/dist/ProofOfPossessionBuilder.d.ts.map +1 -0
- package/dist/ProofOfPossessionBuilder.js +129 -0
- package/dist/ProofOfPossessionBuilder.js.map +1 -0
- package/dist/functions/ProofUtil.d.ts +29 -0
- package/dist/functions/ProofUtil.d.ts.map +1 -0
- package/dist/functions/ProofUtil.js +104 -0
- package/dist/functions/ProofUtil.js.map +1 -0
- package/dist/functions/index.d.ts +4 -0
- package/dist/functions/index.d.ts.map +1 -0
- package/dist/{main → functions}/index.js +20 -18
- package/dist/functions/index.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/{main/lib/index.js → index.js} +25 -24
- package/dist/index.js.map +1 -0
- package/lib/AccessTokenClient.ts +249 -0
- package/lib/AuthorizationDetailsBuilder.ts +46 -0
- package/lib/CredentialOfferClient.ts +108 -0
- package/lib/CredentialRequestClient.ts +137 -0
- package/lib/CredentialRequestClientBuilder.ts +110 -0
- package/lib/MetadataClient.ts +147 -0
- package/lib/OpenID4VCIClient.ts +523 -0
- package/lib/ProofOfPossessionBuilder.ts +181 -0
- package/lib/__tests__/AccessTokenClient.spec.ts +225 -0
- package/lib/__tests__/AuthorizationDetailsBuilder.spec.ts +65 -0
- package/lib/__tests__/AuthzFlowType.spec.ts +39 -0
- package/lib/__tests__/CredentialRequestClient.spec.ts +291 -0
- package/lib/__tests__/CredentialRequestClientBuilder.spec.ts +121 -0
- package/lib/__tests__/HttpUtils.spec.ts +37 -0
- package/lib/__tests__/IT.spec.ts +173 -0
- package/lib/__tests__/IssuanceInitiation.spec.ts +48 -0
- package/lib/__tests__/JsonURIConversions.spec.ts +146 -0
- package/lib/__tests__/MetadataClient.spec.ts +203 -0
- package/lib/__tests__/MetadataMocks.ts +444 -0
- package/lib/__tests__/OpenID4VCIClient.spec.ts +166 -0
- package/lib/__tests__/OpenID4VCIClientPAR.spec.ts +112 -0
- package/lib/__tests__/ProofOfPossessionBuilder.spec.ts +110 -0
- package/lib/__tests__/data/VciDataFixtures.ts +744 -0
- package/lib/functions/ProofUtil.ts +120 -0
- package/lib/functions/index.ts +3 -0
- package/{dist/main/lib/index.d.ts → lib/index.ts} +8 -7
- package/package.json +68 -71
- package/CHANGELOG.md +0 -21
- package/dist/main/index.d.ts +0 -1
- package/dist/main/lib/AccessTokenClient.d.ts +0 -20
- package/dist/main/lib/AccessTokenClient.js +0 -141
- package/dist/main/lib/CredentialRequestClient.d.ts +0 -31
- package/dist/main/lib/CredentialRequestClient.js +0 -66
- package/dist/main/lib/CredentialRequestClientBuilder.d.ts +0 -21
- package/dist/main/lib/CredentialRequestClientBuilder.js +0 -56
- package/dist/main/lib/IssuanceInitiation.d.ts +0 -5
- package/dist/main/lib/IssuanceInitiation.js +0 -29
- package/dist/main/lib/MetadataClient.js +0 -127
- package/dist/main/lib/functions/Encoding.d.ts +0 -17
- package/dist/main/lib/functions/Encoding.js +0 -138
- package/dist/main/lib/functions/HttpUtils.d.ts +0 -17
- package/dist/main/lib/functions/HttpUtils.js +0 -133
- package/dist/main/lib/functions/ProofUtil.d.ts +0 -9
- package/dist/main/lib/functions/ProofUtil.js +0 -76
- package/dist/main/lib/functions/index.d.ts +0 -3
- package/dist/main/lib/functions/index.js +0 -20
- package/dist/main/lib/types/Authorization.types.d.ts +0 -66
- package/dist/main/lib/types/Authorization.types.js +0 -35
- package/dist/main/lib/types/CredentialIssuance.types.d.ts +0 -88
- package/dist/main/lib/types/CredentialIssuance.types.js +0 -8
- package/dist/main/lib/types/Generic.types.d.ts +0 -19
- package/dist/main/lib/types/Generic.types.js +0 -11
- package/dist/main/lib/types/OAuth2ASMetadata.d.ts +0 -37
- package/dist/main/lib/types/OAuth2ASMetadata.js +0 -3
- package/dist/main/lib/types/OID4VCIServerMetadata.d.ts +0 -65
- package/dist/main/lib/types/OID4VCIServerMetadata.js +0 -3
- package/dist/main/lib/types/Oidc4vciErrors.d.ts +0 -3
- package/dist/main/lib/types/Oidc4vciErrors.js +0 -7
- package/dist/main/lib/types/index.d.ts +0 -6
- package/dist/main/lib/types/index.js +0 -23
- package/dist/main/tsconfig.build.tsbuildinfo +0 -1
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { AuthzFlowType, CodeChallengeMethod } from '@sphereon/oid4vci-common';
|
|
2
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
3
|
+
// @ts-ignore
|
|
4
|
+
import nock from 'nock';
|
|
5
|
+
|
|
6
|
+
import { OpenID4VCIClient } from '../OpenID4VCIClient';
|
|
7
|
+
|
|
8
|
+
const MOCK_URL = 'https://server.example.com/';
|
|
9
|
+
|
|
10
|
+
describe('OpenID4VCIClient should', () => {
|
|
11
|
+
let client: OpenID4VCIClient;
|
|
12
|
+
|
|
13
|
+
beforeEach(async () => {
|
|
14
|
+
nock(MOCK_URL).get(/.*/).reply(200, {});
|
|
15
|
+
client = await OpenID4VCIClient.fromURI({
|
|
16
|
+
uri: 'openid-initiate-issuance://?issuer=https://server.example.com&credential_type=TestCredential',
|
|
17
|
+
flowType: AuthzFlowType.AUTHORIZATION_CODE_FLOW,
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
nock.cleanAll();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should create successfully construct an authorization request url', async () => {
|
|
26
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
27
|
+
// @ts-ignore
|
|
28
|
+
client._endpointMetadata?.issuerMetadata.authorization_endpoint = `${MOCK_URL}v1/auth/authorize`;
|
|
29
|
+
const url = client.createAuthorizationRequestUrl({
|
|
30
|
+
clientId: 'test-client',
|
|
31
|
+
codeChallengeMethod: CodeChallengeMethod.SHA256,
|
|
32
|
+
codeChallenge: 'mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs',
|
|
33
|
+
scope: 'openid TestCredential',
|
|
34
|
+
redirectUri: 'http://localhost:8881/cb',
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const urlSearchParams = new URLSearchParams(url.split('?')[1]);
|
|
38
|
+
const scope = urlSearchParams.get('scope')?.split(' ');
|
|
39
|
+
|
|
40
|
+
expect(scope?.[0]).toBe('openid');
|
|
41
|
+
});
|
|
42
|
+
it('throw an error if authorization endpoint is not set in server metadata', async () => {
|
|
43
|
+
expect(() => {
|
|
44
|
+
client.createAuthorizationRequestUrl({
|
|
45
|
+
clientId: 'test-client',
|
|
46
|
+
codeChallengeMethod: CodeChallengeMethod.SHA256,
|
|
47
|
+
codeChallenge: 'mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs',
|
|
48
|
+
scope: 'openid TestCredential',
|
|
49
|
+
redirectUri: 'http://localhost:8881/cb',
|
|
50
|
+
});
|
|
51
|
+
}).toThrow(Error('Server metadata does not contain authorization endpoint'));
|
|
52
|
+
});
|
|
53
|
+
it("injects 'openid' as the first scope if not provided", async () => {
|
|
54
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
55
|
+
// @ts-ignore
|
|
56
|
+
client._endpointMetadata?.issuerMetadata.authorization_endpoint = `${MOCK_URL}v1/auth/authorize`;
|
|
57
|
+
|
|
58
|
+
const url = client.createAuthorizationRequestUrl({
|
|
59
|
+
clientId: 'test-client',
|
|
60
|
+
codeChallengeMethod: CodeChallengeMethod.SHA256,
|
|
61
|
+
codeChallenge: 'mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs',
|
|
62
|
+
scope: 'TestCredential',
|
|
63
|
+
redirectUri: 'http://localhost:8881/cb',
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const urlSearchParams = new URLSearchParams(url.split('?')[1]);
|
|
67
|
+
const scope = urlSearchParams.get('scope')?.split(' ');
|
|
68
|
+
|
|
69
|
+
expect(scope?.[0]).toBe('openid');
|
|
70
|
+
});
|
|
71
|
+
it('throw an error if no scope and no authorization_details is provided', async () => {
|
|
72
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
73
|
+
// @ts-ignore
|
|
74
|
+
client._endpointMetadata?.issuerMetadata.authorization_endpoint = `${MOCK_URL}v1/auth/authorize`;
|
|
75
|
+
|
|
76
|
+
expect(() => {
|
|
77
|
+
client.createAuthorizationRequestUrl({
|
|
78
|
+
clientId: 'test-client',
|
|
79
|
+
codeChallengeMethod: CodeChallengeMethod.SHA256,
|
|
80
|
+
codeChallenge: 'mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs',
|
|
81
|
+
redirectUri: 'http://localhost:8881/cb',
|
|
82
|
+
});
|
|
83
|
+
}).toThrow(Error('Please provide a scope or authorization_details'));
|
|
84
|
+
});
|
|
85
|
+
it('create an authorization request url with authorization_details array property', async () => {
|
|
86
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
87
|
+
// @ts-ignore
|
|
88
|
+
client._endpointMetadata.issuerMetadata.authorization_endpoint = `${MOCK_URL}v1/auth/authorize`;
|
|
89
|
+
|
|
90
|
+
expect(
|
|
91
|
+
client.createAuthorizationRequestUrl({
|
|
92
|
+
clientId: 'test-client',
|
|
93
|
+
codeChallengeMethod: CodeChallengeMethod.SHA256,
|
|
94
|
+
codeChallenge: 'mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs',
|
|
95
|
+
authorizationDetails: [
|
|
96
|
+
{
|
|
97
|
+
type: 'openid_credential',
|
|
98
|
+
format: 'ldp_vc',
|
|
99
|
+
credential_definition: {
|
|
100
|
+
'@context': ['https://www.w3.org/2018/credentials/v1', 'https://www.w3.org/2018/credentials/examples/v1'],
|
|
101
|
+
types: ['VerifiableCredential', 'UniversityDegreeCredential'],
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
type: 'openid_credential',
|
|
106
|
+
format: 'mso_mdoc',
|
|
107
|
+
doctype: 'org.iso.18013.5.1.mDL',
|
|
108
|
+
},
|
|
109
|
+
],
|
|
110
|
+
redirectUri: 'http://localhost:8881/cb',
|
|
111
|
+
})
|
|
112
|
+
).toEqual(
|
|
113
|
+
'https://server.example.com/v1/auth/authorize?response_type=code&client_id=test-client&code_challenge_method=S256&code_challenge=mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs&authorization_details=%5B%7B%22type%22%3A%22openid_credential%22%2C%22format%22%3A%22ldp_vc%22%2C%22credential_definition%22%3A%7B%22%40context%22%3A%5B%22https%3A%2F%2Fwww%2Ew3%2Eorg%2F2018%2Fcredentials%2Fv1%22%2C%22https%3A%2F%2Fwww%2Ew3%2Eorg%2F2018%2Fcredentials%2Fexamples%2Fv1%22%5D%2C%22types%22%3A%5B%22VerifiableCredential%22%2C%22UniversityDegreeCredential%22%5D%7D%2C%22locations%22%3A%22https%3A%2F%2Fserver%2Eexample%2Ecom%22%7D%2C%7B%22type%22%3A%22openid_credential%22%2C%22format%22%3A%22mso_mdoc%22%2C%22doctype%22%3A%22org%2Eiso%2E18013%2E5%2E1%2EmDL%22%2C%22locations%22%3A%22https%3A%2F%2Fserver%2Eexample%2Ecom%22%7D%5D&redirect_uri=http%3A%2F%2Flocalhost%3A8881%2Fcb'
|
|
114
|
+
);
|
|
115
|
+
});
|
|
116
|
+
it('create an authorization request url with authorization_details object property', async () => {
|
|
117
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
118
|
+
// @ts-ignore
|
|
119
|
+
client._endpointMetadata.issuerMetadata.authorization_endpoint = `${MOCK_URL}v1/auth/authorize`;
|
|
120
|
+
|
|
121
|
+
expect(
|
|
122
|
+
client.createAuthorizationRequestUrl({
|
|
123
|
+
clientId: 'test-client',
|
|
124
|
+
codeChallengeMethod: CodeChallengeMethod.SHA256,
|
|
125
|
+
codeChallenge: 'mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs',
|
|
126
|
+
authorizationDetails: {
|
|
127
|
+
type: 'openid_credential',
|
|
128
|
+
format: 'ldp_vc',
|
|
129
|
+
credential_definition: {
|
|
130
|
+
'@context': ['https://www.w3.org/2018/credentials/v1', 'https://www.w3.org/2018/credentials/examples/v1'],
|
|
131
|
+
types: ['VerifiableCredential', 'UniversityDegreeCredential'],
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
redirectUri: 'http://localhost:8881/cb',
|
|
135
|
+
})
|
|
136
|
+
).toEqual(
|
|
137
|
+
'https://server.example.com/v1/auth/authorize?response_type=code&client_id=test-client&code_challenge_method=S256&code_challenge=mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs&authorization_details=%7B%22type%22%3A%22openid_credential%22%2C%22format%22%3A%22ldp_vc%22%2C%22credential_definition%22%3A%7B%22%40context%22%3A%5B%22https%3A%2F%2Fwww%2Ew3%2Eorg%2F2018%2Fcredentials%2Fv1%22%2C%22https%3A%2F%2Fwww%2Ew3%2Eorg%2F2018%2Fcredentials%2Fexamples%2Fv1%22%5D%2C%22types%22%3A%5B%22VerifiableCredential%22%2C%22UniversityDegreeCredential%22%5D%7D%2C%22locations%22%3A%22https%3A%2F%2Fserver%2Eexample%2Ecom%22%7D&redirect_uri=http%3A%2F%2Flocalhost%3A8881%2Fcb'
|
|
138
|
+
);
|
|
139
|
+
});
|
|
140
|
+
it('create an authorization request url with authorization_details and scope', async () => {
|
|
141
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
142
|
+
// @ts-ignore
|
|
143
|
+
client._endpointMetadata.issuerMetadata.authorization_endpoint = `${MOCK_URL}v1/auth/authorize`;
|
|
144
|
+
|
|
145
|
+
expect(
|
|
146
|
+
client.createAuthorizationRequestUrl({
|
|
147
|
+
clientId: 'test-client',
|
|
148
|
+
codeChallengeMethod: CodeChallengeMethod.SHA256,
|
|
149
|
+
codeChallenge: 'mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs',
|
|
150
|
+
authorizationDetails: {
|
|
151
|
+
type: 'openid_credential',
|
|
152
|
+
format: 'ldp_vc',
|
|
153
|
+
locations: ['https://test.com'],
|
|
154
|
+
credential_definition: {
|
|
155
|
+
'@context': ['https://www.w3.org/2018/credentials/v1', 'https://www.w3.org/2018/credentials/examples/v1'],
|
|
156
|
+
types: ['VerifiableCredential', 'UniversityDegreeCredential'],
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
scope: 'openid',
|
|
160
|
+
redirectUri: 'http://localhost:8881/cb',
|
|
161
|
+
})
|
|
162
|
+
).toEqual(
|
|
163
|
+
'https://server.example.com/v1/auth/authorize?response_type=code&client_id=test-client&code_challenge_method=S256&code_challenge=mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs&authorization_details=%7B%22type%22%3A%22openid_credential%22%2C%22format%22%3A%22ldp_vc%22%2C%22locations%22%3A%5B%22https%3A%2F%2Ftest%2Ecom%22%2C%22https%3A%2F%2Fserver%2Eexample%2Ecom%22%5D%2C%22credential_definition%22%3A%7B%22%40context%22%3A%5B%22https%3A%2F%2Fwww%2Ew3%2Eorg%2F2018%2Fcredentials%2Fv1%22%2C%22https%3A%2F%2Fwww%2Ew3%2Eorg%2F2018%2Fcredentials%2Fexamples%2Fv1%22%5D%2C%22types%22%3A%5B%22VerifiableCredential%22%2C%22UniversityDegreeCredential%22%5D%7D%7D&redirect_uri=http%3A%2F%2Flocalhost%3A8881%2Fcb&scope=openid'
|
|
164
|
+
);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { AuthzFlowType, CodeChallengeMethod, Oauth2ASWithOID4VCIMetadata } from '@sphereon/oid4vci-common';
|
|
2
|
+
import nock from 'nock';
|
|
3
|
+
|
|
4
|
+
import { OpenID4VCIClient } from '../OpenID4VCIClient';
|
|
5
|
+
|
|
6
|
+
const MOCK_URL = 'https://server.example.com/';
|
|
7
|
+
describe('OpenID4VCIClient', () => {
|
|
8
|
+
let client: OpenID4VCIClient;
|
|
9
|
+
|
|
10
|
+
beforeEach(async () => {
|
|
11
|
+
nock(MOCK_URL).get(/.*/).reply(200, {});
|
|
12
|
+
nock(`${MOCK_URL}`).post('/v1/auth/par').reply(201, { request_uri: 'test_uri', expires_in: 90 });
|
|
13
|
+
client = await OpenID4VCIClient.fromURI({
|
|
14
|
+
uri: 'openid-initiate-issuance://?issuer=https://server.example.com&credential_type=TestCredential',
|
|
15
|
+
flowType: AuthzFlowType.AUTHORIZATION_CODE_FLOW,
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
nock.cleanAll();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should successfully retrieve the authorization code using PAR', async () => {
|
|
24
|
+
(client.endpointMetadata.issuerMetadata! as Oauth2ASWithOID4VCIMetadata).pushed_authorization_request_endpoint = `${MOCK_URL}v1/auth/par`;
|
|
25
|
+
const actual = await client.acquirePushedAuthorizationRequestURI({
|
|
26
|
+
clientId: 'test-client',
|
|
27
|
+
codeChallengeMethod: CodeChallengeMethod.SHA256,
|
|
28
|
+
codeChallenge: 'mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs',
|
|
29
|
+
scope: 'openid TestCredential',
|
|
30
|
+
redirectUri: 'http://localhost:8881/cb',
|
|
31
|
+
});
|
|
32
|
+
expect(actual.successBody).toEqual({ request_uri: 'test_uri', expires_in: 90 });
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should fail when pushed_authorization_request_endpoint is not present', async () => {
|
|
36
|
+
await expect(() =>
|
|
37
|
+
client.acquirePushedAuthorizationRequestURI({
|
|
38
|
+
clientId: 'test-client',
|
|
39
|
+
codeChallengeMethod: CodeChallengeMethod.SHA256,
|
|
40
|
+
codeChallenge: 'mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs',
|
|
41
|
+
scope: 'openid TestCredential',
|
|
42
|
+
redirectUri: 'http://localhost:8881/cb',
|
|
43
|
+
})
|
|
44
|
+
).rejects.toThrow(Error('Server metadata does not contain pushed authorization request endpoint'));
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should fail when authorization_details and scope are not present', async () => {
|
|
48
|
+
await expect(() =>
|
|
49
|
+
client.acquirePushedAuthorizationRequestURI({
|
|
50
|
+
clientId: 'test-client',
|
|
51
|
+
codeChallengeMethod: CodeChallengeMethod.SHA256,
|
|
52
|
+
codeChallenge: 'mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs',
|
|
53
|
+
redirectUri: 'http://localhost:8881/cb',
|
|
54
|
+
})
|
|
55
|
+
).rejects.toThrow(Error('Please provide a scope or authorization_details'));
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should not fail when only authorization_details is present', async () => {
|
|
59
|
+
(client.endpointMetadata.issuerMetadata! as Oauth2ASWithOID4VCIMetadata).pushed_authorization_request_endpoint = `${MOCK_URL}v1/auth/par`;
|
|
60
|
+
const actual = await client.acquirePushedAuthorizationRequestURI({
|
|
61
|
+
clientId: 'test-client',
|
|
62
|
+
codeChallengeMethod: CodeChallengeMethod.SHA256,
|
|
63
|
+
codeChallenge: 'mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs',
|
|
64
|
+
authorizationDetails: [
|
|
65
|
+
{
|
|
66
|
+
type: 'openid_credential',
|
|
67
|
+
format: 'ldp_vc',
|
|
68
|
+
credential_definition: {
|
|
69
|
+
'@context': ['https://www.w3.org/2018/credentials/v1', 'https://www.w3.org/2018/credentials/examples/v1'],
|
|
70
|
+
types: ['VerifiableCredential', 'UniversityDegreeCredential'],
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
redirectUri: 'http://localhost:8881/cb',
|
|
75
|
+
});
|
|
76
|
+
expect(actual.successBody).toEqual({ request_uri: 'test_uri', expires_in: 90 });
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should not fail when only scope is present', async () => {
|
|
80
|
+
(client.endpointMetadata.issuerMetadata! as Oauth2ASWithOID4VCIMetadata).pushed_authorization_request_endpoint = `${MOCK_URL}v1/auth/par`;
|
|
81
|
+
const actual = await client.acquirePushedAuthorizationRequestURI({
|
|
82
|
+
clientId: 'test-client',
|
|
83
|
+
codeChallengeMethod: CodeChallengeMethod.SHA256,
|
|
84
|
+
codeChallenge: 'mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs',
|
|
85
|
+
scope: 'openid TestCredential',
|
|
86
|
+
redirectUri: 'http://localhost:8881/cb',
|
|
87
|
+
});
|
|
88
|
+
expect(actual.successBody).toEqual({ request_uri: 'test_uri', expires_in: 90 });
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should not fail when both authorization_details and scope are present', async () => {
|
|
92
|
+
(client.endpointMetadata.issuerMetadata! as Oauth2ASWithOID4VCIMetadata).pushed_authorization_request_endpoint = `${MOCK_URL}v1/auth/par`;
|
|
93
|
+
const actual = await client.acquirePushedAuthorizationRequestURI({
|
|
94
|
+
clientId: 'test-client',
|
|
95
|
+
codeChallengeMethod: CodeChallengeMethod.SHA256,
|
|
96
|
+
codeChallenge: 'mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs',
|
|
97
|
+
authorizationDetails: [
|
|
98
|
+
{
|
|
99
|
+
type: 'openid_credential',
|
|
100
|
+
format: 'ldp_vc',
|
|
101
|
+
credential_definition: {
|
|
102
|
+
'@context': ['https://www.w3.org/2018/credentials/v1', 'https://www.w3.org/2018/credentials/examples/v1'],
|
|
103
|
+
types: ['VerifiableCredential', 'UniversityDegreeCredential'],
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
scope: 'openid TestCredential',
|
|
108
|
+
redirectUri: 'http://localhost:8881/cb',
|
|
109
|
+
});
|
|
110
|
+
expect(actual.successBody).toEqual({ request_uri: 'test_uri', expires_in: 90 });
|
|
111
|
+
});
|
|
112
|
+
});
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { KeyObject } from 'crypto';
|
|
2
|
+
|
|
3
|
+
import { Alg, JWS_NOT_VALID, Jwt, NO_JWT_PROVIDED, OpenId4VCIVersion, PROOF_CANT_BE_CONSTRUCTED, ProofOfPossession } from '@sphereon/oid4vci-common';
|
|
4
|
+
import * as jose from 'jose';
|
|
5
|
+
|
|
6
|
+
import { ProofOfPossessionBuilder } from '..';
|
|
7
|
+
|
|
8
|
+
import { IDENTIPROOF_ISSUER_URL } from './MetadataMocks';
|
|
9
|
+
|
|
10
|
+
const jwt: Jwt = {
|
|
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() },
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const kid = 'did:example:ebfeb1f712ebc6f1c276e12ec21/keys/1';
|
|
16
|
+
|
|
17
|
+
let keypair: KeyPair;
|
|
18
|
+
|
|
19
|
+
async function proofOfPossessionCallbackFunction(args: Jwt, kid?: string): Promise<string> {
|
|
20
|
+
if (!args.payload.aud) {
|
|
21
|
+
throw Error('aud required');
|
|
22
|
+
} else if (!kid) {
|
|
23
|
+
throw Error('kid required');
|
|
24
|
+
}
|
|
25
|
+
return await new jose.SignJWT({ ...args.payload })
|
|
26
|
+
.setProtectedHeader({ alg: 'ES256' })
|
|
27
|
+
.setIssuedAt(args.payload.iat)
|
|
28
|
+
.setIssuer(kid)
|
|
29
|
+
.setAudience(args.payload.aud)
|
|
30
|
+
.setExpirationTime('2h')
|
|
31
|
+
.sign(keypair.privateKey);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface KeyPair {
|
|
35
|
+
publicKey: KeyObject;
|
|
36
|
+
privateKey: KeyObject;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
beforeAll(async () => {
|
|
40
|
+
const { privateKey, publicKey } = await jose.generateKeyPair('ES256');
|
|
41
|
+
keypair = { publicKey: publicKey as KeyObject, privateKey: privateKey as KeyObject };
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe('ProofOfPossession Builder ', () => {
|
|
45
|
+
it('should fail without supplied proof or callbacks', async function () {
|
|
46
|
+
await expect(
|
|
47
|
+
ProofOfPossessionBuilder.fromProof(undefined as never, OpenId4VCIVersion.VER_1_0_11)
|
|
48
|
+
.withIssuer(IDENTIPROOF_ISSUER_URL)
|
|
49
|
+
.withClientId('sphereon:wallet')
|
|
50
|
+
.withKid(kid)
|
|
51
|
+
.build()
|
|
52
|
+
).rejects.toThrow(Error(PROOF_CANT_BE_CONSTRUCTED));
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should fail wit undefined jwt supplied', async function () {
|
|
56
|
+
await expect(() =>
|
|
57
|
+
ProofOfPossessionBuilder.fromJwt({ jwt, callbacks: { signCallback: proofOfPossessionCallbackFunction }, version: OpenId4VCIVersion.VER_1_0_08 })
|
|
58
|
+
.withJwt(undefined as never)
|
|
59
|
+
.withIssuer(IDENTIPROOF_ISSUER_URL)
|
|
60
|
+
.withClientId('sphereon:wallet')
|
|
61
|
+
.withKid(kid)
|
|
62
|
+
.build()
|
|
63
|
+
).toThrow(Error(NO_JWT_PROVIDED));
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should build a proof with all required params present', async function () {
|
|
67
|
+
const proof: ProofOfPossession = await ProofOfPossessionBuilder.fromJwt({
|
|
68
|
+
jwt,
|
|
69
|
+
callbacks: {
|
|
70
|
+
signCallback: proofOfPossessionCallbackFunction,
|
|
71
|
+
},
|
|
72
|
+
version: OpenId4VCIVersion.VER_1_0_08,
|
|
73
|
+
})
|
|
74
|
+
.withIssuer(IDENTIPROOF_ISSUER_URL)
|
|
75
|
+
.withKid(kid)
|
|
76
|
+
.withClientId('sphereon:wallet')
|
|
77
|
+
.build();
|
|
78
|
+
expect(proof).toBeDefined();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should fail creating a proof of possession with simple verification', async () => {
|
|
82
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
83
|
+
async function proofOfPossessionCallbackFunction(_args: Jwt, _kid?: string): Promise<string> {
|
|
84
|
+
throw new Error(JWS_NOT_VALID);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
await expect(
|
|
88
|
+
ProofOfPossessionBuilder.fromJwt({ jwt, callbacks: { signCallback: proofOfPossessionCallbackFunction }, version: OpenId4VCIVersion.VER_1_0_08 })
|
|
89
|
+
.withIssuer(IDENTIPROOF_ISSUER_URL)
|
|
90
|
+
.withClientId('sphereon:wallet')
|
|
91
|
+
.withKid(kid)
|
|
92
|
+
.build()
|
|
93
|
+
).rejects.toThrow(Error(JWS_NOT_VALID));
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should fail creating a proof of possession without verify callback', async () => {
|
|
97
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
98
|
+
async function proofOfPossessionCallbackFunction(_args: Jwt, _kid?: string): Promise<string> {
|
|
99
|
+
throw new Error(JWS_NOT_VALID);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
await expect(
|
|
103
|
+
ProofOfPossessionBuilder.fromJwt({ jwt, callbacks: { signCallback: proofOfPossessionCallbackFunction }, version: OpenId4VCIVersion.VER_1_0_08 })
|
|
104
|
+
.withIssuer(IDENTIPROOF_ISSUER_URL)
|
|
105
|
+
.withClientId('sphereon:wallet')
|
|
106
|
+
.withKid(kid)
|
|
107
|
+
.build()
|
|
108
|
+
).rejects.toThrow(Error(JWS_NOT_VALID));
|
|
109
|
+
});
|
|
110
|
+
});
|