@monocloud/auth-core 0.1.7 → 0.1.9

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/dist/index.mjs CHANGED
@@ -1,4 +1,5 @@
1
- import { decodeBase64Url, encodeBase64Url, findToken, getPublicSigKeyFromIssuerJwks, now, parseSpaceSeparated, profileSync, randomBytes, stringToArrayBuffer } from "./utils/internal.mjs";
1
+ import { arrayBufferToBase64, decodeBase64Url, encodeBase64Url, findToken, getPublicSigKeyFromIssuerJwks, now, parseSpaceSeparated, profileSync, randomBytes, stringToArrayBuffer } from "./utils/internal.mjs";
2
+ import { isUserInGroup } from "./utils/index.mjs";
2
3
 
3
4
  //#region src/errors/monocloud-auth-base-error.ts
4
5
  /**
@@ -56,6 +57,129 @@ var MonoCloudTokenError = class extends MonoCloudAuthBaseError {};
56
57
  */
57
58
  var MonoCloudValidationError = class extends MonoCloudAuthBaseError {};
58
59
 
60
+ //#endregion
61
+ //#region src/helper.ts
62
+ const JWT_ASSERTION_CLOCK_SKEW = 5;
63
+ function assertMetadataProperty(metadata, property) {
64
+ if (metadata[property] === void 0 || metadata[property] === null) throw new MonoCloudValidationError(`${property} endpoint is required but not available in the issuer metadata`);
65
+ }
66
+ const innerFetch = async (input, reqInit = {}, customFetch) => {
67
+ try {
68
+ return await (customFetch ?? fetch)(input, reqInit);
69
+ } catch (e) {
70
+ /* v8 ignore next -- @preserve */
71
+ throw new MonoCloudHttpError(e.message ?? "Unexpected Network Error");
72
+ }
73
+ };
74
+ const deserializeJson = async (res) => {
75
+ try {
76
+ return await res.json();
77
+ } catch (e) {
78
+ throw new MonoCloudHttpError(
79
+ /* v8 ignore next -- @preserve */
80
+ `Failed to parse response body as JSON ${e.message ? `: ${e.message}` : ""}`
81
+ );
82
+ }
83
+ };
84
+
85
+ //#endregion
86
+ //#region src/monocloud-oidc-client-base.ts
87
+ /**
88
+ * @category Classes
89
+ */
90
+ var MonoCloudOidcClientBase = class {
91
+ /**
92
+ * Creates a new instance of MonoCloudOidcClientBase.
93
+ *
94
+ * @param tenantDomain - The tenant domain URL.
95
+ * @param metadataCacheDuration - Duration (in seconds) to cache OpenID Connect discovery metadata. Defaults to 300 (5 minutes).
96
+ * @param jwksCacheDuration - Duration (in seconds) to cache the JSON Web Key Set (JWKS). Defaults to 300 (5 minutes).
97
+ * @param fetcher - Custom `fetch` implementation used for making HTTP requests. Falls back to the global `fetch` if not provided.
98
+ */
99
+ constructor(tenantDomain, metadataCacheDuration, jwksCacheDuration, fetcher) {
100
+ this.jwksCacheExpiry = 0;
101
+ this.jwksCacheDuration = 300;
102
+ this.metadataCacheExpiry = 0;
103
+ this.metadataCacheDuration = 300;
104
+ tenantDomain ??= "";
105
+ /* v8 ignore next -- @preserve */
106
+ this.tenantDomain = `${!tenantDomain.startsWith("https://") ? "https://" : ""}${tenantDomain.endsWith("/") ? tenantDomain.slice(0, -1) : tenantDomain}`;
107
+ if (metadataCacheDuration !== void 0) this.metadataCacheDuration = metadataCacheDuration;
108
+ if (jwksCacheDuration !== void 0) this.jwksCacheDuration = jwksCacheDuration;
109
+ this.fetcher = fetcher;
110
+ }
111
+ /**
112
+ * Fetches the authorization server metadata from the .well-known endpoint.
113
+ * The metadata is cached for 5 minutes by default.
114
+ *
115
+ * @param forceRefresh - If `true`, bypasses the cache and fetches fresh metadata from the server.
116
+ *
117
+ * @returns The issuer metadata for the tenant, retrieved from the OpenID Connect discovery endpoint.
118
+ *
119
+ * @throws {@link MonoCloudHttpError} - Thrown if there is a network error during the request or
120
+ * unexpected status code during the request or a serialization error while processing the response.
121
+ *
122
+ */
123
+ async getMetadata(forceRefresh = false) {
124
+ if (!forceRefresh && this.metadata && this.metadataCacheExpiry > now()) return this.metadata;
125
+ this.metadata = void 0;
126
+ const response = await innerFetch(`${this.tenantDomain}/.well-known/openid-configuration`, void 0, this.fetcher);
127
+ if (response.status !== 200) throw new MonoCloudHttpError(`Error while fetching metadata. Unexpected status code: ${response.status}`);
128
+ const metadata = await deserializeJson(response);
129
+ this.metadata = metadata;
130
+ this.metadataCacheExpiry = now() + this.metadataCacheDuration;
131
+ return metadata;
132
+ }
133
+ /**
134
+ * Fetches the JSON Web Keys used to sign the ID token.
135
+ * The JWKS is cached for 5 minutes by default.
136
+ *
137
+ * @param forceRefresh - If `true`, bypasses the cache and fetches fresh set of JWKS from the server.
138
+ *
139
+ * @returns The JSON Web Key Set containing the public keys for token verification.
140
+ *
141
+ * @throws {@link MonoCloudHttpError} - Thrown if there is a network error during the request or
142
+ * unexpected status code during the request or a serialization error while processing the response.
143
+ *
144
+ */
145
+ async getJwks(forceRefresh = false) {
146
+ if (!forceRefresh && this.jwks && this.jwksCacheExpiry > now()) return this.jwks;
147
+ this.jwks = void 0;
148
+ const metadata = await this.getMetadata();
149
+ assertMetadataProperty(metadata, "jwks_uri");
150
+ const response = await innerFetch(metadata.jwks_uri, void 0, this.fetcher);
151
+ if (response.status !== 200) throw new MonoCloudHttpError(`Error while fetching JWKS. Unexpected status code: ${response.status}`);
152
+ const jwks = await deserializeJson(response);
153
+ this.jwks = jwks;
154
+ this.jwksCacheExpiry = now() + this.jwksCacheDuration;
155
+ return jwks;
156
+ }
157
+ /**
158
+ * Decodes the payload of a JSON Web Token (JWT) and returns it as an object.
159
+ *
160
+ * >Note: THIS METHOD DOES NOT VERIFY JWT TOKENS.
161
+ *
162
+ * @param jwt - JWT to decode.
163
+ *
164
+ * @returns Decoded payload.
165
+ *
166
+ * @throws {@link MonoCloudTokenError} - If decoding fails
167
+ *
168
+ */
169
+ static decodeJwt(jwt) {
170
+ try {
171
+ const [, payload] = jwt.split(".");
172
+ if (!payload?.trim()) throw new MonoCloudTokenError("JWT does not contain payload");
173
+ const decoded = decodeBase64Url(payload);
174
+ if (!decoded.startsWith("{")) throw new MonoCloudTokenError("Payload is not an object");
175
+ return JSON.parse(decoded);
176
+ } catch (e) {
177
+ if (e instanceof MonoCloudAuthBaseError) throw e;
178
+ throw new MonoCloudTokenError("Could not parse payload. Malformed payload");
179
+ }
180
+ }
181
+ };
182
+
59
183
  //#endregion
