@monocloud/auth-core 0.1.7 → 0.1.8
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.cjs +318 -97
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +163 -35
- package/dist/index.mjs +318 -99
- package/dist/index.mjs.map +1 -1
- package/dist/{types-DmlJMcTH.d.mts → types-txr_P9Iq.d.mts} +169 -32
- package/dist/utils/index.cjs +1 -1
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.d.mts +1 -1
- package/dist/utils/index.mjs +2 -2
- package/dist/utils/index.mjs.map +1 -1
- package/dist/utils/internal.cjs +10 -0
- package/dist/utils/internal.cjs.map +1 -1
- package/dist/utils/internal.d.mts +11 -2
- package/dist/utils/internal.mjs +10 -1
- package/dist/utils/internal.mjs.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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
|
-
*
|
|
881
|
+
* Creates a new instance of MonoCloudOidcBackendClient.
|
|
816
882
|
*
|
|
817
|
-
*
|
|
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
|
|
902
|
+
* @param accessToken - The access token string to introspect.
|
|
903
|
+
* @param options - Claims validation options.
|
|
820
904
|
*
|
|
821
|
-
* @returns
|
|
905
|
+
* @returns Validated access token claims (without the `active` field).
|
|
822
906
|
*
|
|
823
|
-
* @throws {@link MonoCloudTokenError} - If
|
|
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
|
-
|
|
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
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
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
|