@connectid-tools/rp-nodejs-sdk 4.2.1 → 5.0.1

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.
Files changed (56) hide show
  1. package/README.md +64 -71
  2. package/config.js +2 -31
  3. package/conformance/api/conformance-api.d.ts +38 -0
  4. package/conformance/api/conformance-api.js +53 -0
  5. package/conformance/conformance-config.d.ts +2 -0
  6. package/conformance/conformance-config.js +34 -0
  7. package/crypto/crypto-loader.d.ts +32 -0
  8. package/crypto/crypto-loader.js +49 -0
  9. package/crypto/jwt-helper.d.ts +61 -0
  10. package/crypto/jwt-helper.js +92 -0
  11. package/crypto/pkce-helper.d.ts +43 -0
  12. package/crypto/pkce-helper.js +75 -0
  13. package/endpoints/participants-endpoint.d.ts +55 -0
  14. package/endpoints/participants-endpoint.js +137 -0
  15. package/endpoints/pushed-authorisation-request-endpoint.d.ts +87 -0
  16. package/endpoints/pushed-authorisation-request-endpoint.js +192 -0
  17. package/endpoints/retrieve-token-endpoint.d.ts +66 -0
  18. package/endpoints/retrieve-token-endpoint.js +159 -0
  19. package/endpoints/userinfo-endpoint.d.ts +24 -0
  20. package/endpoints/userinfo-endpoint.js +50 -0
  21. package/fapi/fapi-utils.d.ts +6 -0
  22. package/fapi/fapi-utils.js +9 -0
  23. package/http/http-client-extensions.d.ts +60 -0
  24. package/http/http-client-extensions.js +106 -0
  25. package/http/http-client-factory.d.ts +27 -0
  26. package/http/http-client-factory.js +45 -0
  27. package/model/callback-params.d.ts +31 -0
  28. package/model/callback-params.js +1 -0
  29. package/model/claims.d.ts +100 -0
  30. package/model/claims.js +1 -0
  31. package/model/consolidated-token-set.d.ts +74 -0
  32. package/model/consolidated-token-set.js +100 -0
  33. package/model/discovery-service.d.ts +46 -0
  34. package/model/discovery-service.js +112 -0
  35. package/model/issuer-metadata.d.ts +165 -0
  36. package/model/issuer-metadata.js +1 -0
  37. package/model/jwks.d.ts +12 -0
  38. package/model/jwks.js +1 -0
  39. package/model/token-response.d.ts +31 -0
  40. package/model/token-response.js +1 -0
  41. package/model/token-set.d.ts +73 -0
  42. package/model/token-set.js +179 -0
  43. package/package.json +4 -5
  44. package/relying-party-client-sdk.d.ts +55 -24
  45. package/relying-party-client-sdk.js +90 -304
  46. package/test-data/large-participants-test-data.d.ts +865 -0
  47. package/test-data/large-participants-test-data.js +18907 -0
  48. package/test-data/participants-test-data.d.ts +149 -0
  49. package/test-data/participants-test-data.js +458 -0
  50. package/test-data/sandbox-participants-test-data.d.ts +865 -0
  51. package/test-data/sandbox-participants-test-data.js +3794 -0
  52. package/types.d.ts +61 -32
  53. package/utils/request-utils.d.ts +1 -1
  54. package/utils/request-utils.js +5 -5
  55. package/utils/user-agent.d.ts +1 -1
  56. package/utils/user-agent.js +1 -1