60
184
  //#region src/client-auth.ts
61
185
  const algToSubtle = (alg) => {
@@ -214,13 +338,15 @@ const clientAuth = async (clientId, clientSecret, method, issuer, headers, body,
214
338
  case method === "private_key_jwt" && typeof clientSecret === "object" && clientSecret.kty !== "oct" && !!issuer && !!body:
215
339
  await jwtAssertionGenerator(issuer, clientId, clientSecret, body, jwtAssertionSkew ?? 0);
216
340
  break;
341
+ case (method === "tls_client_auth" || method === "self_signed_tls_client_auth") && !!body:
342
+ body.set("client_id", clientId);
343
+ break;
217
344
  default: throw new Error("Invalid Client Authentication Method");
218
345
  }
219
346
  };
220
347
 
221
348
  //#endregion
222
349
  //#region src/monocloud-oidc-client.ts
223
- const JWT_ASSERTION_CLOCK_SKEW = 5;
224
350
  const FILTER_ID_TOKEN_CLAIMS = [
225
351
  "iss",
226
352
  "exp",
@@ -233,45 +359,23 @@ const FILTER_ID_TOKEN_CLAIMS = [
233
359
  "at_hash",
234
360
  "s_hash"
235
361
  ];
236
- function assertMetadataProperty(metadata, property) {
237
- if (metadata[property] === void 0 || metadata[property] === null) throw new MonoCloudValidationError(`${property} endpoint is required but not available in the issuer metadata`);
238
- }
239
- const innerFetch = async (input, reqInit = {}) => {
240
- try {
241
- return await fetch(input, reqInit);
242
- } catch (e) {
243
- /* v8 ignore next -- @preserve */
244
- throw new MonoCloudHttpError(e.message ?? "Unexpected Network Error");
245
- }
246
- };
247
- const deserializeJson = async (res) => {
248
- try {
249
- return await res.json();
250
- } catch (e) {
251
- throw new MonoCloudHttpError(
252
- /* v8 ignore next -- @preserve */
253
- `Failed to parse response body as JSON ${e.message ? `: ${e.message}` : ""}`
254
- );
255
- }
256
- };
257
362
  /**
258
363
  * @category Classes
259
364
  */
260
- var MonoCloudOidcClient = class MonoCloudOidcClient {
365
+ var MonoCloudOidcClient = class MonoCloudOidcClient extends MonoCloudOidcClientBase {
366
+ /**
367
+ * Creates a new instance of MonoCloudOidcClient.
368
+ *
369
+ * @param tenantDomain - The tenant domain URL.
370
+ * @param clientId - Client id of the application registered in MonoCloud.
371
+ * @param options - Additional client configuration options.
372
+ */
261
373
  constructor(tenantDomain, clientId, options) {
262
- this.jwksCacheExpiry = 0;
263
- this.jwksCacheDuration = 300;
264
- this.metadataCacheExpiry = 0;
265
- this.metadataCacheDuration = 300;
266
- tenantDomain ??= "";
267
- /* v8 ignore next -- @preserve */
268
- this.tenantDomain = `${!tenantDomain.startsWith("https://") ? "https://" : ""}${tenantDomain.endsWith("/") ? tenantDomain.slice(0, -1) : tenantDomain}`;
374
+ super(tenantDomain, options?.metadataCacheDuration, options?.jwksCacheDuration, options?.fetcher);
269
375
  this.clientId = clientId;
270
376
  this.clientSecret = options?.clientSecret;
271
377
  this.authMethod = options?.clientAuthMethod ?? "client_secret_basic";
272
378
  this.idTokenSigningAlgorithm = options?.idTokenSigningAlgorithm ?? "RS256";
273
- if (options?.jwksCacheDuration) this.jwksCacheDuration = options.jwksCacheDuration;
274
- if (options?.metadataCacheDuration) this.metadataCacheDuration = options.metadataCacheDuration;
275
379
  }
276
380
  /**
277
381
  * Generates an authorization URL with specified parameters.
@@ -317,52 +421,6 @@ var MonoCloudOidcClient = class MonoCloudOidcClient {
317
421
  return `${metadata.authorization_endpoint}?${queryParams.toString()}`;
318
422
  }
319
423
  /**
320
- * Fetches the authorization server metadata from the .well-known endpoint.
321
- * The metadata is cached for 1 minute.
322
- *
323
- * @param forceRefresh - If `true`, bypasses the cache and fetches fresh metadata from the server.
324
- *
325
- * @returns The issuer metadata for the tenant, retrieved from the OpenID Connect discovery endpoint.
326
- *
327
- * @throws {@link MonoCloudHttpError} - Thrown if there is a network error during the request or
328
- * unexpected status code during the request or a serialization error while processing the response.
329
- *
330
- */
331
- async getMetadata(forceRefresh = false) {
332
- if (!forceRefresh && this.metadata && this.metadataCacheExpiry > now()) return this.metadata;
333
- this.metadata = void 0;
334
- const response = await innerFetch(`${this.tenantDomain}/.well-known/openid-configuration`);
335
- if (response.status !== 200) throw new MonoCloudHttpError(`Error while fetching metadata. Unexpected status code: ${response.status}`);
336
- const metadata = await deserializeJson(response);
337
- this.metadata = metadata;
338
- this.metadataCacheExpiry = now() + this.metadataCacheDuration;
339
- return metadata;
340
- }
341
- /**
342
- * Fetches the JSON Web Keys used to sign the ID token.
343
- * The JWKS is cached for 1 minute.
344
- *
345
- * @param forceRefresh - If `true`, bypasses the cache and fetches fresh set of JWKS from the server.
346
- *
347
- * @returns The JSON Web Key Set containing the public keys for token verification.
348
- *
349
- * @throws {@link MonoCloudHttpError} - Thrown if there is a network error during the request or
350
- * unexpected status code during the request or a serialization error while processing the response.
351
- *
352
- */
353
- async getJwks(forceRefresh = false) {
354
- if (!forceRefresh && this.jwks && this.jwksCacheExpiry > now()) return this.jwks;
355
- this.jwks = void 0;
356
- const metadata = await this.getMetadata();
357
- assertMetadataProperty(metadata, "jwks_uri");
358
- const response = await innerFetch(metadata.jwks_uri);
359
- if (response.status !== 200) throw new MonoCloudHttpError(`Error while fetching JWKS. Unexpected status code: ${response.status}`);
360
- const jwks = await deserializeJson(response);
361
- this.jwks = jwks;
362
- this.jwksCacheExpiry = now() + this.jwksCacheDuration;
363
- return jwks;
364
- }
365
- /**
366
424
  * Performs a pushed authorization request.
367
425
  *
368
426
  * @param params - Authorization Parameters.
@@ -411,7 +469,7 @@ var MonoCloudOidcClient = class MonoCloudOidcClient {
411
469
  body: body.toString(),
412
470
  method: "POST",
413
471
  headers
414
- });
472
+ }, this.fetcher);
415
473
  if (response.status === 400) {
416
474
  const standardBodyError = await deserializeJson(response);
417
475
  throw new MonoCloudOPError(standardBodyError.error ?? "par_request_failed", standardBodyError.error_description ?? "Pushed Authorization Request Failed");
@@ -443,7 +501,7 @@ var MonoCloudOidcClient = class MonoCloudOidcClient {
443
501
  const response = await innerFetch(metadata.userinfo_endpoint, {
444
502
  method: "GET",
445
503
  headers: { authorization: `Bearer ${accessToken}` }
446
- });
504
+ }, this.fetcher);
447
505
  if (response.status === 401) {
448
506
  const authenticateError = response.headers.get("WWW-Authenticate");
449
507
  if (authenticateError) {
@@ -517,7 +575,7 @@ var MonoCloudOidcClient = class MonoCloudOidcClient {
517
575
  method: "POST",
518
576
  body: body.toString(),
519
577
  headers
520
- });
578
+ }, this.fetcher);
521
579
  if (response.status === 400) {
522
580
  const standardBodyError = await deserializeJson(response);
523
581
  throw new MonoCloudOPError(standardBodyError.error ?? "code_grant_failed", standardBodyError.error_description ?? "Authorization code grant failed");
@@ -559,7 +617,7 @@ var MonoCloudOidcClient = class MonoCloudOidcClient {
559
617
  method: "POST",
560
618
  body: body.toString(),
561
619
  headers
562
- });
620
+ }, this.fetcher);
563
621
  if (response.status === 400) {
564
622
  const standardBodyError = await deserializeJson(response);
565
623
  throw new MonoCloudOPError(standardBodyError.error ?? "refresh_grant_failed", standardBodyError.error_description ?? "Refresh token grant failed");
@@ -743,7 +801,7 @@ var MonoCloudOidcClient = class MonoCloudOidcClient {
743
801
  method: "POST",
744
802
  body: body.toString(),
745
803
  headers
746
- });
804
+ }, this.fetcher);
747
805
  if (response.status === 400) {
748
806
  const standardBodyError = await deserializeJson(response);
749
807
  throw new MonoCloudOPError(standardBodyError.error ?? "revocation_failed", standardBodyError.error_description ?? "Token revocation failed");
@@ -811,32 +869,193 @@ var MonoCloudOidcClient = class MonoCloudOidcClient {
811
869
  if (!(Array.isArray(claims.aud) ? claims.aud : [claims.aud]).includes(this.clientId)) throw new MonoCloudTokenError("Invalid audience claim");
812
870
  return claims;
813
871
  }
872
+ };
873
+
874
+ //#endregion
875
+ //#region src/monocloud-oidc-backend-client.ts
876
+ /**
877
+ * @category Classes
878
+ */
879
+ var MonoCloudOidcBackendClient = class extends MonoCloudOidcClientBase {
814
880
  /**
815
- * Decodes the payload of a JSON Web Token (JWT) and returns it as an object.
881
+ * Creates a new instance of MonoCloudOidcBackendClient.
816
882
  *
817
- * >Note: THIS METHOD DOES NOT VERIFY JWT TOKENS.
883
+ * @param tenantDomain - The tenant domain URL.
884
+ * @param audience - The expected audience value used to validate the `aud` claim in access tokens.
885
+ * @param options - Additional client configuration options.
886
+ */
887
+ constructor(tenantDomain, audience, options) {
888
+ super(tenantDomain, options?.metadataCacheDuration, options?.jwksCacheDuration, options?.fetcher);
889
+ this.clockSkew = 0;
890
+ this.clockTolerance = 300;
891
+ this.audience = audience;
892
+ if (options?.clientId) this.clientId = options.clientId;
893
+ this.clientSecret = options?.clientSecret;
894
+ this.authMethod = options?.clientAuthMethod ?? "client_secret_basic";
895
+ this.groupOptions = options?.groupOptions;
896
+ if (options?.clockSkew !== void 0) this.clockSkew = options.clockSkew;
897
+ if (options?.clockTolerance !== void 0) this.clockTolerance = options.clockTolerance;
898
+ }
899
+ /**
900
+ * Validates an opaque access token using the OAuth 2.0 Token Introspection endpoint (RFC 7662).
818
901
  *
819
- * @param jwt - JWT to decode.
902
+ * @param accessToken - The access token string to introspect.
903
+ * @param options - Claims validation options.
820
904
  *
821
- * @returns Decoded payload.
905
+ * @returns Validated access token claims (without the `active` field).
822
906
  *
823
- * @throws {@link MonoCloudTokenError} - If decoding fails
907
+ * @throws {@link MonoCloudTokenError} - If the token is not active or claim validation fails.
908
+ *
909
+ * @throws {@link MonoCloudOPError} - When the introspection endpoint returns a standardized
910
+ * OAuth 2.0 error response.
911
+ *
912
+ * @throws {@link MonoCloudHttpError} - Thrown if there is a network error during the request or
913
+ * unexpected status code during the request or a serialization error while processing the response.
914
+ *
915
+ * @throws {@link MonoCloudValidationError} - When the access token is empty or the introspection
916
+ * endpoint is not available in the issuer metadata or claims validation fails.
824
917
  *
825
918
  */
826
- static decodeJwt(jwt) {
919
+ async introspectAccessToken(accessToken, options) {
920
+ if (!this.clientId) throw new MonoCloudValidationError("The clientId option must be configured to introspect access tokens");
921
+ if (typeof accessToken !== "string" || accessToken.trim().length === 0) throw new MonoCloudValidationError("Access token must be a valid non-empty string");
922
+ const metadata = await this.getMetadata();
923
+ assertMetadataProperty(metadata, "introspection_endpoint");
924
+ const body = new URLSearchParams();
925
+ body.set("token", accessToken);
926
+ body.set("token_type_hint", "access_token");
927
+ const headers = {
928
+ "content-type": "application/x-www-form-urlencoded",
929
+ accept: "application/json"
930
+ };
931
+ await clientAuth(this.clientId, this.clientSecret, this.authMethod, this.tenantDomain, headers, body, JWT_ASSERTION_CLOCK_SKEW);
932
+ const response = await innerFetch(metadata.introspection_endpoint, {
933
+ method: "POST",
934
+ body: body.toString(),
935
+ headers
936
+ }, this.fetcher);
937
+ if (response.status === 400 || response.status === 401) {
938
+ const standardBodyError = await deserializeJson(response);
939
+ throw new MonoCloudOPError(standardBodyError.error ?? "introspection_failed", standardBodyError.error_description ?? "Token introspection failed");
940
+ }
941
+ if (response.status !== 200) throw new MonoCloudHttpError(`Error while performing token introspection. Unexpected status code: ${response.status}`);
942
+ const introspectionResponse = await deserializeJson(response);
943
+ if (!introspectionResponse.active) throw new MonoCloudTokenError("Token is not active");
944
+ const { active: _, ...claims } = introspectionResponse;
945
+ this.validateAccessTokenClaims(claims, options?.scopes, options?.groups);
946
+ if (options?.validateCertificateBinding) await this.validateCertificateBinding(claims, options.clientCertificate);
947
+ return claims;
948
+ }
949
+ /**
950
+ * Validates a JWT access token by verifying the signature and claims.
951
+ *
952
+ * @param accessToken - The access token JWT string to validate.
953
+ * @param options - Validation options.
954
+ *
955
+ * @returns Validated access token claims.
956
+ *
957
+ * @throws {@link MonoCloudTokenError} - If JWT parsing, signature verification, or claim validation fails.
958
+ *
959
+ * @throws {@link MonoCloudHttpError} - Thrown if there is a network error during the request or
960
+ * unexpected status code during the request or a serialization error while processing the response.
961
+ *
962
+ * @throws {@link MonoCloudValidationError} - When the access token is empty or claims validation fails.
963
+ *
964
+ */
965
+ async validateJwtAccessToken(accessToken, options) {
966
+ if (typeof accessToken !== "string" || accessToken.trim().length === 0) throw new MonoCloudValidationError("Access token must be a valid non-empty string");
967
+ const { 0: protectedHeader, 1: payload, 2: encodedSignature, length } = accessToken.split(".");
968
+ if (length !== 3) throw new MonoCloudTokenError("JWT access token must have a header, payload and signature");
969
+ let header;
827
970
  try {
828
- const [, payload] = jwt.split(".");
829
- if (!payload?.trim()) throw new MonoCloudTokenError("JWT does not contain payload");
830
- const decoded = decodeBase64Url(payload);
831
- if (!decoded.startsWith("{")) throw new MonoCloudTokenError("Payload is not an object");
832
- return JSON.parse(decoded);
833
- } catch (e) {
834
- if (e instanceof MonoCloudAuthBaseError) throw e;
835
- throw new MonoCloudTokenError("Could not parse payload. Malformed payload");
971
+ header = JSON.parse(decodeBase64Url(protectedHeader));
972
+ } catch {
973
+ throw new MonoCloudTokenError("Failed to parse JWT Header");
974
+ }
975
+ if (header === null || typeof header !== "object" || Array.isArray(header)) throw new MonoCloudTokenError("JWT Header must be a top level object");
976
+ if (header.crit !== void 0) throw new MonoCloudTokenError("Unexpected JWT \"crit\" header parameter");
977
+ if (header.typ && header.typ !== "at+jwt") throw new MonoCloudTokenError("Invalid token type");
978
+ const binary = decodeBase64Url(encodedSignature);
979
+ const signature = new Uint8Array(binary.length);
980
+ for (let i = 0; i < binary.length; i++) signature[i] = binary.charCodeAt(i);
981
+ const key = await getPublicSigKeyFromIssuerJwks((options?.jwks ?? await this.getJwks()).keys, header);
982
+ const input = `${protectedHeader}.${payload}`;
983
+ if (!await crypto.subtle.verify(keyToSubtle(key), key, signature, stringToArrayBuffer(input))) throw new MonoCloudTokenError("JWT signature verification failed");
984
+ let claims;
985
+ try {
986
+ claims = JSON.parse(decodeBase64Url(payload));
987
+ } catch {
988
+ throw new MonoCloudTokenError("Failed to parse JWT Payload");
989
+ }
990
+ if (claims === null || typeof claims !== "object" || Array.isArray(claims)) throw new MonoCloudTokenError("JWT Payload must be a top level object");
991
+ this.validateAccessTokenClaims(claims, options?.scopes, options?.groups);
992
+ if (options?.validateCertificateBinding) await this.validateCertificateBinding(claims, options.clientCertificate);
993
+ return claims;
994
+ }
995
+ /**
996
+ * Sets clock skew used for access token time-based claim validation.
997
+ *
998
+ * @param clockSkew - Number of seconds to adjust the current time to account for clock differences.
999
+ */
1000
+ setClockSkew(clockSkew) {
1001
+ this.clockSkew = clockSkew;
1002
+ }
1003
+ /**
1004
+ * Sets clock tolerance used for access token time-based claim validation.
1005
+ *
1006
+ * @param clockTolerance - Additional time tolerance in seconds for time-based claim validation.
1007
+ */
1008
+ setClockTolerance(clockTolerance) {
1009
+ this.clockTolerance = clockTolerance;
1010
+ }
1011
+ validateAccessTokenClaims(claims, scopes, groups) {
1012
+ const current = now() + this.clockSkew;
1013
+ if (claims.iss !== this.tenantDomain) throw new MonoCloudTokenError("Invalid Issuer");
1014
+ if (claims.sub && typeof claims.sub !== "string") throw new MonoCloudTokenError("Invalid subject");
1015
+ if (!(Array.isArray(claims.aud) ? claims.aud : [claims.aud]).includes(this.audience)) throw new MonoCloudTokenError("Invalid audience claim");
1016
+ if (claims.exp !== void 0) {
1017
+ if (typeof claims.exp !== "number") throw new MonoCloudTokenError("Unexpected \"exp\" (expiration time) claim type");
1018
+ if (claims.exp <= current - this.clockTolerance) throw new MonoCloudTokenError("Unexpected \"exp\" (expiration time) claim value, timestamp is <= now()");
1019
+ }
1020
+ if (claims.nbf !== void 0) {
1021
+ if (typeof claims.nbf !== "number") throw new MonoCloudTokenError("Unexpected \"nbf\" (not before) claim type");
1022
+ if (claims.nbf > current + this.clockTolerance) throw new MonoCloudTokenError("Unexpected \"nbf\" (not before) claim value, timestamp is > now()");
1023
+ }
1024
+ if (scopes && scopes.length > 0) {
1025
+ const tokenScopes = new Set(parseSpaceSeparated(claims.scope));
1026
+ for (const requiredScope of scopes) if (!tokenScopes.has(requiredScope)) throw new MonoCloudTokenError("Token is missing required scopes");
1027
+ }
1028
+ if (groups) {
1029
+ if (!isUserInGroup(claims, groups, this.groupOptions?.groupsClaim, this.groupOptions?.matchAll)) throw new MonoCloudTokenError("Token is missing required groups");
1030
+ }
1031
+ }
1032
+ async validateCertificateBinding(accessTokenClaims, certificate) {
1033
+ if (typeof certificate !== "string" || certificate.trim().length === 0) throw new MonoCloudTokenError("Client certificate is not present");
1034
+ const encodedCertificate = (/-----BEGIN CERTIFICATE-----([\s\S]+?)-----END CERTIFICATE-----/.exec(certificate)?.[1] ?? certificate).replace(/\s+/g, "");
1035
+ let certificateBinary;
1036
+ try {
1037
+ certificateBinary = atob(encodedCertificate);
1038
+ } catch {
1039
+ throw new MonoCloudTokenError("Client certificate is malformed");
1040
+ }
1041
+ const certificateBytes = new Uint8Array(certificateBinary.length);
1042
+ for (let i = 0; i < certificateBinary.length; i++) certificateBytes[i] = certificateBinary.charCodeAt(i);
1043
+ const certificateDigest = await crypto.subtle.digest("SHA-256", certificateBytes);
1044
+ const clientCertHash = arrayBufferToBase64(new Uint8Array(certificateDigest));
1045
+ let cnfClaimValue = accessTokenClaims.cnf;
1046
+ if (cnfClaimValue === void 0 || cnfClaimValue === null) throw new MonoCloudTokenError("Access token does not contain a 'cnf' (confirmation) claim for certificate binding");
1047
+ if (typeof cnfClaimValue === "string") try {
1048
+ cnfClaimValue = JSON.parse(cnfClaimValue);
1049
+ } catch {
1050
+ throw new MonoCloudTokenError("Malformed 'cnf' claim for certificate binding");
836
1051
  }
1052
+ if (cnfClaimValue === null || typeof cnfClaimValue !== "object" || Array.isArray(cnfClaimValue)) throw new MonoCloudTokenError("The 'cnf' claim could not be parsed");
1053
+ const certHash = cnfClaimValue["x5t#S256"];
1054
+ if (typeof certHash !== "string" || certHash.length === 0) throw new MonoCloudTokenError("The 'cnf' claim does not contain an 'x5t#S256' member specifying the certificate hash for binding");
1055
+ if (certHash !== clientCertHash) throw new MonoCloudTokenError("The certificate hash in the access token does not match the presented client certificate (certificate binding validation failed)");
837
1056
  }
838
1057
  };
839
1058
 
840
1059
  //#endregion
841
- export { MonoCloudAuthBaseError, MonoCloudHttpError, MonoCloudOPError, MonoCloudOidcClient, MonoCloudTokenError, MonoCloudValidationError };
1060
+ export { MonoCloudAuthBaseError, MonoCloudHttpError, MonoCloudOPError, MonoCloudOidcBackendClient, MonoCloudOidcClient, MonoCloudOidcClientBase, MonoCloudTokenError, MonoCloudValidationError };
842
1061
  //# sourceMappingURL=index.mjs.map