@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,121 @@
|
|
|
1
|
+
import { KeyObject } from 'crypto';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Alg,
|
|
5
|
+
CredentialIssuerMetadata,
|
|
6
|
+
Jwt,
|
|
7
|
+
JWTPayload,
|
|
8
|
+
OpenId4VCIVersion,
|
|
9
|
+
ProofOfPossession,
|
|
10
|
+
UniformCredentialRequest,
|
|
11
|
+
} from '@sphereon/oid4vci-common';
|
|
12
|
+
import * as jose from 'jose';
|
|
13
|
+
|
|
14
|
+
import { CredentialRequestClientBuilder, ProofOfPossessionBuilder } from '..';
|
|
15
|
+
|
|
16
|
+
import { IDENTIPROOF_ISSUER_URL, IDENTIPROOF_OID4VCI_METADATA, INITIATION_TEST_URI, WALT_ISSUER_URL, WALT_OID4VCI_METADATA } from './MetadataMocks';
|
|
17
|
+
|
|
18
|
+
const partialJWT = 'eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJkaWQ6ZXhhbXBsZTplYmZlYjFmN';
|
|
19
|
+
|
|
20
|
+
const jwt: Jwt = {
|
|
21
|
+
header: { alg: Alg.ES256, kid: 'did:example:ebfeb1f712ebc6f1c276e12ec21/keys/1', typ: 'jwt' },
|
|
22
|
+
payload: { iss: 'sphereon:wallet', nonce: 'tZignsnFbp', jti: 'tZignsnFbp223', aud: IDENTIPROOF_ISSUER_URL },
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const kid = 'did:example:ebfeb1f712ebc6f1c276e12ec21/keys/1';
|
|
26
|
+
|
|
27
|
+
let keypair: KeyPair;
|
|
28
|
+
|
|
29
|
+
beforeAll(async () => {
|
|
30
|
+
const { privateKey, publicKey } = await jose.generateKeyPair('ES256');
|
|
31
|
+
keypair = { publicKey: publicKey as KeyObject, privateKey: privateKey as KeyObject };
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
async function proofOfPossessionCallbackFunction(args: Jwt, kid?: string): Promise<string> {
|
|
35
|
+
if (!args.payload.aud) {
|
|
36
|
+
throw Error('aud required');
|
|
37
|
+
} else if (!kid) {
|
|
38
|
+
throw Error('kid required');
|
|
39
|
+
}
|
|
40
|
+
return await new jose.SignJWT({ ...args.payload })
|
|
41
|
+
.setProtectedHeader({ alg: 'ES256' })
|
|
42
|
+
.setIssuedAt()
|
|
43
|
+
.setIssuer(kid)
|
|
44
|
+
.setAudience(args.payload.aud)
|
|
45
|
+
.setExpirationTime('2h')
|
|
46
|
+
.sign(keypair.privateKey);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
interface KeyPair {
|
|
50
|
+
publicKey: KeyObject;
|
|
51
|
+
privateKey: KeyObject;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function proofOfPossessionVerifierCallbackFunction(args: { jwt: string; kid?: string }): Promise<Jwt> {
|
|
55
|
+
const result = await jose.jwtVerify(args.jwt, keypair.publicKey);
|
|
56
|
+
return { header: result.protectedHeader, payload: result.payload as unknown as JWTPayload };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
describe('Credential Request Client Builder', () => {
|
|
60
|
+
it('should build correctly provided with correct params', async function () {
|
|
61
|
+
const credReqClient = (await CredentialRequestClientBuilder.fromURI({ uri: INITIATION_TEST_URI }))
|
|
62
|
+
.withCredentialEndpoint('https://oidc4vci.demo.spruceid.com/credential')
|
|
63
|
+
.withFormat('jwt_vc')
|
|
64
|
+
.withCredentialType('credentialType')
|
|
65
|
+
.withToken('token')
|
|
66
|
+
.build();
|
|
67
|
+
expect(credReqClient.credentialRequestOpts.credentialEndpoint).toBe('https://oidc4vci.demo.spruceid.com/credential');
|
|
68
|
+
expect(credReqClient.credentialRequestOpts.format).toBe('jwt_vc');
|
|
69
|
+
expect(credReqClient.credentialRequestOpts.credentialTypes).toStrictEqual(['credentialType']);
|
|
70
|
+
expect(credReqClient.credentialRequestOpts.token).toBe('token');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should build credential request correctly', async () => {
|
|
74
|
+
const credReqClient = (await CredentialRequestClientBuilder.fromURI({ uri: INITIATION_TEST_URI }))
|
|
75
|
+
.withCredentialEndpoint('https://oidc4vci.demo.spruceid.com/credential')
|
|
76
|
+
.withFormat('jwt_vc')
|
|
77
|
+
.withCredentialType('https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#OpenBadgeCredential')
|
|
78
|
+
.build();
|
|
79
|
+
const proof: ProofOfPossession = await ProofOfPossessionBuilder.fromJwt({
|
|
80
|
+
jwt,
|
|
81
|
+
callbacks: {
|
|
82
|
+
signCallback: proofOfPossessionCallbackFunction,
|
|
83
|
+
verifyCallback: proofOfPossessionVerifierCallbackFunction,
|
|
84
|
+
},
|
|
85
|
+
version: OpenId4VCIVersion.VER_1_0_08,
|
|
86
|
+
})
|
|
87
|
+
.withClientId('sphereon:wallet')
|
|
88
|
+
.withKid(kid)
|
|
89
|
+
.build();
|
|
90
|
+
await proofOfPossessionVerifierCallbackFunction({ ...proof, kid });
|
|
91
|
+
const credentialRequest: UniformCredentialRequest = await credReqClient.createCredentialRequest({
|
|
92
|
+
proofInput: proof,
|
|
93
|
+
version: OpenId4VCIVersion.VER_1_0_08,
|
|
94
|
+
});
|
|
95
|
+
expect(credentialRequest.proof?.jwt).toContain(partialJWT);
|
|
96
|
+
expect('types' in credentialRequest).toBe(true);
|
|
97
|
+
if ('types' in credentialRequest) {
|
|
98
|
+
expect(credentialRequest.types).toStrictEqual(['https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#OpenBadgeCredential']);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('should build correctly from metadata', async () => {
|
|
103
|
+
const credReqClient = (
|
|
104
|
+
await CredentialRequestClientBuilder.fromURI({
|
|
105
|
+
uri: INITIATION_TEST_URI,
|
|
106
|
+
metadata: WALT_OID4VCI_METADATA,
|
|
107
|
+
})
|
|
108
|
+
)
|
|
109
|
+
.withFormat('jwt_vc')
|
|
110
|
+
.build();
|
|
111
|
+
expect(credReqClient.credentialRequestOpts.credentialEndpoint).toBe(`${WALT_ISSUER_URL}/credential`);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('should build correctly with endpoint from metadata', async () => {
|
|
115
|
+
const credReqClient = (await CredentialRequestClientBuilder.fromURI({ uri: INITIATION_TEST_URI }))
|
|
116
|
+
.withFormat('jwt_vc')
|
|
117
|
+
.withCredentialEndpointFromMetadata(IDENTIPROOF_OID4VCI_METADATA as unknown as CredentialIssuerMetadata)
|
|
118
|
+
.build();
|
|
119
|
+
expect(credReqClient.credentialRequestOpts.credentialEndpoint).toBe(`${IDENTIPROOF_ISSUER_URL}/credential`);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { isValidURL } from '../functions';
|
|
2
|
+
|
|
3
|
+
describe('httputils.isValidURL', () => {
|
|
4
|
+
it('Should return true for http://localhost', () => {
|
|
5
|
+
expect(isValidURL('http://localhost:4000/some/random/path')).toBeTruthy();
|
|
6
|
+
});
|
|
7
|
+
it('Should return false when no scheme is used', () => {
|
|
8
|
+
expect(isValidURL('sphereon.com/some/random/path')).toBeFalsy();
|
|
9
|
+
});
|
|
10
|
+
it('Should return false when a different scheme than http(s) is used', () => {
|
|
11
|
+
expect(isValidURL('ftp://sphereon.com/some/random/path')).toBeFalsy();
|
|
12
|
+
});
|
|
13
|
+
it('Should return false for invalid scheme http:xxx', () => {
|
|
14
|
+
expect(isValidURL('http:localhost:4000/some/random/path')).toBeFalsy();
|
|
15
|
+
});
|
|
16
|
+
it('Should return false for non-localhost hostname with no domain', () => {
|
|
17
|
+
expect(isValidURL('https://mydomain/some/random/path')).toBeFalsy();
|
|
18
|
+
});
|
|
19
|
+
it('Should return true for https://sphereon.com', () => {
|
|
20
|
+
expect(isValidURL('https://sphereon.com/some/random/path')).toBeTruthy();
|
|
21
|
+
});
|
|
22
|
+
it('Should return true for https://sphereon.com:400', () => {
|
|
23
|
+
expect(isValidURL('https://sphereon.com:400/some/random/path')).toBeTruthy();
|
|
24
|
+
});
|
|
25
|
+
it('Should return true when no path is supplied', () => {
|
|
26
|
+
expect(isValidURL('https://sphereon.com')).toBeTruthy();
|
|
27
|
+
});
|
|
28
|
+
it('Should return true for https://sphereon.com:400/some/random/path?query=param', () => {
|
|
29
|
+
expect(isValidURL('https://sphereon.com:400/some/random/path?query=param')).toBeTruthy();
|
|
30
|
+
});
|
|
31
|
+
it('Should return true for https://sphereon.com:400/some/random/path#fragment', () => {
|
|
32
|
+
expect(isValidURL('https://sphereon.com:400/some/random/path#fragment')).toBeTruthy();
|
|
33
|
+
});
|
|
34
|
+
it('Should return true for https://sphereon.com:400/some/random/path?query=param#fragment', () => {
|
|
35
|
+
expect(isValidURL('https://sphereon.com:400/some/random/path?query=param#fragment')).toBeTruthy();
|
|
36
|
+
});
|
|
37
|
+
});
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AccessTokenResponse,
|
|
3
|
+
Alg,
|
|
4
|
+
AuthzFlowType,
|
|
5
|
+
CredentialOfferRequestWithBaseUrl,
|
|
6
|
+
Jwt,
|
|
7
|
+
OpenId4VCIVersion,
|
|
8
|
+
ProofOfPossession,
|
|
9
|
+
} from '@sphereon/oid4vci-common';
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
11
|
+
// @ts-ignore
|
|
12
|
+
import nock from 'nock';
|
|
13
|
+
|
|
14
|
+
import { AccessTokenClient, CredentialRequestClientBuilder, OpenID4VCIClient, ProofOfPossessionBuilder } from '..';
|
|
15
|
+
import { CredentialOfferClient } from '../CredentialOfferClient';
|
|
16
|
+
|
|
17
|
+
import { IDENTIPROOF_AS_METADATA, IDENTIPROOF_AS_URL, IDENTIPROOF_ISSUER_URL, IDENTIPROOF_OID4VCI_METADATA } from './MetadataMocks';
|
|
18
|
+
|
|
19
|
+
export const UNIT_TEST_TIMEOUT = 30000;
|
|
20
|
+
|
|
21
|
+
const ISSUER_URL = 'https://issuer.research.identiproof.io';
|
|
22
|
+
const jwt = {
|
|
23
|
+
header: { alg: Alg.ES256, kid: 'did:example:ebfeb1f712ebc6f1c276e12ec21/keys/1', typ: 'jwt' },
|
|
24
|
+
payload: { iss: 'test-clientId', nonce: 'tZignsnFbp', jti: 'tZignsnFbp223', aud: ISSUER_URL },
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
describe('OID4VCI-Client should', () => {
|
|
28
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
29
|
+
async function proofOfPossessionCallbackFunction(_args: Jwt, _kid?: string): Promise<string> {
|
|
30
|
+
return 'ey.val.ue';
|
|
31
|
+
}
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
nock.cleanAll();
|
|
34
|
+
});
|
|
35
|
+
afterEach(() => {
|
|
36
|
+
nock.cleanAll();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Access token mocks
|
|
40
|
+
const mockedAccessTokenResponse: AccessTokenResponse = {
|
|
41
|
+
access_token: 'ey6546.546654.64565',
|
|
42
|
+
authorization_pending: false,
|
|
43
|
+
c_nonce: 'c_nonce2022101300',
|
|
44
|
+
c_nonce_expires_in: 2025101300,
|
|
45
|
+
interval: 2025101300,
|
|
46
|
+
token_type: 'Bearer',
|
|
47
|
+
};
|
|
48
|
+
const mockedVC =
|
|
49
|
+
'eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL2V4YW1wbGVzL3YxIl0sImlkIjoiaHR0cDovL2V4YW1wbGUuZWR1L2NyZWRlbnRpYWxzLzM3MzIiLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVW5pdmVyc2l0eURlZ3JlZUNyZWRlbnRpYWwiXSwiaXNzdWVyIjoiaHR0cHM6Ly9leGFtcGxlLmVkdS9pc3N1ZXJzLzU2NTA0OSIsImlzc3VhbmNlRGF0ZSI6IjIwMTAtMDEtMDFUMDA6MDA6MDBaIiwiY3JlZGVudGlhbFN1YmplY3QiOnsiaWQiOiJkaWQ6ZXhhbXBsZTplYmZlYjFmNzEyZWJjNmYxYzI3NmUxMmVjMjEiLCJkZWdyZWUiOnsidHlwZSI6IkJhY2hlbG9yRGVncmVlIiwibmFtZSI6IkJhY2hlbG9yIG9mIFNjaWVuY2UgYW5kIEFydHMifX19LCJpc3MiOiJodHRwczovL2V4YW1wbGUuZWR1L2lzc3VlcnMvNTY1MDQ5IiwibmJmIjoxMjYyMzA0MDAwLCJqdGkiOiJodHRwOi8vZXhhbXBsZS5lZHUvY3JlZGVudGlhbHMvMzczMiIsInN1YiI6ImRpZDpleGFtcGxlOmViZmViMWY3MTJlYmM2ZjFjMjc2ZTEyZWMyMSJ9.z5vgMTK1nfizNCg5N-niCOL3WUIAL7nXy-nGhDZYO_-PNGeE-0djCpWAMH8fD8eWSID5PfkPBYkx_dfLJnQ7NA';
|
|
50
|
+
const INITIATE_QR =
|
|
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
|
+
const OFFER_QR =
|
|
53
|
+
'openid-credential-offer://credential_offer=%7B%22credential_issuer%22:%22https://credential-issuer.example.com%22,%22credentials%22:%5B%7B%22format%22:%22jwt_vc_json%22,%22types%22:%5B%22VerifiableCredential%22,%22UniversityDegreeCredential%22%5D%7D%5D,%22issuer_state%22:%22eyJhbGciOiJSU0Et...FYUaBy%22%7D';
|
|
54
|
+
|
|
55
|
+
function succeedWithAFullFlowWithClientSetup() {
|
|
56
|
+
nock(IDENTIPROOF_ISSUER_URL).get('/.well-known/openid-credential-issuer').reply(200, JSON.stringify(IDENTIPROOF_OID4VCI_METADATA));
|
|
57
|
+
nock(IDENTIPROOF_AS_URL).get('/.well-known/oauth-authorization-server').reply(200, JSON.stringify(IDENTIPROOF_AS_METADATA));
|
|
58
|
+
nock(IDENTIPROOF_AS_URL)
|
|
59
|
+
.post(/oauth2\/token.*/)
|
|
60
|
+
.reply(200, JSON.stringify(mockedAccessTokenResponse));
|
|
61
|
+
nock(ISSUER_URL)
|
|
62
|
+
.post(/credential/)
|
|
63
|
+
.reply(200, {
|
|
64
|
+
format: 'jwt-vc',
|
|
65
|
+
credential: mockedVC,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
it('succeed with a full flow with the client using OpenID4VCI version 9', async () => {
|
|
70
|
+
succeedWithAFullFlowWithClientSetup();
|
|
71
|
+
const client = await OpenID4VCIClient.fromURI({
|
|
72
|
+
uri: INITIATE_QR,
|
|
73
|
+
flowType: AuthzFlowType.PRE_AUTHORIZED_CODE_FLOW,
|
|
74
|
+
kid: 'did:example:ebfeb1f712ebc6f1c276e12ec21/keys/1',
|
|
75
|
+
alg: Alg.ES256,
|
|
76
|
+
clientId: 'test-clientId',
|
|
77
|
+
});
|
|
78
|
+
await assertionOfsucceedWithAFullFlowWithClient(client);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test.skip('succeed with a full flow wit the client using OpenID4VCI version 11', async () => {
|
|
82
|
+
succeedWithAFullFlowWithClientSetup();
|
|
83
|
+
const client = await OpenID4VCIClient.fromURI({
|
|
84
|
+
uri: OFFER_QR,
|
|
85
|
+
flowType: AuthzFlowType.PRE_AUTHORIZED_CODE_FLOW,
|
|
86
|
+
kid: 'did:example:ebfeb1f712ebc6f1c276e12ec21/keys/1',
|
|
87
|
+
alg: Alg.ES256,
|
|
88
|
+
clientId: 'test-clientId',
|
|
89
|
+
});
|
|
90
|
+
await assertionOfsucceedWithAFullFlowWithClient(client);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
async function assertionOfsucceedWithAFullFlowWithClient(client: OpenID4VCIClient) {
|
|
94
|
+
expect(client.flowType).toEqual(AuthzFlowType.PRE_AUTHORIZED_CODE_FLOW);
|
|
95
|
+
expect(client.credentialOffer).toBeDefined();
|
|
96
|
+
expect(client.endpointMetadata).toBeDefined();
|
|
97
|
+
expect(client.getIssuer()).toEqual('https://issuer.research.identiproof.io');
|
|
98
|
+
expect(client.getCredentialEndpoint()).toEqual('https://issuer.research.identiproof.io/credential');
|
|
99
|
+
expect(client.getAccessTokenEndpoint()).toEqual('https://auth.research.identiproof.io/oauth2/token');
|
|
100
|
+
|
|
101
|
+
const accessToken = await client.acquireAccessToken({ pin: '1234' });
|
|
102
|
+
expect(accessToken).toEqual(mockedAccessTokenResponse);
|
|
103
|
+
|
|
104
|
+
const credentialResponse = await client.acquireCredentials({
|
|
105
|
+
credentialTypes: 'OpenBadgeCredential',
|
|
106
|
+
format: 'jwt_vc_json-ld',
|
|
107
|
+
proofCallbacks: {
|
|
108
|
+
signCallback: proofOfPossessionCallbackFunction,
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
expect(credentialResponse.credential).toEqual(mockedVC);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
it(
|
|
115
|
+
'succeed with a full flow without the client',
|
|
116
|
+
async () => {
|
|
117
|
+
/* Convert the URI into an object */
|
|
118
|
+
const credentialOffer: CredentialOfferRequestWithBaseUrl = await CredentialOfferClient.fromURI(INITIATE_QR);
|
|
119
|
+
|
|
120
|
+
expect(credentialOffer.baseUrl).toEqual('openid-initiate-issuance://');
|
|
121
|
+
expect(credentialOffer.original_credential_offer).toEqual({
|
|
122
|
+
credential_type: ['OpenBadgeCredentialUrl'],
|
|
123
|
+
issuer: ISSUER_URL,
|
|
124
|
+
'pre-authorized_code':
|
|
125
|
+
'4jLs9xZHEfqcoow0kHE7d1a8hUk6Sy-5bVSV2MqBUGUgiFFQi-ImL62T-FmLIo8hKA1UdMPH0lM1xAgcFkJfxIw9L-lI3mVs0hRT8YVwsEM1ma6N3wzuCdwtMU4bcwKp',
|
|
126
|
+
user_pin_required: 'true',
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
nock(ISSUER_URL)
|
|
130
|
+
.post(/token.*/)
|
|
131
|
+
.reply(200, JSON.stringify(mockedAccessTokenResponse));
|
|
132
|
+
|
|
133
|
+
/* The actual access token calls */
|
|
134
|
+
const accessTokenClient: AccessTokenClient = new AccessTokenClient();
|
|
135
|
+
const accessTokenResponse = await accessTokenClient.acquireAccessToken({ credentialOffer: credentialOffer, pin: '1234' });
|
|
136
|
+
expect(accessTokenResponse.successBody).toEqual(mockedAccessTokenResponse);
|
|
137
|
+
// Get the credential
|
|
138
|
+
nock(ISSUER_URL)
|
|
139
|
+
.post(/credential/)
|
|
140
|
+
.reply(200, {
|
|
141
|
+
format: 'jwt-vc',
|
|
142
|
+
credential: mockedVC,
|
|
143
|
+
});
|
|
144
|
+
const credReqClient = CredentialRequestClientBuilder.fromCredentialOffer({ credentialOffer: credentialOffer })
|
|
145
|
+
.withFormat('jwt_vc')
|
|
146
|
+
|
|
147
|
+
.withTokenFromResponse(accessTokenResponse.successBody!)
|
|
148
|
+
.build();
|
|
149
|
+
|
|
150
|
+
//TS2322: Type '(args: ProofOfPossessionCallbackArgs) => Promise<string>'
|
|
151
|
+
// is not assignable to type 'ProofOfPossessionCallback'.
|
|
152
|
+
// Types of parameters 'args' and 'args' are incompatible.
|
|
153
|
+
// Property 'kid' is missing in type '{ header: unknown; payload: unknown; }' but required in type 'ProofOfPossessionCallbackArgs'.
|
|
154
|
+
const proof: ProofOfPossession = await ProofOfPossessionBuilder.fromJwt({
|
|
155
|
+
jwt,
|
|
156
|
+
callbacks: {
|
|
157
|
+
signCallback: proofOfPossessionCallbackFunction,
|
|
158
|
+
},
|
|
159
|
+
version: OpenId4VCIVersion.VER_1_0_11,
|
|
160
|
+
})
|
|
161
|
+
.withEndpointMetadata({
|
|
162
|
+
issuer: 'https://issuer.research.identiproof.io',
|
|
163
|
+
credential_endpoint: 'https://issuer.research.identiproof.io/credential',
|
|
164
|
+
token_endpoint: 'https://issuer.research.identiproof.io/token',
|
|
165
|
+
})
|
|
166
|
+
.withKid('did:example:ebfeb1f712ebc6f1c276e12ec21/keys/1')
|
|
167
|
+
.build();
|
|
168
|
+
const credResponse = await credReqClient.acquireCredentialsUsingProof({ proofInput: proof });
|
|
169
|
+
expect(credResponse.successBody?.credential).toEqual(mockedVC);
|
|
170
|
+
},
|
|
171
|
+
UNIT_TEST_TIMEOUT
|
|
172
|
+
);
|
|
173
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { CredentialOfferClient } from '../CredentialOfferClient';
|
|
2
|
+
|
|
3
|
+
import { INITIATION_TEST, INITIATION_TEST_HTTPS_URI, INITIATION_TEST_URI } from './MetadataMocks';
|
|
4
|
+
|
|
5
|
+
describe('Issuance Initiation', () => {
|
|
6
|
+
it('Should return Issuance Initiation Request with base URL from https URI', async () => {
|
|
7
|
+
expect(await CredentialOfferClient.fromURI(INITIATION_TEST_HTTPS_URI)).toEqual({
|
|
8
|
+
baseUrl: 'https://server.example.com',
|
|
9
|
+
credential_offer: {
|
|
10
|
+
credential_issuer: 'https://server.example.com',
|
|
11
|
+
credentials: ['https://did.example.org/healthCard', 'https://did.example.org/driverLicense'],
|
|
12
|
+
grants: {
|
|
13
|
+
authorization_code: {
|
|
14
|
+
issuer_state: 'eyJhbGciOiJSU0Et...FYUaBy',
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
issuerState: 'eyJhbGciOiJSU0Et...FYUaBy',
|
|
19
|
+
original_credential_offer: {
|
|
20
|
+
credential_type: ['https://did.example.org/healthCard', 'https://did.example.org/driverLicense'],
|
|
21
|
+
issuer: 'https://server.example.com',
|
|
22
|
+
op_state: 'eyJhbGciOiJSU0Et...FYUaBy',
|
|
23
|
+
},
|
|
24
|
+
scheme: 'https',
|
|
25
|
+
supportedFlows: ['Authorization Code Flow'],
|
|
26
|
+
userPinRequired: false,
|
|
27
|
+
version: 1008,
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('Should return Issuance Initiation Request with base URL from openid-initiate-issuance URI', async () => {
|
|
32
|
+
expect(await CredentialOfferClient.fromURI(INITIATION_TEST_URI)).toEqual(INITIATION_TEST);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('Should return Issuance Initiation URI from request', async () => {
|
|
36
|
+
expect(CredentialOfferClient.toURI(INITIATION_TEST)).toEqual(INITIATION_TEST_URI);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('Should return URI from Issuance Initiation Request', async () => {
|
|
40
|
+
const issuanceInitiationClient = await CredentialOfferClient.fromURI(INITIATION_TEST_HTTPS_URI);
|
|
41
|
+
expect(CredentialOfferClient.toURI(issuanceInitiationClient)).toEqual(INITIATION_TEST_HTTPS_URI);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('Should throw error on invalid URI', async () => {
|
|
45
|
+
const issuanceInitiationURI = INITIATION_TEST_HTTPS_URI.replace('?', '');
|
|
46
|
+
await expect(async () => CredentialOfferClient.fromURI(issuanceInitiationURI)).rejects.toThrowError('Invalid Credential Offer Request');
|
|
47
|
+
});
|
|
48
|
+
});
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { convertJsonToURI, convertURIToJsonObject, OpenId4VCIVersion } from '@sphereon/oid4vci-common';
|
|
2
|
+
|
|
3
|
+
describe('JSON To URI v8', () => {
|
|
4
|
+
it('should parse an object into open-id-URI with a single credential_type', () => {
|
|
5
|
+
expect(
|
|
6
|
+
convertJsonToURI(
|
|
7
|
+
{
|
|
8
|
+
issuer: 'https://server.example.com',
|
|
9
|
+
credential_type: 'https://did.example.org/healthCard',
|
|
10
|
+
op_state: 'eyJhbGciOiJSU0Et...FYUaBy',
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
uriTypeProperties: ['issuer', 'credential_type'],
|
|
14
|
+
version: OpenId4VCIVersion.VER_1_0_08,
|
|
15
|
+
}
|
|
16
|
+
)
|
|
17
|
+
).toEqual(
|
|
18
|
+
'issuer=https%3A%2F%2Fserver%2Eexample%2Ecom&credential_type=https%3A%2F%2Fdid%2Eexample%2Eorg%2FhealthCard&op_state=eyJhbGciOiJSU0Et...FYUaBy'
|
|
19
|
+
);
|
|
20
|
+
});
|
|
21
|
+
it('should parse an object into open-id-URI with an array of credential_type', () => {
|
|
22
|
+
expect(
|
|
23
|
+
convertJsonToURI(
|
|
24
|
+
{
|
|
25
|
+
issuer: 'https://server.example.com',
|
|
26
|
+
credential_type: ['https://did.example.org/healthCard', 'https://did.example.org/driverLicense'],
|
|
27
|
+
op_state: 'eyJhbGciOiJSU0Et...FYUaBy',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
arrayTypeProperties: ['credential_type'],
|
|
31
|
+
uriTypeProperties: ['issuer', 'credential_type'],
|
|
32
|
+
version: OpenId4VCIVersion.VER_1_0_08,
|
|
33
|
+
}
|
|
34
|
+
)
|
|
35
|
+
).toEqual(
|
|
36
|
+
'issuer=https%3A%2F%2Fserver%2Eexample%2Ecom&credential_type=https%3A%2F%2Fdid%2Eexample%2Eorg%2FhealthCard&credential_type=https%3A%2F%2Fdid%2Eexample%2Eorg%2FdriverLicense&op_state=eyJhbGciOiJSU0Et...FYUaBy'
|
|
37
|
+
);
|
|
38
|
+
});
|
|
39
|
+
it('should parse an object into open-id-URI with an array of credential_type and json string', () => {
|
|
40
|
+
expect(
|
|
41
|
+
convertJsonToURI(
|
|
42
|
+
JSON.stringify({
|
|
43
|
+
issuer: 'https://server.example.com',
|
|
44
|
+
credential_type: ['https://did.example.org/healthCard', 'https://did.example.org/driverLicense'],
|
|
45
|
+
op_state: 'eyJhbGciOiJSU0Et...FYUaBy',
|
|
46
|
+
}),
|
|
47
|
+
{
|
|
48
|
+
arrayTypeProperties: ['credential_type'],
|
|
49
|
+
uriTypeProperties: ['issuer', 'credential_type'],
|
|
50
|
+
version: OpenId4VCIVersion.VER_1_0_08,
|
|
51
|
+
}
|
|
52
|
+
)
|
|
53
|
+
).toEqual(
|
|
54
|
+
'issuer=https%3A%2F%2Fserver%2Eexample%2Ecom&credential_type=https%3A%2F%2Fdid%2Eexample%2Eorg%2FhealthCard&credential_type=https%3A%2F%2Fdid%2Eexample%2Eorg%2FdriverLicense&op_state=eyJhbGciOiJSU0Et...FYUaBy'
|
|
55
|
+
);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe('JSON To URI v9', () => {
|
|
60
|
+
it('should parse an object into open-id-URI with a single credential_type', () => {
|
|
61
|
+
expect(
|
|
62
|
+
convertJsonToURI(
|
|
63
|
+
{
|
|
64
|
+
issuer: 'https://server.example.com',
|
|
65
|
+
credential_type: 'https://did.example.org/healthCard',
|
|
66
|
+
op_state: 'eyJhbGciOiJSU0Et...FYUaBy',
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
uriTypeProperties: ['issuer', 'credential_type'],
|
|
70
|
+
version: OpenId4VCIVersion.VER_1_0_09,
|
|
71
|
+
}
|
|
72
|
+
)
|
|
73
|
+
).toEqual(
|
|
74
|
+
'%7B%22issuer%22%3A%22https%3A%2F%2Fserver.example.com%22%2C%22credential_type%22%3A%22https%3A%2F%2Fdid.example.org%2FhealthCard%22%2C%22op_state%22%3A%22eyJhbGciOiJSU0Et...FYUaBy%22%7D'
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
it('should parse an object into open-id-URI with an array of credential_type', () => {
|
|
78
|
+
expect(
|
|
79
|
+
convertJsonToURI(
|
|
80
|
+
{
|
|
81
|
+
issuer: 'https://server.example.com',
|
|
82
|
+
credential_type: ['https://did.example.org/healthCard', 'https://did.example.org/driverLicense'],
|
|
83
|
+
op_state: 'eyJhbGciOiJSU0Et...FYUaBy',
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
arrayTypeProperties: ['credential_type'],
|
|
87
|
+
uriTypeProperties: ['issuer', 'credential_type'],
|
|
88
|
+
version: OpenId4VCIVersion.VER_1_0_09,
|
|
89
|
+
}
|
|
90
|
+
)
|
|
91
|
+
).toEqual(
|
|
92
|
+
'%7B%22issuer%22%3A%22https%3A%2F%2Fserver.example.com%22%2C%22credential_type%22%3A%5B%22https%3A%2F%2Fdid.example.org%2FhealthCard%22%2C%22https%3A%2F%2Fdid.example.org%2FdriverLicense%22%5D%2C%22op_state%22%3A%22eyJhbGciOiJSU0Et...FYUaBy%22%7D'
|
|
93
|
+
);
|
|
94
|
+
});
|
|
95
|
+
it('should parse an object into open-id-URI with an array of credential_type and json string', () => {
|
|
96
|
+
expect(
|
|
97
|
+
convertJsonToURI(
|
|
98
|
+
JSON.stringify({
|
|
99
|
+
issuer: 'https://server.example.com',
|
|
100
|
+
credential_type: ['https://did.example.org/healthCard', 'https://did.example.org/driverLicense'],
|
|
101
|
+
op_state: 'eyJhbGciOiJSU0Et...FYUaBy',
|
|
102
|
+
}),
|
|
103
|
+
{
|
|
104
|
+
arrayTypeProperties: ['credential_type'],
|
|
105
|
+
uriTypeProperties: ['issuer', 'credential_type'],
|
|
106
|
+
version: OpenId4VCIVersion.VER_1_0_09,
|
|
107
|
+
}
|
|
108
|
+
)
|
|
109
|
+
).toEqual(
|
|
110
|
+
'%7B%22issuer%22%3A%22https%3A%2F%2Fserver.example.com%22%2C%22credential_type%22%3A%5B%22https%3A%2F%2Fdid.example.org%2FhealthCard%22%2C%22https%3A%2F%2Fdid.example.org%2FdriverLicense%22%5D%2C%22op_state%22%3A%22eyJhbGciOiJSU0Et...FYUaBy%22%7D'
|
|
111
|
+
);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe('URI To Json Object', () => {
|
|
116
|
+
it('should parse open-id-URI as json object with a single credential_type', () => {
|
|
117
|
+
expect(
|
|
118
|
+
convertURIToJsonObject(
|
|
119
|
+
'issuer=https%3A%2F%2Fserver%2Eexample%2Ecom&credential_type=https%3A%2F%2Fdid%2Eexample%2Eorg%2FhealthCard&op_state=eyJhbGciOiJSU0Et...FYUaBy',
|
|
120
|
+
{
|
|
121
|
+
arrayTypeProperties: ['credential_type'],
|
|
122
|
+
requiredProperties: ['issuer', 'credential_type'],
|
|
123
|
+
}
|
|
124
|
+
)
|
|
125
|
+
).toEqual({
|
|
126
|
+
issuer: 'https://server.example.com',
|
|
127
|
+
credential_type: ['https://did.example.org/healthCard'],
|
|
128
|
+
op_state: 'eyJhbGciOiJSU0Et...FYUaBy',
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
it('should parse open-id-URI as json object with an array of credential_type', () => {
|
|
132
|
+
expect(
|
|
133
|
+
convertURIToJsonObject(
|
|
134
|
+
'issuer=https%3A%2F%2Fserver%2Eexample%2Ecom&credential_type=https%3A%2F%2Fdid%2Eexample%2Eorg%2FhealthCard&credential_type=https%3A%2F%2Fdid%2Eexample%2Eorg%2FdriverLicense&op_state=eyJhbGciOiJSU0Et...FYUaBy',
|
|
135
|
+
{
|
|
136
|
+
arrayTypeProperties: ['credential_type'],
|
|
137
|
+
requiredProperties: ['issuer', 'credential_type'],
|
|
138
|
+
}
|
|
139
|
+
)
|
|
140
|
+
).toEqual({
|
|
141
|
+
issuer: 'https://server.example.com',
|
|
142
|
+
credential_type: ['https://did.example.org/healthCard', 'https://did.example.org/driverLicense'],
|
|
143
|
+
op_state: 'eyJhbGciOiJSU0Et...FYUaBy',
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
});
|