@@ -0,0 +1,73 @@
1
+ import { IdTokenClaims } from './claims.js';
2
+ import { TokenResponse } from './token-response.js';
3
+ import { JWKSet } from './jwks.js';
4
+ /**
5
+ * Token Set
6
+ *
7
+ * Represents an OAuth 2.0 / OIDC token response with validation capabilities.
8
+ * Handles ID token validation and claims extraction.
9
+ */
10
+ export declare class TokenSet {
11
+ readonly access_token?: string;
12
+ readonly token_type?: string;
13
+ readonly expires_in?: number;
14
+ readonly refresh_token?: string;
15
+ readonly scope?: string;
16
+ readonly id_token?: string;
17
+ private idTokenClaims?;
18
+ private jwtPayload?;
19
+ private tokenIssuedAt;
20
+ /**
21
+ * Creates a new TokenSet from a token response.
22
+ *
23
+ * @param tokenResponse - Raw token response from the token endpoint
24
+ */
25
+ constructor(tokenResponse: TokenResponse);
26
+ /**
27
+ * Validates the ID token.
28
+ *
29
+ * Performs the following validations:
30
+ * - Algorithm validation against allowed algorithms
31
+ * - Signature verification using JWKS
32
+ * - Issuer validation
33
+ * - Audience validation
34
+ * - Nonce validation
35
+ * - Timestamp validation (iat, exp)
36
+ *
37
+ * @param jwks - JSON Web Key Set for signature verification
38
+ * @param expectedIssuer - Expected issuer claim value
39
+ * @param expectedAudience - Expected audience claim value
40
+ * @param expectedNonce - Expected nonce value
41
+ * @param allowedAlgorithms - Optional list of allowed signing algorithms from discovery document
42
+ * @throws Error if validation fails
43
+ */
44
+ validate(jwks: JWKSet, expectedIssuer: string, expectedAudience: string, expectedNonce: string, allowedAlgorithms?: string[]): Promise<void>;
45
+ /**
46
+ * Returns the parsed ID token claims.
47
+ *
48
+ * Must call validate() first.
49
+ *
50
+ * @returns Parsed and validated ID token claims
51
+ * @throws Error if token has not been validated
52
+ */
53
+ claims(): IdTokenClaims;
54
+ /**
55
+ * Checks if the access token has expired.
56
+ *
57
+ * @returns true if the token is expired, false otherwise
58
+ */
59
+ expired(): boolean;
60
+ /**
61
+ * Selects the appropriate key from JWKS for verification.
62
+ *
63
+ * Matches based on:
64
+ * - kid (key ID)
65
+ * - alg (algorithm)
66
+ * - use (key usage - should be 'sig')
67
+ *
68
+ * @param jwks - JSON Web Key Set
69
+ * @returns Imported crypto key for verification
70
+ * @throws Error if no matching key is found
71
+ */
72
+ private selectKey;
73
+ }
@@ -0,0 +1,179 @@
1
+ import { jwtVerify, decodeProtectedHeader, importJWK } from 'jose';
2
+ /**
3
+ * Token Set
4
+ *
5
+ * Represents an OAuth 2.0 / OIDC token response with validation capabilities.
6
+ * Handles ID token validation and claims extraction.
7
+ */
8
+ export class TokenSet {
9
+ /**
10
+ * Creates a new TokenSet from a token response.
11
+ *
12
+ * @param tokenResponse - Raw token response from the token endpoint
13
+ */
14
+ constructor(tokenResponse) {
15
+ this.access_token = tokenResponse.access_token;
16
+ this.token_type = tokenResponse.token_type;
17
+ this.expires_in = tokenResponse.expires_in;
18
+ this.refresh_token = tokenResponse.refresh_token;
19
+ this.scope = tokenResponse.scope;
20
+ this.id_token = tokenResponse.id_token;
21
+ this.tokenIssuedAt = Math.floor(Date.now() / 1000);
22
+ }
23
+ /**
24
+ * Validates the ID token.
25
+ *
26
+ * Performs the following validations:
27
+ * - Algorithm validation against allowed algorithms
28
+ * - Signature verification using JWKS
29
+ * - Issuer validation
30
+ * - Audience validation
31
+ * - Nonce validation
32
+ * - Timestamp validation (iat, exp)
33
+ *
34
+ * @param jwks - JSON Web Key Set for signature verification
35
+ * @param expectedIssuer - Expected issuer claim value
36
+ * @param expectedAudience - Expected audience claim value
37
+ * @param expectedNonce - Expected nonce value
38
+ * @param allowedAlgorithms - Optional list of allowed signing algorithms from discovery document
39
+ * @throws Error if validation fails
40
+ */
41
+ async validate(jwks, expectedIssuer, expectedAudience, expectedNonce, allowedAlgorithms) {
42
+ if (!this.id_token) {
43
+ throw new Error('No id_token to validate');
44
+ }
45
+ try {
46
+ // Decode header to check algorithm before verification
47
+ const header = decodeProtectedHeader(this.id_token);
48
+ // Validate algorithm if allowed algorithms are provided
49
+ if (allowedAlgorithms && allowedAlgorithms.length > 0) {
50
+ if (!header.alg) {
51
+ throw new Error('ID token missing alg in header');
52
+ }
53
+ const isAllowed = allowedAlgorithms.some((alg) => alg.toLowerCase() === header.alg.toLowerCase());
54
+ if (!isAllowed) {
55
+ throw new Error(`ID token algorithm '${header.alg}' is not one of the allowed algorithms: ${allowedAlgorithms.join(', ')}`);
56
+ }
57
+ }
58
+ // Select the appropriate key from JWKS
59
+ const key = await this.selectKey(jwks);
60
+ // Verify signature and standard claims
61
+ const { payload } = await jwtVerify(this.id_token, key, {
62
+ issuer: expectedIssuer,
63
+ audience: expectedAudience,
64
+ algorithms: ['PS256', 'RS256'], // Accept both algorithms
65
+ });
66
+ // Validate nonce
67
+ if (payload.nonce !== expectedNonce) {
68
+ throw new Error(`Nonce mismatch: expected ${expectedNonce}, got ${payload.nonce}`);
69
+ }
70
+ // Validate audience claim per OpenID Connect Core 3.1.3.7
71
+ // If aud is an array, all audiences must be trusted (only our client ID is trusted)
72
+ // and azp claim should be present
73
+ if (Array.isArray(payload.aud)) {
74
+ // Check if all audiences are trusted (only client ID is trusted)
75
+ const untrustedAudiences = payload.aud.filter((aud) => aud !== expectedAudience);
76
+ if (untrustedAudiences.length > 0) {
77
+ throw new Error(`ID token contains untrusted audiences: ${untrustedAudiences.join(', ')}`);
78
+ }
79
+ // Per OIDC spec clause 4: if multiple audiences, azp should be present
80
+ if (payload.aud.length > 1 && !payload.azp) {
81
+ throw new Error('ID token contains multiple audiences but azp claim is missing');
82
+ }
83
+ }
84
+ // Validate required claims are present
85
+ const now = Math.floor(Date.now() / 1000);
86
+ if (!payload.iat) {
87
+ throw new Error('ID token missing iat claim');
88
+ }
89
+ if (!payload.exp) {
90
+ throw new Error('ID token missing exp claim');
91
+ }
92
+ // Validate iat (must not be more than 10 minutes old per Java SDK)
93
+ if (now - payload.iat > 600) {
94
+ throw new Error(`ID token iat is too old: issued at ${payload.iat}, current time ${now}`);
95
+ }
96
+ // Validate exp (must not be more than 5 minutes in the past - clock skew tolerance)
97
+ if (payload.exp < now - 300) {
98
+ throw new Error(`ID token expired more than 5 minutes ago: exp ${payload.exp}, current time ${now}`);
99
+ }
100
+ // Store validated claims
101
+ this.jwtPayload = payload;
102
+ this.idTokenClaims = payload;
103
+ }
104
+ catch (error) {
105
+ throw new Error(`ID token validation failed: ${error instanceof Error ? error.message : String(error)}`);
106
+ }
107
+ }
108
+ /**
109
+ * Returns the parsed ID token claims.
110
+ *
111
+ * Must call validate() first.
112
+ *
113
+ * @returns Parsed and validated ID token claims
114
+ * @throws Error if token has not been validated
115
+ */
116
+ claims() {
117
+ if (!this.idTokenClaims) {
118
+ throw new Error('ID token has not been validated. Call validate() first.');
119
+ }
120
+ return this.idTokenClaims;
121
+ }
122
+ /**
123
+ * Checks if the access token has expired.
124
+ *
125
+ * @returns true if the token is expired, false otherwise
126
+ */
127
+ expired() {
128
+ if (!this.expires_in) {
129
+ // If no expires_in, assume token doesn't expire
130
+ return false;
131
+ }
132
+ const now = Math.floor(Date.now() / 1000);
133
+ const expiresAt = this.tokenIssuedAt + this.expires_in;
134
+ return now >= expiresAt;
135
+ }
136
+ /**
137
+ * Selects the appropriate key from JWKS for verification.
138
+ *
139
+ * Matches based on:
140
+ * - kid (key ID)
141
+ * - alg (algorithm)
142
+ * - use (key usage - should be 'sig')
143
+ *
144
+ * @param jwks - JSON Web Key Set
145
+ * @returns Imported crypto key for verification
146
+ * @throws Error if no matching key is found
147
+ */
148
+ async selectKey(jwks) {
149
+ if (!this.id_token) {
150
+ throw new Error('No id_token present');
151
+ }
152
+ // Decode header to get kid and alg
153
+ const header = decodeProtectedHeader(this.id_token);
154
+ if (!header.kid) {
155
+ throw new Error('ID token missing kid in header');
156
+ }
157
+ // Find matching key
158
+ const matchingKey = jwks.keys.find((key) => {
159
+ // Match by kid (required)
160
+ if (key.kid !== header.kid) {
161
+ return false;
162
+ }
163
+ // Match by alg if present in key
164
+ if (key.alg && key.alg !== header.alg) {
165
+ return false;
166
+ }
167
+ // Match by use if present (should be 'sig' for signing)
168
+ if (key.use && key.use !== 'sig') {
169
+ return false;
170
+ }
171
+ return true;
172
+ });
173
+ if (!matchingKey) {
174
+ throw new Error(`No matching key found in JWKS for kid: ${header.kid}, alg: ${header.alg}`);
175
+ }
176
+ // Import the JWK for verification
177
+ return importJWK(matchingKey, header.alg);
178
+ }
179
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@connectid-tools/rp-nodejs-sdk",
3
- "version": "4.2.1",
3
+ "version": "5.0.1",
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,17 +31,16 @@
31
31
  "homepage": "https://github.com/connectid-tools/rp-nodejs-sample-app",
32
32
  "dependencies": {
33
33
  "https": "^1.0.0",
34
- "node-jose": "^2.2.0",
35
- "openid-client": "^5.7.1",
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": "^2.2.0",
43
+ "replace-in-files-cli": "^4.0.0",
45
44
  "tsx": "^4.20.3",
46
45
  "typescript": "^5.9.2"
47
46
  },
@@ -1,37 +1,68 @@
1
- import { CallbackParamsType } from 'openid-client';
2
- import { AuthorisationServer, ConsolidatedTokenSet, Participant, RelyingPartyClientSdkConfig } from './types';
1
+ import { CallbackParams, ConsolidatedTokenSet, Participant, RelyingPartyClientSdkConfig } from './types.js';
3
2
  export default class RelyingPartyClientSdk {
4
3
  private readonly logger;
5
4
  private config;
6
- private readonly transportKey;
7
- private readonly transportPem;
8
- private readonly signingKey;
9
- private readonly caPem;
10
5
  private readonly purpose;
11
- private participantFilters;
12
- private readonly default_cache_ttl;
13
- private cachedParticipantsExpiry;
14
- private cachedParticipants;
6
+ private readonly participantsEndpoint;
7
+ private readonly pushedAuthorisationRequestEndpoint;
8
+ private readonly retrieveTokenEndpoint;
9
+ private readonly userInfoEndpoint;
10
+ private readonly httpClient;
11
+ private readonly jwtHelper;
15
12
  constructor(config: RelyingPartyClientSdkConfig);
13
+ /**
14
+ * Get the list of participating identity providers within the scheme.
15
+ *
16
+ * Applies filtering based on SDK configuration.
17
+ *
18
+ * @returns List of participants
19
+ */
16
20
  getParticipants(): Promise<Participant[]>;
21
+ /**
22
+ * Get the list of fallback provider participants.
23
+ *
24
+ * @returns List of fallback provider participants
25
+ */
17
26
  getFallbackProviderParticipants(): Promise<Participant[]>;
18
- getCurrentDate(): Date;
19
- fetchParticipants(participantsUri: string): Promise<Participant[]>;
20
- private retrieveFullParticipantsList;
27
+ /**
28
+ * Sends a Pushed Authorization Request (PAR).
29
+ *
30
+ * @param authServerId - Authorization server ID
31
+ * @param essentialClaims - Claims that must be provided
32
+ * @param voluntaryClaims - Claims that are optional
33
+ * @param purpose - Purpose string for data sharing
34
+ * @returns Object containing authorization URL and PKCE parameters
35
+ */
21
36
  sendPushedAuthorisationRequest(authServerId: string, essentialClaims: string[], voluntaryClaims?: string[], purpose?: string): Promise<{
22
37
  authUrl: string;
23
- code_verifier: string;
38
+ codeVerifier: string;
24
39
  state: string;
25
40
  nonce: string;
26
- xFapiInteractionId: `${string}-${string}-${string}-${string}-${string}`;
41
+ xFapiInteractionId: string;
27
42
  }>;
28
- retrieveTokens(authorisationServerId: string, requestParams: CallbackParamsType, codeVerifier: string, state: string, nonce: string): Promise<ConsolidatedTokenSet>;
29
- private buildConsolidatedTokenSet;
30
- getAuthServerDetails(authServerId: string): Promise<AuthorisationServer>;
31
- private generateClaimsRequest;
32
- getUserInfo(authorisationServerId: string, accessToken: string): Promise<import("openid-client").UserinfoResponse<import("openid-client").UnknownObject, import("openid-client").UnknownObject>>;
33
- private getKeyset;
34
- private setupClient;
35
- private generateRequest;
36
- private generateXFapiInteractionId;
43
+ /**
44
+ * Retrieves tokens using an authorisation code.
45
+ *
46
+ * @param authorisationServerId - Authorisation server ID
47
+ * @param requestParams - OAuth callback parameters
48
+ * @param codeVerifier - PKCE code verifier from PAR
49
+ * @param state - State parameter from PAR
50
+ * @param nonce - Nonce parameter from PAR
51
+ * @returns Consolidated token set with validated claims
52
+ */
53
+ retrieveTokens(authorisationServerId: string, requestParams: CallbackParams, codeVerifier: string, state: string, nonce: string): Promise<ConsolidatedTokenSet>;
54
+ /**
55
+ * Retrieves user information from the UserInfo endpoint.
56
+ *
57
+ * @param authorisationServerId - Authorization server ID
58
+ * @param accessToken - Access token
59
+ * @returns UserInfo claims
60
+ */
61
+ getUserInfo(authorisationServerId: string, accessToken: string): Promise<Record<string, unknown>>;
62
+ /**
63
+ * Gets the current date (for testing purposes).
64
+ *
65
+ * @returns Current date
66
+ */
67
+ getCurrentDate(): Date;
37
68
  }