@connectid-tools/rp-nodejs-sdk 4.2.0 → 5.0.0
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 +284 -237
- package/package.json +7 -5
- package/{config.js → src/config.js} +2 -31
- package/src/conformance/api/conformance-api.d.ts +38 -0
- package/src/conformance/api/conformance-api.js +53 -0
- package/src/conformance/config.json +60 -0
- package/src/conformance/conformance-config.d.ts +2 -0
- package/src/conformance/conformance-config.js +34 -0
- package/src/conformance/conformance.test.js +101 -0
- package/src/conformance/variant.json +1 -0
- package/src/crypto/crypto-loader.d.ts +32 -0
- package/src/crypto/crypto-loader.js +49 -0
- package/src/crypto/jwt-helper.d.ts +61 -0
- package/src/crypto/jwt-helper.js +92 -0
- package/src/crypto/pkce-helper.d.ts +43 -0
- package/src/crypto/pkce-helper.js +75 -0
- package/src/endpoints/participants-endpoint.d.ts +55 -0
- package/src/endpoints/participants-endpoint.js +137 -0
- package/src/endpoints/pushed-authorisation-request-endpoint.d.ts +87 -0
- package/src/endpoints/pushed-authorisation-request-endpoint.js +192 -0
- package/src/endpoints/retrieve-token-endpoint.d.ts +66 -0
- package/src/endpoints/retrieve-token-endpoint.js +159 -0
- package/src/endpoints/userinfo-endpoint.d.ts +24 -0
- package/src/endpoints/userinfo-endpoint.js +50 -0
- package/src/fapi/fapi-utils.d.ts +6 -0
- package/src/fapi/fapi-utils.js +9 -0
- package/src/http/http-client-extensions.d.ts +60 -0
- package/src/http/http-client-extensions.js +106 -0
- package/src/http/http-client-factory.d.ts +27 -0
- package/src/http/http-client-factory.js +45 -0
- package/src/integration/integration.test.d.ts +1 -0
- package/src/integration/integration.test.js +30 -0
- package/src/model/callback-params.d.ts +31 -0
- package/src/model/callback-params.js +1 -0
- package/src/model/claims.d.ts +100 -0
- package/src/model/claims.js +1 -0
- package/src/model/consolidated-token-set.d.ts +74 -0
- package/src/model/consolidated-token-set.js +100 -0
- package/src/model/discovery-service.d.ts +46 -0
- package/src/model/discovery-service.js +112 -0
- package/src/model/issuer-metadata.d.ts +165 -0
- package/src/model/issuer-metadata.js +1 -0
- package/src/model/jwks.d.ts +12 -0
- package/src/model/jwks.js +1 -0
- package/src/model/token-response.d.ts +31 -0
- package/src/model/token-response.js +1 -0
- package/src/model/token-set.d.ts +73 -0
- package/src/model/token-set.js +179 -0
- package/src/relying-party-client-sdk.d.ts +68 -0
- package/src/relying-party-client-sdk.js +150 -0
- package/src/test-data/large-participants-test-data.d.ts +865 -0
- package/src/test-data/large-participants-test-data.js +18907 -0
- package/src/test-data/participants-test-data.d.ts +149 -0
- package/src/test-data/participants-test-data.js +458 -0
- package/src/test-data/sandbox-participants-test-data.d.ts +865 -0
- package/src/test-data/sandbox-participants-test-data.js +3794 -0
- package/src/tests/cert-utils.test.d.ts +1 -0
- package/src/tests/cert-utils.test.js +13 -0
- package/src/tests/functional-utils.test.d.ts +1 -0
- package/src/tests/functional-utils.test.js +13 -0
- package/src/tests/participant-filters.test.d.ts +1 -0
- package/src/tests/participant-filters.test.js +151 -0
- package/src/tests/pushed-authorisation-request-endpoint.test.d.ts +1 -0
- package/src/tests/pushed-authorisation-request-endpoint.test.js +159 -0
- package/src/tests/relying-party-client-sdk.test.d.ts +1 -0
- package/src/tests/relying-party-client-sdk.test.js +313 -0
- package/src/tests/request-utils.test.d.ts +1 -0
- package/src/tests/request-utils.test.js +16 -0
- package/src/tests/system-information.test.d.ts +1 -0
- package/src/tests/system-information.test.js +16 -0
- package/src/tests/user-agent.test.d.ts +1 -0
- package/src/tests/user-agent.test.js +23 -0
- package/src/tests/validator.test.d.ts +1 -0
- package/src/tests/validator.test.js +38 -0
- package/{types.d.ts → src/types.d.ts} +61 -32
- package/src/types.js +1 -0
- package/{utils → src/utils}/request-utils.d.ts +1 -1
- package/src/utils/request-utils.js +8 -0
- package/{utils → src/utils}/user-agent.d.ts +1 -1
- package/src/utils/user-agent.js +4 -0
- package/relying-party-client-sdk.d.ts +0 -37
- package/relying-party-client-sdk.js +0 -364
- package/utils/request-utils.js +0 -8
- package/utils/user-agent.js +0 -6
- /package/{config.d.ts → src/config.d.ts} +0 -0
- /package/{types.js → src/conformance/conformance.test.d.ts} +0 -0
- /package/{filter → src/filter}/participant-filters.d.ts +0 -0
- /package/{filter → src/filter}/participant-filters.js +0 -0
- /package/{logger.d.ts → src/logger.d.ts} +0 -0
- /package/{logger.js → src/logger.js} +0 -0
- /package/{utils → src/utils}/cert-utils.d.ts +0 -0
- /package/{utils → src/utils}/cert-utils.js +0 -0
- /package/{utils → src/utils}/functional-utils.d.ts +0 -0
- /package/{utils → src/utils}/functional-utils.js +0 -0
- /package/{utils → src/utils}/system-information.d.ts +0 -0
- /package/{utils → src/utils}/system-information.js +0 -0
- /package/{validator.d.ts → src/validator.d.ts} +0 -0
- /package/{validator.js → src/validator.js} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@connectid-tools/rp-nodejs-sdk",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"description": "Digital Identity Relying Party Node SDK",
|
|
5
5
|
"main": "relying-party-client-sdk.js",
|
|
6
6
|
"types": "relying-party-client-sdk.d.ts",
|
|
@@ -31,18 +31,20 @@
|
|
|
31
31
|
"homepage": "https://github.com/connectid-tools/rp-nodejs-sample-app",
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"https": "^1.0.0",
|
|
34
|
-
"
|
|
35
|
-
"
|
|
34
|
+
"jose": "^6.0.0",
|
|
35
|
+
"undici": "^7.16.0",
|
|
36
36
|
"winston": "^3.17.0"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@types/node": "^20.19.9",
|
|
40
|
-
"@types/openid-client": "^3.7.0",
|
|
41
40
|
"add-js-extension": "^1.0.4",
|
|
42
41
|
"eslint": "^9.32.0",
|
|
43
42
|
"prettier": "^3.6.2",
|
|
44
|
-
"replace-in-files-cli": "^
|
|
43
|
+
"replace-in-files-cli": "^4.0.0",
|
|
45
44
|
"tsx": "^4.20.3",
|
|
46
45
|
"typescript": "^5.9.2"
|
|
46
|
+
},
|
|
47
|
+
"overrides": {
|
|
48
|
+
"node-forge": "^1.3.2"
|
|
47
49
|
}
|
|
48
50
|
}
|
|
@@ -40,36 +40,7 @@ export const config = {
|
|
|
40
40
|
// The purpose to be displayed to the consumer to indicate why their data is being requested to be shared
|
|
41
41
|
// Must be between 3 and 300 chars and not contain any of the following characters: <>(){}'\
|
|
42
42
|
purpose: 'verifying your identity',
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
// Update with your client specific metadata. The client_id and organisation_id can be found in the registry.
|
|
46
|
-
client_id: 'https://rp.directory.sandbox.connectid.com.au/openid_relying_party/280518db-9807-4824-b080-324d94b45f6a',
|
|
47
|
-
organisation_id: 'ab837240-9618-4953-966e-90fd1fa63999',
|
|
48
|
-
jwks_uri: 'https://keystore.directory.sandbox.connectid.com.au/ab837240-9618-4953-966e-90fd1fa63999/280518db-9807-4824-b080-324d94b45f6a/application.jwks',
|
|
49
|
-
redirect_uris: ['https://demo.relyingpart.net/cb', 'https://tpp.localhost/cb'],
|
|
50
|
-
organisation_name: 'ConnectID Developer Tools Sample App',
|
|
51
|
-
organisation_number: 'ABN123123123',
|
|
52
|
-
software_description: 'App to demonstrate ConnectID end to end flows.',
|
|
53
|
-
// The following config is here for reference - you should not need to change any of it
|
|
54
|
-
application_type: 'web',
|
|
55
|
-
grant_types: ['client_credentials', 'authorization_code', 'implicit'],
|
|
56
|
-
id_token_signed_response_alg: 'PS256',
|
|
57
|
-
post_logout_redirect_uris: [],
|
|
58
|
-
require_auth_time: false,
|
|
59
|
-
response_types: ['code id_token', 'code'],
|
|
60
|
-
subject_type: 'public',
|
|
61
|
-
token_endpoint_auth_method: 'private_key_jwt',
|
|
62
|
-
token_endpoint_auth_signing_alg: 'PS256',
|
|
63
|
-
introspection_endpoint_auth_method: 'private_key_jwt',
|
|
64
|
-
revocation_endpoint_auth_method: 'private_key_jwt',
|
|
65
|
-
request_object_signing_alg: 'PS256',
|
|
66
|
-
require_signed_request_object: true,
|
|
67
|
-
require_pushed_authorization_requests: true,
|
|
68
|
-
authorization_signed_response_alg: 'PS256',
|
|
69
|
-
tls_client_certificate_bound_access_tokens: true,
|
|
70
|
-
backchannel_user_code_parameter: false,
|
|
71
|
-
scope: 'openid',
|
|
72
|
-
software_roles: ['RP-CORE'],
|
|
73
|
-
},
|
|
43
|
+
// Update with your client specific metadata. The client_id can be found in the registry.
|
|
44
|
+
client_id: 'https://rp.directory.sandbox.connectid.com.au/openid_relying_party/280518db-9807-4824-b080-324d94b45f6a',
|
|
74
45
|
},
|
|
75
46
|
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
interface TestModuleInstance {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
url: string;
|
|
5
|
+
}
|
|
6
|
+
interface PlanInfo {
|
|
7
|
+
id: string;
|
|
8
|
+
name: string;
|
|
9
|
+
modules: Module[];
|
|
10
|
+
}
|
|
11
|
+
interface Module {
|
|
12
|
+
testModule: string;
|
|
13
|
+
}
|
|
14
|
+
interface TestInformation {
|
|
15
|
+
planId: string;
|
|
16
|
+
testId: string;
|
|
17
|
+
testName: string;
|
|
18
|
+
started: string;
|
|
19
|
+
status: string;
|
|
20
|
+
result: string;
|
|
21
|
+
version: string;
|
|
22
|
+
}
|
|
23
|
+
interface ErrorResponse {
|
|
24
|
+
error: string;
|
|
25
|
+
message: string;
|
|
26
|
+
}
|
|
27
|
+
declare class ConformanceApi {
|
|
28
|
+
private readonly bearerToken;
|
|
29
|
+
private readonly baseUrl;
|
|
30
|
+
constructor(bearerToken: string, baseUrl?: string);
|
|
31
|
+
createPlan(planName: string, variant: string, config: ConformanceConfig): Promise<PlanInfo>;
|
|
32
|
+
createTestFromPlan(planId: string, testName: string): Promise<TestModuleInstance>;
|
|
33
|
+
getPlanInfo(planId: string): Promise<PlanInfo>;
|
|
34
|
+
getTestInformation(testId: string): Promise<TestInformation>;
|
|
35
|
+
}
|
|
36
|
+
declare class ConformanceConfig {
|
|
37
|
+
}
|
|
38
|
+
export { ConformanceApi, ConformanceConfig, TestModuleInstance, PlanInfo, Module, TestInformation, ErrorResponse };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { URLSearchParams } from 'url';
|
|
2
|
+
class ConformanceApi {
|
|
3
|
+
constructor(bearerToken, baseUrl = 'https://www.certification.openid.net/') {
|
|
4
|
+
this.baseUrl = baseUrl;
|
|
5
|
+
this.bearerToken = bearerToken;
|
|
6
|
+
}
|
|
7
|
+
async createPlan(planName, variant, config) {
|
|
8
|
+
const urlSearchParams = new URLSearchParams({ planName, variant }).toString();
|
|
9
|
+
console.log('bearerToken', this.bearerToken);
|
|
10
|
+
console.log('baseUrl', this.baseUrl);
|
|
11
|
+
const response = await fetch(`${this.baseUrl}api/plan?${urlSearchParams}`, {
|
|
12
|
+
method: 'POST',
|
|
13
|
+
headers: {
|
|
14
|
+
'Content-Type': 'application/json',
|
|
15
|
+
Authorization: `Bearer ${this.bearerToken}`,
|
|
16
|
+
},
|
|
17
|
+
body: JSON.stringify(config),
|
|
18
|
+
});
|
|
19
|
+
return await response.json();
|
|
20
|
+
}
|
|
21
|
+
async createTestFromPlan(planId, testName) {
|
|
22
|
+
const urlSearchParams = new URLSearchParams({ test: testName, plan: planId }).toString();
|
|
23
|
+
const response = await fetch(`${this.baseUrl}api/runner?${urlSearchParams}`, {
|
|
24
|
+
method: 'POST',
|
|
25
|
+
headers: {
|
|
26
|
+
'Content-Type': 'application/json',
|
|
27
|
+
Authorization: `Bearer ${this.bearerToken}`,
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
return await response.json();
|
|
31
|
+
}
|
|
32
|
+
async getPlanInfo(planId) {
|
|
33
|
+
const response = await fetch(`${this.baseUrl}api/plan/${planId}`, {
|
|
34
|
+
headers: {
|
|
35
|
+
'Content-Type': 'application/json',
|
|
36
|
+
Authorization: `Bearer ${this.bearerToken}`,
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
return await response.json();
|
|
40
|
+
}
|
|
41
|
+
async getTestInformation(testId) {
|
|
42
|
+
const response = await fetch(`${this.baseUrl}api/info/${testId}`, {
|
|
43
|
+
headers: {
|
|
44
|
+
'Content-Type': 'application/json',
|
|
45
|
+
Authorization: `Bearer ${this.bearerToken}`,
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
return await response.json();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
class ConformanceConfig {
|
|
52
|
+
}
|
|
53
|
+
export { ConformanceApi, ConformanceConfig };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"alias": "conformancetest",
|
|
3
|
+
"client": {
|
|
4
|
+
"certificate": "-----BEGIN CERTIFICATE-----\nMIIGbjCCBVagAwIBAgIUf1OixaHH1iH+CVnP/HuYkNduJMYwDQYJKoZIhvcNAQEL\nBQAwdzELMAkGA1UEBhMCQVUxKDAmBgNVBAoTH2VmdHBvcyBEaWdpdGFsIElkZW50\naXR5IFB0eSBMdGQxEjAQBgNVBAsTCWNvbm5lY3RpZDEqMCgGA1UEAxMhY29ubmVj\ndGlkIFNBTkRCT1ggSXNzdWluZyBDQSAtIEcxMB4XDTI1MDYzMDA5NDYwMFoXDTI2\nMDczMDA5NDYwMFowfzELMAkGA1UEBhMCQVUxEjAQBgNVBAoTCWNvbm5lY3RpZDEt\nMCsGA1UECxMkYWI4MzcyNDAtOTYxOC00OTUzLTk2NmUtOTBmZDFmYTYzOTk5MS0w\nKwYDVQQDEyQyODA1MThkYi05ODA3LTQ4MjQtYjA4MC0zMjRkOTRiNDVmNmEwggEi\nMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDjIRxuBVItK1P9hPzF93ecY6I1\niUSYF5OL0KKsHaVJvZhG3l2UDkZldUP6mwzhIgo3TwEdiJD7aDen6ZjN98drBKJ/\nPpruDd+R+DP0ZoIbyFph3AbOywtqx3h8YuWGrwmVlVt+vXuXsPmD49/lVtodSvIe\nDP/qrXVsbcsq1kKm2DRjn4APPdpDLlt/cRCYqsISYb+ln1NEBp7kkUVhZR9BIlPS\nsHBCO4p2J7eMk7ZPVs9S6ZihL0bSg32tbatBL8sg3N1m5SjW6CMlHd0WTI+dRMPU\nNgjB05vvRLkH6dC2/lmJJl3Lx+PYAWvc/7jClmulRT61dD1cQBgOkSLVw1vzAgMB\nAAGjggLoMIIC5DAOBgNVHQ8BAf8EBAMCA6gwHQYDVR0lBBYwFAYIKwYBBQUHAwEG\nCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFC5fWr82Dx5ZO+AxsGnK\nYFY14xD8MB8GA1UdIwQYMBaAFCID4FOkkmygH6/lwRVx+ZS6Dk9xMEQGCCsGAQUF\nBwEBBDgwNjA0BggrBgEFBQcwAYYoaHR0cDovL29jc3AucGtpLnNhbmRib3guY29u\nbmVjdGlkLmNvbS5hdTBDBgNVHR8EPDA6MDigNqA0hjJodHRwOi8vY3JsLnBraS5z\nYW5kYm94LmNvbm5lY3RpZC5jb20uYXUvaXNzdWVyLmNybDCCAdgGA1UdIASCAc8w\nggHLMIIBxwYLKwYBBAGDui9sAQIwggG2MIIBbQYIKwYBBQUHAgIwggFfDIIBW1Ro\naXMgQ2VydGlmaWNhdGUgaXMgc29sZWx5IGZvciB1c2Ugd2l0aCBlZnRwb3MgRGln\naXRhbCBJZGVudGl0eSBQdHkgTHRkIGFuZCBvdGhlciBwYXJ0aWNpcGF0aW5nIG9y\nZ2FuaXNhdGlvbnMgdXNpbmcgZWZ0cG9zIERpZ2l0YWwgSWRlbnRpdHkgUHR5IEx0\nZCBzZXJ2aWNlcywgYXMgcHJvdmlkZWQgYnkgdGhlIGJ1c2luZXNzIGZyb20gdGlt\nZSB0byB0aW1lLiBJdHMgcmVjZWlwdCwgcG9zc2Vzc2lvbiBvciB1c2UgY29uc3Rp\ndHV0ZXMgYWNjZXB0YW5jZSBvZiB0aGUgZWZ0cG9zIERpZ2l0YWwgSWRlbnRpdHkg\nUHR5IEx0ZCBDZXJ0aWZpY2F0ZSBQb2xpY3kgYW5kIHJlbGF0ZWQgZG9jdW1lbnRz\nIHRoZXJlaW4uMEMGCCsGAQUFBwIBFjdodHRwOi8vcmVwb3NpdG9yeS5wa2kuc2Fu\nZGJveC5jb25uZWN0aWQuY29tLmF1L3BvbGljaWVzMA0GCSqGSIb3DQEBCwUAA4IB\nAQBSi9yE4wF7iTym+HVqKzGfCP2cl7HWJey0zEMUnmLW6s0FcHlxuU5Qw7+L2OTD\nIOCEKwyBNddCC/gnlXkrqC0/71JLbOrtEdZls9DsO1P+BQG3OuDnlo3xSaPNVRXM\naCB3lrl4MGo3q9du2rOivlD0/aof8D6aLapkOsNgtwJLW4ntnpOgRTxPgI8HHfXr\nsmUmmdz2RiJ8UjYffl8or9XSlBz4nOsLrPPUy+T/eiLZsooEvmqvz+ptlX49e+pl\npfMWZX8UBNZbp4wd3lw89A/BMBsSLCmQ/prJyw5FDEMvxTg9wUhs/+pLFpaCFc6J\nX5lsYKc5Hf4Dll4t8Hb/fhjV\n-----END CERTIFICATE-----",
|
|
5
|
+
"client_id": "https://rp.directory.sandbox.connectid.com.au/openid_relying_party/280518db-9807-4824-b080-324d94b45f6a",
|
|
6
|
+
"jwks": {
|
|
7
|
+
"keys": [
|
|
8
|
+
{
|
|
9
|
+
"kty": "RSA",
|
|
10
|
+
"use": "sig",
|
|
11
|
+
"x5c": [
|
|
12
|
+
"MIIGTzCCBTegAwIBAgIUGFmcYfeY/2xuP2RRwBwNDdijwEYwDQYJKoZIhvcNAQELBQAwdzELMAkGA1UEBhMCQVUxKDAmBgNVBAoTH2VmdHBvcyBEaWdpdGFsIElkZW50aXR5IFB0eSBMdGQxEjAQBgNVBAsTCWNvbm5lY3RpZDEqMCgGA1UEAxMhY29ubmVjdGlkIFNBTkRCT1ggSXNzdWluZyBDQSAtIEcxMB4XDTI1MDYzMDA5NDUwMFoXDTI2MDczMDA5NDUwMFowfzELMAkGA1UEBhMCQVUxEjAQBgNVBAoTCWNvbm5lY3RpZDEtMCsGA1UECxMkYWI4MzcyNDAtOTYxOC00OTUzLTk2NmUtOTBmZDFmYTYzOTk5MS0wKwYDVQQDEyQyODA1MThkYi05ODA3LTQ4MjQtYjA4MC0zMjRkOTRiNDVmNmEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3Z5vutMgWpAfMWbMuta8OXmqpWoU6Dm66CImoot2HhdhOk4T4ijZpntUHbU1k3Lz2hXijj9F1DBVwU5H3g7JuKanw+5uPPz1aLZ1rhHtb7NI+QPx56FoC4VPMw81ZtLeHBda3ah7DX0q0vfkeSTPpULJNVq56NkRcyaFZUz5jgeyVRkZwz3OBVbqO4tFaUR1tF+3m9CXZJLA864bP9L9/4wTYJG6BDz1SjX2qoXWm1eUnCVlqrZBAUx3/eKgaksmRJGNTify6BcbubIYxfLchwCt0W3lZDPknBVZKu7ahUvq0VlbVvwiA+1Nnjw9VDP975jm8qruwJxMjQuCvGJ7bAgMBAAGjggLJMIICxTAOBgNVHQ8BAf8EBAMCA7gwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUTtU+nUL63TcTDLnxn89lhudPa4IwHwYDVR0jBBgwFoAUIgPgU6SSbKAfr+XBFXH5lLoOT3EwRAYIKwYBBQUHAQEEODA2MDQGCCsGAQUFBzABhihodHRwOi8vb2NzcC5wa2kuc2FuZGJveC5jb25uZWN0aWQuY29tLmF1MEMGA1UdHwQ8MDowOKA2oDSGMmh0dHA6Ly9jcmwucGtpLnNhbmRib3guY29ubmVjdGlkLmNvbS5hdS9pc3N1ZXIuY3JsMIIB2AYDVR0gBIIBzzCCAcswggHHBgsrBgEEAYO6L2wBAjCCAbYwggFtBggrBgEFBQcCAjCCAV8MggFbVGhpcyBDZXJ0aWZpY2F0ZSBpcyBzb2xlbHkgZm9yIHVzZSB3aXRoIGVmdHBvcyBEaWdpdGFsIElkZW50aXR5IFB0eSBMdGQgYW5kIG90aGVyIHBhcnRpY2lwYXRpbmcgb3JnYW5pc2F0aW9ucyB1c2luZyBlZnRwb3MgRGlnaXRhbCBJZGVudGl0eSBQdHkgTHRkIHNlcnZpY2VzLCBhcyBwcm92aWRlZCBieSB0aGUgYnVzaW5lc3MgZnJvbSB0aW1lIHRvIHRpbWUuIEl0cyByZWNlaXB0LCBwb3NzZXNzaW9uIG9yIHVzZSBjb25zdGl0dXRlcyBhY2NlcHRhbmNlIG9mIHRoZSBlZnRwb3MgRGlnaXRhbCBJZGVudGl0eSBQdHkgTHRkIENlcnRpZmljYXRlIFBvbGljeSBhbmQgcmVsYXRlZCBkb2N1bWVudHMgdGhlcmVpbi4wQwYIKwYBBQUHAgEWN2h0dHA6Ly9yZXBvc2l0b3J5LnBraS5zYW5kYm94LmNvbm5lY3RpZC5jb20uYXUvcG9saWNpZXMwDQYJKoZIhvcNAQELBQADggEBAJCvUcPNxrnYow+Xnb7MsPIHrMY8T16nG+3iQqtdJXJw5QoWJ2PnuoCQltleU9ilvO++5Uh5PswGLoNiCCtj7aQmOW9w1CL4b2FSgL4nuWfjgQzpNMQtb+dqL928R7GiksK++apW759sunDB/VlFUjypADpZHaLfc0VvtX8nq/o/lmwsycONLV1IKnuWZp0VmY5BsGpnlx57DHqVx2OPu0zu4rkxz9Rmdzdg619vpRYApnbepMiN6NyylF+Et0/qDmK7xMSKNm4JNGldSSh+D43/ccKYFVORcsLKPH4DE+VnWEA+z2DF72BSyLZuS4gD0QP3ZG0XAWktHHrn46PyINo="
|
|
13
|
+
],
|
|
14
|
+
"n": "t2eb7rTIFqQHzFmzLrWvDl5qqVqFOg5uugiJqKLdh4XYTpOE-Io2aZ7VB21NZNy89oV4o4_RdQwVcFOR94Oybimp8Pubjz89Wi2da4R7W-zSPkD8eehaAuFTzMPNWbS3hwXWt2oew19KtL35Hkkz6VCyTVauejZEXMmhWVM-Y4HslUZGcM9zgVW6juLRWlEdbRft5vQl2SSwPOuGz_S_f-ME2CRugQ89Uo19qqF1ptXlJwlZaq2QQFMd_3ioGpLJkSRjU4n8ugXG7myGMXy3IcArdFt5WQz5JwVWSru2oVL6tFZW1b8IgPtTZ48PVQz_e-Y5vKq7sCcTI0Lgrxie2w",
|
|
15
|
+
"e": "AQAB",
|
|
16
|
+
"kid": "lHf9shwoF1wEES2sB9TBafbs0AVrLiU-1_ntzCrBo8A",
|
|
17
|
+
"x5u": "https://keystore.directory.sandbox.connectid.com.au/ab837240-9618-4953-966e-90fd1fa63999/280518db-9807-4824-b080-324d94b45f6a/lHf9shwoF1wEES2sB9TBafbs0AVrLiU-1_ntzCrBo8A.pem",
|
|
18
|
+
"x5t#S256": "lHf9shwoF1wEES2sB9TBafbs0AVrLiU-1_ntzCrBo8A",
|
|
19
|
+
"x5dn": "CN=280518db-9807-4824-b080-324d94b45f6a,OU=ab837240-9618-4953-966e-90fd1fa63999,O=connectid,C=AU"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"kty": "RSA",
|
|
23
|
+
"use": "sig",
|
|
24
|
+
"x5c": [
|
|
25
|
+
"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"
|
|
26
|
+
],
|
|
27
|
+
"n": "ikku68s3okqL55DLEfiR02l0SMUn9UfZdhOUSGfkrEGBKRuf2ZeUJqGB6jG_bUOuvAL1HQjeddCNkaxpCTfRGKSI3D0p2fr-Aswq_Ms4J2jtkJ_bDL0Zdpjsf0hekKf1j1WCvCJK7zFQ6N7OA5oVp6UFtTFuT68ymeHB5lubKLSkjL4f5SbZBZ-jHw-zoIyiptd0y1KiXXCoIxEbd3Y5b_Dx5aG2YTVzidB0V-RUDoAKJYQbJMVYzlq30P3TkXWD5qwIa6GZr_CZpw6SncbkNqZMDQzgqFhd8J0KczVThXf-qlc4LNh0g0wbYjNcidQ6FDCHJESzDiZwECXT_XFc_w",
|
|
28
|
+
"e": "AQAB",
|
|
29
|
+
"kid": "6kWTo-i7nrIJrdl6jxfDyT0Nprw-aRT0mcZvxvXtKVE",
|
|
30
|
+
"x5u": "https://keystore.directory.sandbox.connectid.com.au/ab837240-9618-4953-966e-90fd1fa63999/280518db-9807-4824-b080-324d94b45f6a/6kWTo-i7nrIJrdl6jxfDyT0Nprw-aRT0mcZvxvXtKVE.pem",
|
|
31
|
+
"x5t#S256": "6kWTo-i7nrIJrdl6jxfDyT0Nprw-aRT0mcZvxvXtKVE",
|
|
32
|
+
"x5dn": "CN=280518db-9807-4824-b080-324d94b45f6a,OU=ab837240-9618-4953-966e-90fd1fa63999,O=connectid,C=AU"
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
},
|
|
36
|
+
"redirect_uri": "https://tpp.localhost/cb"
|
|
37
|
+
},
|
|
38
|
+
"consent": {},
|
|
39
|
+
"description": "NodeJS Conformance Test Template",
|
|
40
|
+
"server": {
|
|
41
|
+
"jwks": {
|
|
42
|
+
"keys": [
|
|
43
|
+
{
|
|
44
|
+
"p": "9ROiJdNH4qHPe4K92GpxUpQY2T2EP2KjbdOp16TFaL6DmXdZLaJzaYEpinR4NtSnuEpO2nl441_kma1JP99p6VdtmLUvTRUeCEUJXdFh_bqhc0INfGHRuXNBeLLQFcrRRS5Vhi50IM_NqYmpO1GEshxNYfKCweTCTkEwsJzIZkM",
|
|
45
|
+
"kty": "RSA",
|
|
46
|
+
"q": "nluxAF8DH-LhGz8qB3qzVhBaD7y5JB_pZ3SMaYPCV92yWXRysgNvYx2CyaWOtrIPabfzTBauR1XXEiLccxzQRy2f8bQ7pwJZtQJJtmC4UwCnNnIcdQ1WsjzzHMqd0p45gJQVnXpxmPfThIZeotToW4f4CTHZjJW8fKbd2zDg61U",
|
|
47
|
+
"d": "a6N1dGahEGBMd9KYBbwyuliqebughkoNsUeBRFDg29wReDYDfxsCx9nIqS-xNtkkVQYdG7t8FKL8IVapbuJGFcLKvu_w6kbJ7v2csTN71DETrIH4W1xHlor0QuCShYdKeUG_xZjkBznVv8m_2FwzpRNo4WaVHQGwNoiBU5cIBmeA9XxlaGuabaagU6F0z4ArJzl0DIR6XjLYH8DTxb4KFuMvouBS_o9eDw-t1ubU5ffD28nyOY7UvSTd2_kUw4xyWqfyCvN5oTvFNFaJ8EmgkaycvPDkxqWIWOBRoCEk2gAz0UV14t4iPCV75czuA5dc9HlzAh57Ds3k3frm8jAUoQ",
|
|
48
|
+
"e": "AQAB",
|
|
49
|
+
"use": "sig",
|
|
50
|
+
"kid": "ruWVspGvgNx3V22h4ufR9yr64sjuOaaVwDB_i5rwdOM",
|
|
51
|
+
"qi": "0dm4Eb27B9VAP8SoXfE7aUSp0RimmBN3LKlucdL5h1LGyaCFNeRgYHJHo6BL33TKL-tL8m9y0ztyePtVe_EMGtP0QEws6oti8-SUK0ynQ4cXet8fH5uqJx-ArGgW_KDi3xvTeScy4faK3toyzStPZCG4jFdWyv1f2vbv4qg4rDU",
|
|
52
|
+
"dp": "8fkPmK8yA6bl17nvvbTi7LjCjAN8BqVaXT6mK_9I1jF8d9Lp3u_NafcYT9bNNr3iV0gu8PEMldsBN2Zrsz_gL36d_C-wYzgdbebT56irSryxWb522D8wth0BIK3UXB_jXZ3w3UoSaK8kDWeZCrNjBASDttidl9lIq8Eb1NUH3Ec",
|
|
53
|
+
"alg": "PS256",
|
|
54
|
+
"dq": "TTdAc4HgsCecxABkqgj2cTy_7XSEgkzdLojx_nE0zktXr67MTmjGY3n8T_7eO89PHKmJhMx6ZmZA3KMLA0ZFeK-SkfTkMWc__rcC4l7_AdoLrsyte5XpdDesA5n4or5sI3oRoBwYUBJnnPM4KgXO1vLRywn3nklVAyMKgtqukZE",
|
|
55
|
+
"n": "l5nlgOnp5m-klhQGYjcxytnu-vM6CpPFyhzuLII31Y4JT2sJjpItN4DOWRVs5sWL7aj_otQNYWrBz9JJOUJJ2oGIQDq2UBn9nHDTKqCWCZ8r2Gtq9U7z-mFfHx8f67hY6KFmeuMdVcqCBcNN76ZHtCkvRiziglvtYkMpovWF2cGqeyCuunlTr0clMe2N_iA0wOgLwiYDBtvqLi8DBshRcGzTHHlncW-OLdspu0saOb2igGMEaBAuzY2EsL0E2jZODB5bfPfA3WDuo7Swi2FVqDuaudm3pJofOr02lDidJNQCOBJRJhHVH867vFK12_xtgCDAydWn9qNub19nDwl1Pw"
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export const conformanceConfig = {
|
|
2
|
+
data: {
|
|
3
|
+
// Set the signing Key Id based on what is contained in the JWKS
|
|
4
|
+
signing_kid: 'lHf9shwoF1wEES2sB9TBafbs0AVrLiU-1_ntzCrBo8A',
|
|
5
|
+
// The location of the signing certificate and key that are used for signing purposes
|
|
6
|
+
signing_key: './certs/signing.key',
|
|
7
|
+
signing_pem: './certs/signing.pem', // TODO not being used atm
|
|
8
|
+
// The content (string) of the signing certificate and key that are used for signing purposes - overrides `signing_key` and `signing_pem`
|
|
9
|
+
signing_key_content: '',
|
|
10
|
+
signing_pem_content: '', // TODO not being used atm
|
|
11
|
+
// The location of the transport certificate and key that are used for mutual TLS
|
|
12
|
+
transport_key: './certs/transport.key',
|
|
13
|
+
transport_pem: './certs/transport.pem',
|
|
14
|
+
// The content (string) of the transport certificate and key that are used for mutual TLS - overrides `transport_key` and `transport_pem`
|
|
15
|
+
transport_key_content: '',
|
|
16
|
+
transport_pem_content: '',
|
|
17
|
+
// The location of the root certificate for the trust authority
|
|
18
|
+
ca_pem: './certs/ca.pem',
|
|
19
|
+
// The content (string) of the root certificate for the trust authority - overrides `ca_pem`
|
|
20
|
+
ca_pem_content: '',
|
|
21
|
+
// This is the URL that this application is actually running on and using for callbacks (noting that multiple may be registered for the client)
|
|
22
|
+
application_redirect_uri: 'https://tpp.localhost/cb',
|
|
23
|
+
// The registry API endpoint that will list all participants with their auth server details
|
|
24
|
+
registry_participants_uri: 'https://api.sandbox.connectid.com.au/oidf-conformance/participants?alias=a/conformance-nodejs',
|
|
25
|
+
// The application logging level (info - normal logging, debug - full request/response)
|
|
26
|
+
// This MUST not be set to debug in a production environment as it will log all personal data received
|
|
27
|
+
log_level: 'info',
|
|
28
|
+
// When running the OIDC FAPI compliance suite, it requires a call to user info after successfully decoding the
|
|
29
|
+
// response claims. If this is set to true, the SDK will automatically make the call.
|
|
30
|
+
enable_auto_compliance_verification: true,
|
|
31
|
+
// Update with your client specific metadata. The client_id and organisation_id can be found in the registry.
|
|
32
|
+
client_id: 'https://rp.directory.sandbox.connectid.com.au/openid_relying_party/280518db-9807-4824-b080-324d94b45f6a',
|
|
33
|
+
},
|
|
34
|
+
};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import RelyingPartyClientSdk from '../relying-party-client-sdk.js';
|
|
2
|
+
import { conformanceConfig } from './conformance-config.js';
|
|
3
|
+
import config from './config.json';
|
|
4
|
+
import variant from './variant.json';
|
|
5
|
+
import packageJson from '../../package.json';
|
|
6
|
+
import { parse } from 'url';
|
|
7
|
+
import { ConformanceApi } from './api/conformance-api.js';
|
|
8
|
+
import winston from 'winston';
|
|
9
|
+
import { describe, before, it } from 'node:test';
|
|
10
|
+
import assert from 'node:assert';
|
|
11
|
+
const alias = 'conformance-nodejs-' + Date.now();
|
|
12
|
+
const conformanceEnv = process.env.CONFORMANCE_ENV || 'production';
|
|
13
|
+
const conformanceApiToken = process.env.CONFORMANCE_API_TOKEN || '';
|
|
14
|
+
const conformanceNodeVersion = process.env.CONFORMANCE_NODE_VERSION || 'unknown';
|
|
15
|
+
const participantsEnv = conformanceEnv === 'production' ? '' : conformanceEnv;
|
|
16
|
+
conformanceConfig.data.registry_participants_uri = `https://api.sandbox.connectid.com.au/oidf-conformance/participants?alias=a/${alias}&env=${participantsEnv}`;
|
|
17
|
+
const rpClient = new RelyingPartyClientSdk(conformanceConfig);
|
|
18
|
+
const conformanceEnvUrls = new Map([
|
|
19
|
+
['staging', 'https://staging.certification.openid.net/'],
|
|
20
|
+
['production', 'https://www.certification.openid.net/']
|
|
21
|
+
]);
|
|
22
|
+
const conformanceBaseUrl = conformanceEnvUrls.get(conformanceEnv);
|
|
23
|
+
console.log(`Selected environment ${conformanceEnv}, baseUrl: ${conformanceBaseUrl}`);
|
|
24
|
+
const conformanceApi = new ConformanceApi(conformanceApiToken, conformanceBaseUrl);
|
|
25
|
+
describe('Conformance Test', { timeout: 600000 }, () => {
|
|
26
|
+
let planInfo;
|
|
27
|
+
before(async () => {
|
|
28
|
+
const planName = 'fapi2-message-signing-id1-client-test-plan';
|
|
29
|
+
config.description = `NodeJS SDK ${packageJson.version} (NodeJS v${conformanceNodeVersion})`;
|
|
30
|
+
config.alias = alias;
|
|
31
|
+
planInfo = await conformanceApi.createPlan(planName, JSON.stringify(variant), config);
|
|
32
|
+
});
|
|
33
|
+
it('should execute the conformance test', async () => {
|
|
34
|
+
const testModules = planInfo.modules.map((module) => module.testModule);
|
|
35
|
+
for (const testName of testModules) {
|
|
36
|
+
console.log(`Executing test ${testName}`);
|
|
37
|
+
// We are mimicking an application log here.
|
|
38
|
+
const logger = createTestLogger(testName);
|
|
39
|
+
rpClient.logger = logger;
|
|
40
|
+
const testInstance = await conformanceApi.createTestFromPlan(planInfo.id, testName);
|
|
41
|
+
let testInformation = await conformanceApi.getTestInformation(testInstance.id);
|
|
42
|
+
logger.info(`Executing version ${testInformation.version} of ${testName}`);
|
|
43
|
+
console.log(`See ${conformanceBaseUrl}log-detail.html?log=${testInstance.id}`);
|
|
44
|
+
const idps = await rpClient.getParticipants();
|
|
45
|
+
const authorisationServerId = idps[0].AuthorisationServers[0].AuthorisationServerId;
|
|
46
|
+
logger.info(`Sending PAR to ${authorisationServerId}`);
|
|
47
|
+
const essentialClaims = ['given_name', 'middle_name', 'family_name', 'phone_number', 'email', 'address', 'birthdate', 'txn'];
|
|
48
|
+
const parResponse = await rpClient.sendPushedAuthorisationRequest(authorisationServerId, essentialClaims);
|
|
49
|
+
const response = await fetch(parResponse.authUrl, {
|
|
50
|
+
redirect: 'manual',
|
|
51
|
+
});
|
|
52
|
+
if (response.status !== 303) {
|
|
53
|
+
console.error(`Expected 303, got ${response.status}`);
|
|
54
|
+
throw new Error(`Expected 303, got ${response.status}`);
|
|
55
|
+
}
|
|
56
|
+
const location = response.headers.get('location');
|
|
57
|
+
if (!location) {
|
|
58
|
+
throw new Error('No location header');
|
|
59
|
+
}
|
|
60
|
+
const locationObj = parse(location, true);
|
|
61
|
+
logger.info(`Executing ${testName}`);
|
|
62
|
+
try {
|
|
63
|
+
logger.info('Getting tokens');
|
|
64
|
+
await rpClient.retrieveTokens(authorisationServerId, locationObj.query, parResponse.codeVerifier, parResponse.state, parResponse.nonce);
|
|
65
|
+
logger.info('Tokens successfully retrieved');
|
|
66
|
+
}
|
|
67
|
+
catch (e) {
|
|
68
|
+
logger.error('An error occured while getting tokens:' + getErrorMessage(e));
|
|
69
|
+
console.log(e);
|
|
70
|
+
}
|
|
71
|
+
while (testInformation.status !== 'FINISHED') {
|
|
72
|
+
await delay(250);
|
|
73
|
+
testInformation = await conformanceApi.getTestInformation(testInstance.id);
|
|
74
|
+
}
|
|
75
|
+
logger.info(`Test finished with result: ${testInformation.result}, status: ${testInformation.status}`);
|
|
76
|
+
console.log('Full test information:', JSON.stringify(testInformation, null, 2));
|
|
77
|
+
assert.ok(['WARNING', 'PASSED'].includes(testInformation.result));
|
|
78
|
+
if (testInformation.result === 'WARNING') {
|
|
79
|
+
console.log(`*** A warning occurred while executing ${testName}, please check the logs!!! ***`);
|
|
80
|
+
}
|
|
81
|
+
// TODO: download the logs and store them in Github
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
function delay(time) {
|
|
86
|
+
// @ts-ignore
|
|
87
|
+
return new Promise((resolve) => setTimeout(resolve, time));
|
|
88
|
+
}
|
|
89
|
+
function getErrorMessage(error) {
|
|
90
|
+
if (error instanceof Error)
|
|
91
|
+
return error.message;
|
|
92
|
+
return String(error);
|
|
93
|
+
}
|
|
94
|
+
function createTestLogger(testName) {
|
|
95
|
+
return winston.createLogger({
|
|
96
|
+
level: 'info',
|
|
97
|
+
format: winston.format.simple(),
|
|
98
|
+
defaultMeta: { service: 'conformance-test' },
|
|
99
|
+
transports: [new winston.transports.File({ filename: `logs/${testName}.log` })],
|
|
100
|
+
});
|
|
101
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{ "client_auth_type": "private_key_jwt", "fapi_request_method": "signed_non_repudiation", "fapi_client_type": "oidc", "sender_constrain": "mtls", "fapi_profile": "connectid_au", "fapi_response_mode": "plain_response" }
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { KeyObject } from 'node:crypto';
|
|
2
|
+
/**
|
|
3
|
+
* Utility class for loading cryptographic keys and certificates.
|
|
4
|
+
*
|
|
5
|
+
* Handles conversion from PEM format to Node.js crypto objects.
|
|
6
|
+
*/
|
|
7
|
+
export declare class CryptoLoader {
|
|
8
|
+
/**
|
|
9
|
+
* Loads a private key from PEM format.
|
|
10
|
+
*
|
|
11
|
+
* @param keyPem - Private key in PEM format (string or Buffer)
|
|
12
|
+
* @returns KeyObject that can be used for signing operations
|
|
13
|
+
* @throws Error if the key cannot be loaded
|
|
14
|
+
*/
|
|
15
|
+
static loadPrivateKey(keyPem: string | Buffer): KeyObject;
|
|
16
|
+
/**
|
|
17
|
+
* Loads a certificate from PEM format.
|
|
18
|
+
*
|
|
19
|
+
* @param certPem - Certificate in PEM format (string or Buffer)
|
|
20
|
+
* @returns Certificate as string
|
|
21
|
+
*/
|
|
22
|
+
static loadCertificate(certPem: string | Buffer): string;
|
|
23
|
+
/**
|
|
24
|
+
* Loads a certificate chain from PEM format.
|
|
25
|
+
*
|
|
26
|
+
* Handles single or multiple certificates in a PEM bundle.
|
|
27
|
+
*
|
|
28
|
+
* @param caPem - CA certificate(s) in PEM format (string or Buffer)
|
|
29
|
+
* @returns Array of certificates as strings
|
|
30
|
+
*/
|
|
31
|
+
static loadCertificateChain(caPem: string | Buffer): string[];
|
|
32
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { createPrivateKey } from 'node:crypto';
|
|
2
|
+
/**
|
|
3
|
+
* Utility class for loading cryptographic keys and certificates.
|
|
4
|
+
*
|
|
5
|
+
* Handles conversion from PEM format to Node.js crypto objects.
|
|
6
|
+
*/
|
|
7
|
+
export class CryptoLoader {
|
|
8
|
+
/**
|
|
9
|
+
* Loads a private key from PEM format.
|
|
10
|
+
*
|
|
11
|
+
* @param keyPem - Private key in PEM format (string or Buffer)
|
|
12
|
+
* @returns KeyObject that can be used for signing operations
|
|
13
|
+
* @throws Error if the key cannot be loaded
|
|
14
|
+
*/
|
|
15
|
+
static loadPrivateKey(keyPem) {
|
|
16
|
+
try {
|
|
17
|
+
return createPrivateKey(keyPem);
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
throw new Error(`Failed to load private key: ${error instanceof Error ? error.message : String(error)}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Loads a certificate from PEM format.
|
|
25
|
+
*
|
|
26
|
+
* @param certPem - Certificate in PEM format (string or Buffer)
|
|
27
|
+
* @returns Certificate as string
|
|
28
|
+
*/
|
|
29
|
+
static loadCertificate(certPem) {
|
|
30
|
+
return typeof certPem === 'string' ? certPem : certPem.toString('utf-8');
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Loads a certificate chain from PEM format.
|
|
34
|
+
*
|
|
35
|
+
* Handles single or multiple certificates in a PEM bundle.
|
|
36
|
+
*
|
|
37
|
+
* @param caPem - CA certificate(s) in PEM format (string or Buffer)
|
|
38
|
+
* @returns Array of certificates as strings
|
|
39
|
+
*/
|
|
40
|
+
static loadCertificateChain(caPem) {
|
|
41
|
+
const pemString = typeof caPem === 'string' ? caPem : caPem.toString('utf-8');
|
|
42
|
+
// Split on BEGIN CERTIFICATE markers to handle certificate chains
|
|
43
|
+
const certificates = pemString
|
|
44
|
+
.split(/(?=-----BEGIN CERTIFICATE-----)/)
|
|
45
|
+
.map((cert) => cert.trim())
|
|
46
|
+
.filter((cert) => cert.length > 0);
|
|
47
|
+
return certificates;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { KeyObject } from 'node:crypto';
|
|
2
|
+
import { ClaimsRequest } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Parameters for generating a request JWT (PAR request object).
|
|
5
|
+
*/
|
|
6
|
+
export interface RequestJwtParams {
|
|
7
|
+
issuer: string;
|
|
8
|
+
audience: string;
|
|
9
|
+
redirectUri: string;
|
|
10
|
+
scope: string;
|
|
11
|
+
responseType: string;
|
|
12
|
+
codeChallenge: string;
|
|
13
|
+
codeChallengeMethod: string;
|
|
14
|
+
state: string;
|
|
15
|
+
nonce: string;
|
|
16
|
+
claims: ClaimsRequest;
|
|
17
|
+
purpose: string;
|
|
18
|
+
prompt: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Helper class for JWT operations.
|
|
22
|
+
*
|
|
23
|
+
* Handles creation and signing of JWTs for:
|
|
24
|
+
* - Request objects (PAR)
|
|
25
|
+
* - Client assertions (token endpoint authentication)
|
|
26
|
+
*
|
|
27
|
+
* Uses the PS256 algorithm (RSA-PSS with SHA-256) as required by FAPI.
|
|
28
|
+
*/
|
|
29
|
+
export declare class JwtHelper {
|
|
30
|
+
private readonly signingKey;
|
|
31
|
+
private readonly signingKid;
|
|
32
|
+
private readonly clientId;
|
|
33
|
+
/**
|
|
34
|
+
* Creates a new JwtHelper instance.
|
|
35
|
+
*
|
|
36
|
+
* @param signingKey - Private key for signing JWTs
|
|
37
|
+
* @param signingKid - Key ID to include in JWT header
|
|
38
|
+
* @param clientId - OAuth client ID
|
|
39
|
+
*/
|
|
40
|
+
constructor(signingKey: KeyObject, signingKid: string, clientId: string);
|
|
41
|
+
/**
|
|
42
|
+
* Generates a signed request JWT for PAR.
|
|
43
|
+
*
|
|
44
|
+
* The request object contains all authorization request parameters
|
|
45
|
+
* and is signed to prevent tampering.
|
|
46
|
+
*
|
|
47
|
+
* @param params - Request parameters
|
|
48
|
+
* @returns Signed JWT string
|
|
49
|
+
*/
|
|
50
|
+
generateRequestJwt(params: RequestJwtParams): Promise<string>;
|
|
51
|
+
/**
|
|
52
|
+
* Generates a client assertion JWT for token endpoint authentication.
|
|
53
|
+
*
|
|
54
|
+
* The client assertion proves the client's identity using JWT-based
|
|
55
|
+
* authentication (private_key_jwt method).
|
|
56
|
+
*
|
|
57
|
+
* @param audience - Token endpoint URL (or issuer)
|
|
58
|
+
* @returns Signed JWT string
|
|
59
|
+
*/
|
|
60
|
+
generateClientAssertionJwt(audience: string): Promise<string>;
|
|
61
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { SignJWT } from 'jose';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
/**
|
|
4
|
+
* Helper class for JWT operations.
|
|
5
|
+
*
|
|
6
|
+
* Handles creation and signing of JWTs for:
|
|
7
|
+
* - Request objects (PAR)
|
|
8
|
+
* - Client assertions (token endpoint authentication)
|
|
9
|
+
*
|
|
10
|
+
* Uses the PS256 algorithm (RSA-PSS with SHA-256) as required by FAPI.
|
|
11
|
+
*/
|
|
12
|
+
export class JwtHelper {
|
|
13
|
+
/**
|
|
14
|
+
* Creates a new JwtHelper instance.
|
|
15
|
+
*
|
|
16
|
+
* @param signingKey - Private key for signing JWTs
|
|
17
|
+
* @param signingKid - Key ID to include in JWT header
|
|
18
|
+
* @param clientId - OAuth client ID
|
|
19
|
+
*/
|
|
20
|
+
constructor(signingKey, signingKid, clientId) {
|
|
21
|
+
this.signingKey = signingKey;
|
|
22
|
+
this.signingKid = signingKid;
|
|
23
|
+
this.clientId = clientId;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Generates a signed request JWT for PAR.
|
|
27
|
+
*
|
|
28
|
+
* The request object contains all authorization request parameters
|
|
29
|
+
* and is signed to prevent tampering.
|
|
30
|
+
*
|
|
31
|
+
* @param params - Request parameters
|
|
32
|
+
* @returns Signed JWT string
|
|
33
|
+
*/
|
|
34
|
+
async generateRequestJwt(params) {
|
|
35
|
+
const now = Math.floor(Date.now() / 1000);
|
|
36
|
+
return new SignJWT({
|
|
37
|
+
// OAuth/OIDC parameters
|
|
38
|
+
iss: this.clientId,
|
|
39
|
+
aud: params.audience,
|
|
40
|
+
client_id: this.clientId,
|
|
41
|
+
scope: params.scope,
|
|
42
|
+
response_type: params.responseType,
|
|
43
|
+
redirect_uri: params.redirectUri,
|
|
44
|
+
// PKCE
|
|
45
|
+
code_challenge: params.codeChallenge,
|
|
46
|
+
code_challenge_method: params.codeChallengeMethod,
|
|
47
|
+
// OIDC parameters
|
|
48
|
+
state: params.state,
|
|
49
|
+
nonce: params.nonce,
|
|
50
|
+
claims: params.claims,
|
|
51
|
+
prompt: params.prompt,
|
|
52
|
+
// ConnectID extension
|
|
53
|
+
purpose: params.purpose,
|
|
54
|
+
// JWT metadata
|
|
55
|
+
jti: randomUUID(),
|
|
56
|
+
})
|
|
57
|
+
.setProtectedHeader({
|
|
58
|
+
alg: 'PS256',
|
|
59
|
+
kid: this.signingKid,
|
|
60
|
+
typ: 'oauth-authz-req+jwt',
|
|
61
|
+
})
|
|
62
|
+
.setIssuedAt(now)
|
|
63
|
+
.setNotBefore(now) // nbf = iat (required by FAPI)
|
|
64
|
+
.setExpirationTime(now + 300) // 5 minutes
|
|
65
|
+
.sign(this.signingKey);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Generates a client assertion JWT for token endpoint authentication.
|
|
69
|
+
*
|
|
70
|
+
* The client assertion proves the client's identity using JWT-based
|
|
71
|
+
* authentication (private_key_jwt method).
|
|
72
|
+
*
|
|
73
|
+
* @param audience - Token endpoint URL (or issuer)
|
|
74
|
+
* @returns Signed JWT string
|
|
75
|
+
*/
|
|
76
|
+
async generateClientAssertionJwt(audience) {
|
|
77
|
+
const now = Math.floor(Date.now() / 1000);
|
|
78
|
+
return new SignJWT({
|
|
79
|
+
iss: this.clientId,
|
|
80
|
+
sub: this.clientId,
|
|
81
|
+
aud: audience,
|
|
82
|
+
jti: randomUUID(),
|
|
83
|
+
})
|
|
84
|
+
.setProtectedHeader({
|
|
85
|
+
alg: 'PS256',
|
|
86
|
+
kid: this.signingKid,
|
|
87
|
+
})
|
|
88
|
+
.setIssuedAt(now)
|
|
89
|
+
.setExpirationTime(now + 300) // 5 minutes
|
|
90
|
+
.sign(this.signingKey);
|
|
91
|
+
}
|
|
92
|
+
}
|