@enterprisestandard/react 0.0.4 → 0.0.5-beta.20260114.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 (47) hide show
  1. package/dist/group-store.d.ts +164 -0
  2. package/dist/group-store.d.ts.map +1 -0
  3. package/dist/iam.d.ts +205 -12
  4. package/dist/iam.d.ts.map +1 -1
  5. package/dist/index.d.ts +44 -11
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +3164 -572
  8. package/dist/index.js.map +29 -0
  9. package/dist/server.d.ts +6 -4
  10. package/dist/server.d.ts.map +1 -1
  11. package/dist/session-store.d.ts +179 -0
  12. package/dist/session-store.d.ts.map +1 -0
  13. package/dist/sso.d.ts +74 -16
  14. package/dist/sso.d.ts.map +1 -1
  15. package/dist/tenant-server.d.ts +8 -0
  16. package/dist/tenant-server.d.ts.map +1 -0
  17. package/dist/tenant.d.ts +280 -0
  18. package/dist/tenant.d.ts.map +1 -0
  19. package/dist/types/base-user.d.ts +27 -0
  20. package/dist/types/base-user.d.ts.map +1 -0
  21. package/dist/types/enterprise-user.d.ts +158 -0
  22. package/dist/types/enterprise-user.d.ts.map +1 -0
  23. package/dist/{oidc-schema.d.ts → types/oidc-schema.d.ts} +42 -0
  24. package/dist/types/oidc-schema.d.ts.map +1 -0
  25. package/dist/types/scim-schema.d.ts +419 -0
  26. package/dist/types/scim-schema.d.ts.map +1 -0
  27. package/dist/types/standard-schema.d.ts.map +1 -0
  28. package/dist/types/user.d.ts +41 -0
  29. package/dist/types/user.d.ts.map +1 -0
  30. package/dist/types/workload-schema.d.ts +106 -0
  31. package/dist/types/workload-schema.d.ts.map +1 -0
  32. package/dist/ui/sso-provider.d.ts +3 -3
  33. package/dist/ui/sso-provider.d.ts.map +1 -1
  34. package/dist/user-store.d.ts +161 -0
  35. package/dist/user-store.d.ts.map +1 -0
  36. package/dist/workload-server.d.ts +126 -0
  37. package/dist/workload-server.d.ts.map +1 -0
  38. package/dist/workload-token-store.d.ts +187 -0
  39. package/dist/workload-token-store.d.ts.map +1 -0
  40. package/dist/workload.d.ts +227 -0
  41. package/dist/workload.d.ts.map +1 -0
  42. package/package.json +2 -5
  43. package/dist/enterprise-user.d.ts +0 -125
  44. package/dist/enterprise-user.d.ts.map +0 -1
  45. package/dist/oidc-schema.d.ts.map +0 -1
  46. package/dist/standard-schema.d.ts.map +0 -1
  47. /package/dist/{standard-schema.d.ts → types/standard-schema.d.ts} +0 -0
package/dist/index.js CHANGED
@@ -1,239 +1,2435 @@
1
- // src/iam.ts
2
- async function iam(config) {
3
- return {
4
- url: config.url,
5
- userEndpoint: config.userEndpoint,
6
- groupEndpoint: config.groupEndpoint
7
- };
8
- }
9
-
10
- // src/utils.ts
11
- var defaultInstance;
12
- function must(value, message = "Assertion failed. Required value is null or undefined.") {
1
+ // packages/react/src/types/scim-schema.ts
2
+ function validateString(value, fieldName, required, issues, path) {
13
3
  if (value === undefined || value === null) {
14
- throw new Error(message);
4
+ if (required) {
5
+ issues.push({
6
+ message: `${fieldName} is required`,
7
+ path
8
+ });
9
+ }
10
+ return;
11
+ }
12
+ if (typeof value !== "string") {
13
+ issues.push({
14
+ message: `${fieldName} must be a string`,
15
+ path
16
+ });
17
+ return;
15
18
  }
16
19
  return value;
17
20
  }
18
- function setDefaultInstance(es) {
19
- defaultInstance = es;
21
+ function validateBoolean(value, fieldName, issues, path) {
22
+ if (value === undefined || value === null) {
23
+ return;
24
+ }
25
+ if (typeof value !== "boolean") {
26
+ issues.push({
27
+ message: `${fieldName} must be a boolean`,
28
+ path
29
+ });
30
+ return;
31
+ }
32
+ return value;
20
33
  }
21
- function getDefaultInstance() {
22
- return defaultInstance;
34
+ function validateName(value, issues, basePath) {
35
+ if (value === undefined || value === null) {
36
+ return;
37
+ }
38
+ if (typeof value !== "object" || value === null) {
39
+ issues.push({
40
+ message: "name must be an object",
41
+ path: basePath
42
+ });
43
+ return;
44
+ }
45
+ const name = value;
46
+ const result = {};
47
+ result.formatted = validateString(name.formatted, "formatted", false, issues, [...basePath, "formatted"]);
48
+ result.familyName = validateString(name.familyName, "familyName", false, issues, [...basePath, "familyName"]);
49
+ result.givenName = validateString(name.givenName, "givenName", false, issues, [...basePath, "givenName"]);
50
+ result.middleName = validateString(name.middleName, "middleName", false, issues, [...basePath, "middleName"]);
51
+ result.honorificPrefix = validateString(name.honorificPrefix, "honorificPrefix", false, issues, [
52
+ ...basePath,
53
+ "honorificPrefix"
54
+ ]);
55
+ result.honorificSuffix = validateString(name.honorificSuffix, "honorificSuffix", false, issues, [
56
+ ...basePath,
57
+ "honorificSuffix"
58
+ ]);
59
+ return result;
23
60
  }
24
- function getES(es) {
25
- if (es)
26
- return es;
27
- if (defaultInstance)
28
- return defaultInstance;
29
- throw new Error(`TODO standardize the error message when there isn't a default EntepriseStandard`);
61
+ function validateEmails(value, issues, basePath) {
62
+ if (value === undefined || value === null) {
63
+ return;
64
+ }
65
+ if (!Array.isArray(value)) {
66
+ issues.push({
67
+ message: "emails must be an array",
68
+ path: basePath
69
+ });
70
+ return;
71
+ }
72
+ const emails = [];
73
+ for (let i = 0;i < value.length; i++) {
74
+ const email = value[i];
75
+ const emailPath = [...basePath, i];
76
+ if (typeof email !== "object" || email === null) {
77
+ issues.push({
78
+ message: "email must be an object",
79
+ path: emailPath
80
+ });
81
+ continue;
82
+ }
83
+ const emailObj = email;
84
+ const emailValue = validateString(emailObj.value, "value", true, issues, [...emailPath, "value"]);
85
+ if (emailValue) {
86
+ emails.push({
87
+ value: emailValue,
88
+ display: validateString(emailObj.display, "display", false, issues, [...emailPath, "display"]),
89
+ type: validateString(emailObj.type, "type", false, issues, [...emailPath, "type"]),
90
+ primary: validateBoolean(emailObj.primary, "primary", issues, [...emailPath, "primary"])
91
+ });
92
+ }
93
+ }
94
+ return emails.length > 0 ? emails : undefined;
30
95
  }
31
-
32
- // src/sso.ts
33
- var jwksCache = new Map;
34
- var _logoutTokenJtis = new Set;
35
- function sso(config) {
36
- const configWithDefaults = {
37
- ...config,
38
- cookies_secure: config.cookies_secure !== undefined ? config.cookies_secure : true,
39
- cookies_same_site: config.cookies_same_site !== undefined ? config.cookies_same_site : "Strict",
40
- cookies_prefix: config.cookies_prefix ?? `es.sso.${config.client_id}`,
41
- cookies_path: config.cookies_path ?? "/"
42
- };
43
- async function getUser(request) {
44
- if (!configWithDefaults) {
45
- console.error("SSO Manager not initialized");
46
- return;
96
+ function validatePhoneNumbers(value, issues, basePath) {
97
+ if (value === undefined || value === null) {
98
+ return;
99
+ }
100
+ if (!Array.isArray(value)) {
101
+ issues.push({
102
+ message: "phoneNumbers must be an array",
103
+ path: basePath
104
+ });
105
+ return;
106
+ }
107
+ const phoneNumbers = [];
108
+ for (let i = 0;i < value.length; i++) {
109
+ const phone = value[i];
110
+ const phonePath = [...basePath, i];
111
+ if (typeof phone !== "object" || phone === null) {
112
+ issues.push({
113
+ message: "phoneNumber must be an object",
114
+ path: phonePath
115
+ });
116
+ continue;
47
117
  }
48
- try {
49
- const { tokens } = await getTokenFromCookies(request);
50
- if (!tokens)
51
- return;
52
- return await parseUser(tokens);
53
- } catch (error) {
54
- console.error("Error parsing user from cookies:", error);
55
- return;
118
+ const phoneObj = phone;
119
+ const phoneValue = validateString(phoneObj.value, "value", true, issues, [...phonePath, "value"]);
120
+ if (phoneValue) {
121
+ phoneNumbers.push({
122
+ value: phoneValue,
123
+ display: validateString(phoneObj.display, "display", false, issues, [...phonePath, "display"]),
124
+ type: validateString(phoneObj.type, "type", false, issues, [...phonePath, "type"]),
125
+ primary: validateBoolean(phoneObj.primary, "primary", issues, [...phonePath, "primary"])
126
+ });
56
127
  }
57
128
  }
58
- async function getRequiredUser(request) {
59
- const user = await getUser(request);
60
- if (user)
61
- return user;
62
- throw new Response("Unauthorized", {
63
- status: 401,
64
- statusText: "Unauthorized"
129
+ return phoneNumbers.length > 0 ? phoneNumbers : undefined;
130
+ }
131
+ function validateAddresses(value, issues, basePath) {
132
+ if (value === undefined || value === null) {
133
+ return;
134
+ }
135
+ if (!Array.isArray(value)) {
136
+ issues.push({
137
+ message: "addresses must be an array",
138
+ path: basePath
65
139
  });
140
+ return;
66
141
  }
67
- async function initiateLogin({ landingUrl, errorUrl }) {
68
- if (!configWithDefaults) {
69
- console.error("SSO Manager not initialized");
70
- return Promise.resolve(new Response("SSO Manager not initialized", { status: 503 }));
142
+ const addresses = [];
143
+ for (let i = 0;i < value.length; i++) {
144
+ const address = value[i];
145
+ const addressPath = [...basePath, i];
146
+ if (typeof address !== "object" || address === null) {
147
+ issues.push({
148
+ message: "address must be an object",
149
+ path: addressPath
150
+ });
151
+ continue;
71
152
  }
72
- const state = generateRandomString();
73
- const codeVerifier = generateRandomString(64);
74
- const url = new URL(configWithDefaults.authorization_url);
75
- url.searchParams.append("client_id", configWithDefaults.client_id);
76
- url.searchParams.append("redirect_uri", configWithDefaults.redirect_uri);
77
- url.searchParams.append("response_type", "code");
78
- url.searchParams.append("scope", configWithDefaults.scope);
79
- url.searchParams.append("state", state);
80
- const codeChallenge = await pkceChallengeFromVerifier(codeVerifier);
81
- url.searchParams.append("code_challenge", codeChallenge);
82
- url.searchParams.append("code_challenge_method", "S256");
83
- const val = {
84
- state,
85
- codeVerifier,
86
- landingUrl,
87
- errorUrl
88
- };
89
- return new Response("Redirecting to SSO Provider", {
90
- status: 302,
91
- headers: {
92
- Location: url.toString(),
93
- "Set-Cookie": createCookie("state", val, 86400)
94
- }
153
+ const addressObj = address;
154
+ addresses.push({
155
+ formatted: validateString(addressObj.formatted, "formatted", false, issues, [...addressPath, "formatted"]),
156
+ streetAddress: validateString(addressObj.streetAddress, "streetAddress", false, issues, [
157
+ ...addressPath,
158
+ "streetAddress"
159
+ ]),
160
+ locality: validateString(addressObj.locality, "locality", false, issues, [...addressPath, "locality"]),
161
+ region: validateString(addressObj.region, "region", false, issues, [...addressPath, "region"]),
162
+ postalCode: validateString(addressObj.postalCode, "postalCode", false, issues, [...addressPath, "postalCode"]),
163
+ country: validateString(addressObj.country, "country", false, issues, [...addressPath, "country"]),
164
+ type: validateString(addressObj.type, "type", false, issues, [...addressPath, "type"]),
165
+ primary: validateBoolean(addressObj.primary, "primary", issues, [...addressPath, "primary"])
95
166
  });
96
167
  }
97
- async function logout(request, _config) {
98
- try {
99
- const refreshToken2 = getCookie("refresh", request);
100
- if (refreshToken2) {
101
- await revokeToken(refreshToken2);
102
- }
103
- } catch (error) {
104
- console.warn("Failed to revoke token:", error);
168
+ return addresses.length > 0 ? addresses : undefined;
169
+ }
170
+ function validateGroups(value, issues, basePath) {
171
+ if (value === undefined || value === null) {
172
+ return;
173
+ }
174
+ if (!Array.isArray(value)) {
175
+ issues.push({
176
+ message: "groups must be an array",
177
+ path: basePath
178
+ });
179
+ return;
180
+ }
181
+ const groups = [];
182
+ for (let i = 0;i < value.length; i++) {
183
+ const group = value[i];
184
+ const groupPath = [...basePath, i];
185
+ if (typeof group !== "object" || group === null) {
186
+ issues.push({
187
+ message: "group must be an object",
188
+ path: groupPath
189
+ });
190
+ continue;
105
191
  }
106
- const clearHeaders = [
107
- ["Set-Cookie", clearCookie("access")],
108
- ["Set-Cookie", clearCookie("id")],
109
- ["Set-Cookie", clearCookie("refresh")],
110
- ["Set-Cookie", clearCookie("control")],
111
- ["Set-Cookie", clearCookie("state")]
112
- ];
113
- const url = new URL(request.url);
114
- const redirectTo = url.searchParams.get("redirect");
115
- if (redirectTo) {
116
- return new Response("Logged out", {
117
- status: 302,
118
- headers: [["Location", redirectTo], ...clearHeaders]
192
+ const groupObj = group;
193
+ const groupValue = validateString(groupObj.value, "value", true, issues, [...groupPath, "value"]);
194
+ if (groupValue) {
195
+ groups.push({
196
+ value: groupValue,
197
+ $ref: validateString(groupObj.$ref, "$ref", false, issues, [...groupPath, "$ref"]),
198
+ display: validateString(groupObj.display, "display", false, issues, [...groupPath, "display"]),
199
+ type: validateString(groupObj.type, "type", false, issues, [...groupPath, "type"])
119
200
  });
120
201
  }
121
- const accept = request.headers.get("accept");
122
- const isAjax = accept?.includes("application/json") || accept?.includes("text/javascript");
123
- if (isAjax) {
124
- return new Response(JSON.stringify({ success: true, message: "Logged out" }), {
125
- status: 200,
126
- headers: [["Content-Type", "application/json"], ...clearHeaders]
202
+ }
203
+ return groups.length > 0 ? groups : undefined;
204
+ }
205
+ function validateRoles(value, issues, basePath) {
206
+ if (value === undefined || value === null) {
207
+ return;
208
+ }
209
+ if (!Array.isArray(value)) {
210
+ issues.push({
211
+ message: "roles must be an array",
212
+ path: basePath
213
+ });
214
+ return;
215
+ }
216
+ const roles = [];
217
+ for (let i = 0;i < value.length; i++) {
218
+ const role = value[i];
219
+ const rolePath = [...basePath, i];
220
+ if (typeof role !== "object" || role === null) {
221
+ issues.push({
222
+ message: "role must be an object",
223
+ path: rolePath
127
224
  });
128
- } else {
129
- return new Response(`
130
- <!DOCTYPE html><html lang="en"><body>
131
- <h1>Logout Complete</h1>
132
- <div style="display: none">
133
- It is not recommended to show the default logout page. Include '?redirect=/someHomePage' or logout asynchronously.
134
- Check the <a href="https://EnterpriseStandard.com/sso#logout">Enterprise Standard Packages</a> for more information.
135
- </div>
136
- </body></html>
137
- `, {
138
- status: 200,
139
- headers: [["Content-Type", "text/html"], ...clearHeaders]
225
+ continue;
226
+ }
227
+ const roleObj = role;
228
+ const roleValue = validateString(roleObj.value, "value", true, issues, [...rolePath, "value"]);
229
+ if (roleValue) {
230
+ roles.push({
231
+ value: roleValue,
232
+ display: validateString(roleObj.display, "display", false, issues, [...rolePath, "display"]),
233
+ type: validateString(roleObj.type, "type", false, issues, [...rolePath, "type"]),
234
+ primary: validateBoolean(roleObj.primary, "primary", issues, [...rolePath, "primary"])
140
235
  });
141
236
  }
142
237
  }
143
- async function callbackHandler(request) {
144
- if (!configWithDefaults) {
145
- console.error("SSO Manager not initialized");
146
- return Promise.resolve(new Response("SSO Manager not initialized", { status: 503 }));
147
- }
148
- const url = new URL(request.url);
149
- const params = new URLSearchParams(url.search);
150
- try {
151
- const codeFromUrl = must(params.get("code"), 'OIDC "code" was not passed as a search param, ensure that the SSO login completed successfully');
152
- const stateFromUrl = must(params.get("state"), 'OIDC "state" was not passed as a search param, ensure that the SSO login completed successfully');
153
- const cookie = getCookie("state", request, true);
154
- const { codeVerifier, state, landingUrl } = cookie ?? {};
155
- must(codeVerifier, 'OIDC "codeVerifier" was not present in cookies, ensure that the SSO login was initiated correctly');
156
- must(state, 'OIDC "stateVerifier" was not present in cookies, ensure that the SSO login was initiated correctly');
157
- must(landingUrl, 'OIDC "landingUrl" was not present in cookies');
158
- if (stateFromUrl !== state) {
159
- throw new Error('SSO State Verifier failed, the "state" request parameter does not equal the "state" in the SSO cookie');
160
- }
161
- const tokenResponse = await exchangeCodeForToken(codeFromUrl, codeVerifier);
162
- const user = await parseUser(tokenResponse);
163
- return new Response("Authentication successful, redirecting", {
164
- status: 302,
165
- headers: [
166
- ["Location", landingUrl],
167
- ["Set-Cookie", clearCookie("state")],
168
- ...createJwtCookies(tokenResponse, user.sso.expires)
169
- ]
238
+ return roles.length > 0 ? roles : undefined;
239
+ }
240
+ function validateEnterpriseUser(value, issues, basePath) {
241
+ if (value === undefined || value === null) {
242
+ return;
243
+ }
244
+ if (typeof value !== "object" || value === null) {
245
+ issues.push({
246
+ message: "Enterprise User extension must be an object",
247
+ path: basePath
248
+ });
249
+ return;
250
+ }
251
+ const enterprise = value;
252
+ const result = {};
253
+ result.employeeNumber = validateString(enterprise.employeeNumber, "employeeNumber", false, issues, [
254
+ ...basePath,
255
+ "employeeNumber"
256
+ ]);
257
+ result.costCenter = validateString(enterprise.costCenter, "costCenter", false, issues, [...basePath, "costCenter"]);
258
+ result.organization = validateString(enterprise.organization, "organization", false, issues, [
259
+ ...basePath,
260
+ "organization"
261
+ ]);
262
+ result.division = validateString(enterprise.division, "division", false, issues, [...basePath, "division"]);
263
+ result.department = validateString(enterprise.department, "department", false, issues, [...basePath, "department"]);
264
+ if (enterprise.manager !== undefined && enterprise.manager !== null) {
265
+ if (typeof enterprise.manager !== "object" || enterprise.manager === null) {
266
+ issues.push({
267
+ message: "manager must be an object",
268
+ path: [...basePath, "manager"]
170
269
  });
171
- } catch (error) {
172
- console.error("Error during sign-in callback:", error);
173
- try {
174
- const cookie = getCookie("state", request, true);
175
- const { errorUrl } = cookie ?? {};
176
- if (errorUrl) {
177
- return new Response("Redirecting to error url", {
178
- status: 302,
179
- headers: [["Location", errorUrl]]
180
- });
270
+ } else {
271
+ const manager = enterprise.manager;
272
+ result.manager = {
273
+ value: validateString(manager.value, "value", false, issues, [...basePath, "manager", "value"]),
274
+ $ref: validateString(manager.$ref, "$ref", false, issues, [...basePath, "manager", "$ref"]),
275
+ displayName: validateString(manager.displayName, "displayName", false, issues, [
276
+ ...basePath,
277
+ "manager",
278
+ "displayName"
279
+ ])
280
+ };
281
+ }
282
+ }
283
+ return result;
284
+ }
285
+ function userSchema(vendor) {
286
+ return {
287
+ "~standard": {
288
+ version: 1,
289
+ vendor,
290
+ validate: (value) => {
291
+ if (typeof value !== "object" || value === null) {
292
+ return {
293
+ issues: [
294
+ {
295
+ message: "Expected an object"
296
+ }
297
+ ]
298
+ };
181
299
  }
182
- } catch (_err) {
183
- console.warn("Error parsing the errorUrl from the OIDC cookie");
300
+ const user = value;
301
+ const issues = [];
302
+ const result = {};
303
+ const userName = validateString(user.userName, "userName", true, issues, ["userName"]);
304
+ if (!userName) {
305
+ return { issues };
306
+ }
307
+ result.userName = userName;
308
+ result.id = validateString(user.id, "id", false, issues, ["id"]);
309
+ result.externalId = validateString(user.externalId, "externalId", false, issues, ["externalId"]);
310
+ result.displayName = validateString(user.displayName, "displayName", false, issues, ["displayName"]);
311
+ result.nickName = validateString(user.nickName, "nickName", false, issues, ["nickName"]);
312
+ result.profileUrl = validateString(user.profileUrl, "profileUrl", false, issues, ["profileUrl"]);
313
+ result.title = validateString(user.title, "title", false, issues, ["title"]);
314
+ result.userType = validateString(user.userType, "userType", false, issues, ["userType"]);
315
+ result.preferredLanguage = validateString(user.preferredLanguage, "preferredLanguage", false, issues, [
316
+ "preferredLanguage"
317
+ ]);
318
+ result.locale = validateString(user.locale, "locale", false, issues, ["locale"]);
319
+ result.timezone = validateString(user.timezone, "timezone", false, issues, ["timezone"]);
320
+ result.password = validateString(user.password, "password", false, issues, ["password"]);
321
+ result.active = validateBoolean(user.active, "active", issues, ["active"]);
322
+ result.name = validateName(user.name, issues, ["name"]);
323
+ result.emails = validateEmails(user.emails, issues, ["emails"]);
324
+ result.phoneNumbers = validatePhoneNumbers(user.phoneNumbers, issues, ["phoneNumbers"]);
325
+ result.addresses = validateAddresses(user.addresses, issues, ["addresses"]);
326
+ result.groups = validateGroups(user.groups, issues, ["groups"]);
327
+ result.roles = validateRoles(user.roles, issues, ["roles"]);
328
+ const enterpriseKey = "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User";
329
+ if (user[enterpriseKey] !== undefined) {
330
+ result[enterpriseKey] = validateEnterpriseUser(user[enterpriseKey], issues, [enterpriseKey]);
331
+ }
332
+ if (user.schemas !== undefined) {
333
+ if (Array.isArray(user.schemas)) {
334
+ result.schemas = user.schemas.filter((s) => typeof s === "string");
335
+ } else {
336
+ issues.push({
337
+ message: "schemas must be an array",
338
+ path: ["schemas"]
339
+ });
340
+ }
341
+ }
342
+ if (user.meta !== undefined) {
343
+ if (typeof user.meta === "object" && user.meta !== null) {
344
+ const meta = user.meta;
345
+ result.meta = {
346
+ resourceType: typeof meta.resourceType === "string" ? meta.resourceType : undefined,
347
+ created: typeof meta.created === "string" ? meta.created : undefined,
348
+ lastModified: typeof meta.lastModified === "string" ? meta.lastModified : undefined,
349
+ location: typeof meta.location === "string" ? meta.location : undefined,
350
+ version: typeof meta.version === "string" ? meta.version : undefined
351
+ };
352
+ } else {
353
+ issues.push({
354
+ message: "meta must be an object",
355
+ path: ["meta"]
356
+ });
357
+ }
358
+ }
359
+ if (issues.length > 0) {
360
+ return { issues };
361
+ }
362
+ return { value: result };
184
363
  }
185
- console.warn("No error page was found in the cookies. The user will be shown a default error page.");
186
- return new Response("An error occurred during authentication, please return to the application homepage and try again.", {
187
- status: 500
188
- });
189
364
  }
365
+ };
366
+ }
367
+ function validateMembers(value, issues, basePath) {
368
+ if (value === undefined || value === null) {
369
+ return;
190
370
  }
191
- async function parseUser(token) {
192
- if (!configWithDefaults)
193
- throw new Error("SSO Manager not initialized");
194
- const idToken = await parseJwt(token.id_token);
195
- const expiresIn = Number(token.refresh_expires_in ?? token.expires_in ?? 3600);
196
- const expires = token.expires ? new Date(token.expires) : new Date(Date.now() + expiresIn * 1000);
197
- return {
198
- id: idToken.sub,
199
- userName: idToken.preferred_username || "",
200
- name: idToken.name || "",
201
- email: idToken.email || "",
202
- emails: [
203
- {
204
- value: idToken.email || "",
205
- primary: true
371
+ if (!Array.isArray(value)) {
372
+ issues.push({
373
+ message: "members must be an array",
374
+ path: basePath
375
+ });
376
+ return;
377
+ }
378
+ const members = [];
379
+ for (let i = 0;i < value.length; i++) {
380
+ const member = value[i];
381
+ const memberPath = [...basePath, i];
382
+ if (typeof member !== "object" || member === null) {
383
+ issues.push({
384
+ message: "member must be an object",
385
+ path: memberPath
386
+ });
387
+ continue;
388
+ }
389
+ const memberObj = member;
390
+ const memberValue = validateString(memberObj.value, "value", true, issues, [...memberPath, "value"]);
391
+ if (memberValue) {
392
+ const memberType = validateString(memberObj.type, "type", false, issues, [...memberPath, "type"]);
393
+ members.push({
394
+ value: memberValue,
395
+ $ref: validateString(memberObj.$ref, "$ref", false, issues, [...memberPath, "$ref"]),
396
+ display: validateString(memberObj.display, "display", false, issues, [...memberPath, "display"]),
397
+ type: memberType === "User" || memberType === "Group" ? memberType : undefined
398
+ });
399
+ }
400
+ }
401
+ return members.length > 0 ? members : undefined;
402
+ }
403
+ function groupResourceSchema(vendor) {
404
+ return {
405
+ "~standard": {
406
+ version: 1,
407
+ vendor,
408
+ validate: (value) => {
409
+ if (typeof value !== "object" || value === null) {
410
+ return {
411
+ issues: [
412
+ {
413
+ message: "Expected an object"
414
+ }
415
+ ]
416
+ };
206
417
  }
207
- ],
208
- avatarUrl: idToken.picture,
209
- sso: {
210
- profile: {
211
- ...idToken,
212
- iss: idToken.iss || configWithDefaults.authority,
213
- aud: idToken.aud || configWithDefaults.client_id
418
+ const group = value;
419
+ const issues = [];
420
+ const result = {};
421
+ const displayName = validateString(group.displayName, "displayName", true, issues, ["displayName"]);
422
+ if (!displayName) {
423
+ return { issues };
424
+ }
425
+ result.displayName = displayName;
426
+ result.id = validateString(group.id, "id", false, issues, ["id"]);
427
+ result.externalId = validateString(group.externalId, "externalId", false, issues, ["externalId"]);
428
+ result.members = validateMembers(group.members, issues, ["members"]);
429
+ if (group.schemas !== undefined) {
430
+ if (Array.isArray(group.schemas)) {
431
+ result.schemas = group.schemas.filter((s) => typeof s === "string");
432
+ } else {
433
+ issues.push({
434
+ message: "schemas must be an array",
435
+ path: ["schemas"]
436
+ });
437
+ }
438
+ }
439
+ if (group.meta !== undefined) {
440
+ if (typeof group.meta === "object" && group.meta !== null) {
441
+ const meta = group.meta;
442
+ result.meta = {
443
+ resourceType: typeof meta.resourceType === "string" ? meta.resourceType : undefined,
444
+ created: typeof meta.created === "string" ? meta.created : undefined,
445
+ lastModified: typeof meta.lastModified === "string" ? meta.lastModified : undefined,
446
+ location: typeof meta.location === "string" ? meta.location : undefined,
447
+ version: typeof meta.version === "string" ? meta.version : undefined
448
+ };
449
+ } else {
450
+ issues.push({
451
+ message: "meta must be an object",
452
+ path: ["meta"]
453
+ });
454
+ }
455
+ }
456
+ if (issues.length > 0) {
457
+ return { issues };
458
+ }
459
+ return { value: result };
460
+ }
461
+ }
462
+ };
463
+ }
464
+
465
+ // packages/react/src/iam.ts
466
+ var SCIM_CONTENT_TYPE = "application/scim+json";
467
+ function scimErrorResponse(status, detail, scimType) {
468
+ return new Response(JSON.stringify({
469
+ schemas: ["urn:ietf:params:scim:api:messages:2.0:Error"],
470
+ status: String(status),
471
+ scimType,
472
+ detail
473
+ }), {
474
+ status,
475
+ headers: { "Content-Type": SCIM_CONTENT_TYPE }
476
+ });
477
+ }
478
+ function scimListResponse(resources) {
479
+ return new Response(JSON.stringify({
480
+ schemas: ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
481
+ totalResults: resources.length,
482
+ startIndex: 1,
483
+ itemsPerPage: resources.length,
484
+ Resources: resources
485
+ }), {
486
+ status: 200,
487
+ headers: { "Content-Type": SCIM_CONTENT_TYPE }
488
+ });
489
+ }
490
+ function scimResourceResponse(resource, status = 200) {
491
+ return new Response(JSON.stringify(resource), {
492
+ status,
493
+ headers: { "Content-Type": SCIM_CONTENT_TYPE }
494
+ });
495
+ }
496
+ function storedGroupToResource(group) {
497
+ return {
498
+ schemas: ["urn:ietf:params:scim:schemas:core:2.0:Group"],
499
+ id: group.id,
500
+ externalId: group.externalId,
501
+ displayName: group.displayName,
502
+ members: group.members,
503
+ meta: {
504
+ resourceType: "Group",
505
+ created: group.createdAt.toISOString(),
506
+ lastModified: group.updatedAt.toISOString()
507
+ }
508
+ };
509
+ }
510
+ function generateId() {
511
+ return crypto.randomUUID();
512
+ }
513
+ function iam(config, workload) {
514
+ const { url, group_store } = config;
515
+ async function buildHeaders() {
516
+ const token = await workload.getToken();
517
+ return new Headers({
518
+ "Content-Type": SCIM_CONTENT_TYPE,
519
+ Accept: SCIM_CONTENT_TYPE,
520
+ Authorization: `Bearer ${token}`
521
+ });
522
+ }
523
+ async function scimRequest(method, endpoint, body, validator) {
524
+ if (!url) {
525
+ return {
526
+ success: false,
527
+ error: {
528
+ schemas: ["urn:ietf:params:scim:api:messages:2.0:Error"],
529
+ status: "500",
530
+ detail: "IAM URL not configured for outgoing requests"
214
531
  },
215
- tenant: {
216
- id: idToken.idp || idToken.iss || configWithDefaults.authority,
217
- name: idToken.iss || configWithDefaults.authority
532
+ status: 500
533
+ };
534
+ }
535
+ const requestUrl = `${url}${endpoint}`;
536
+ try {
537
+ const headers = await buildHeaders();
538
+ const response = await fetch(requestUrl, {
539
+ method,
540
+ headers,
541
+ body: body ? JSON.stringify(body) : undefined
542
+ });
543
+ const responseData = await response.json();
544
+ if (!response.ok) {
545
+ return {
546
+ success: false,
547
+ error: responseData,
548
+ status: response.status
549
+ };
550
+ }
551
+ if (validator) {
552
+ const validationResult = await validator["~standard"].validate(responseData);
553
+ if ("issues" in validationResult) {
554
+ return {
555
+ success: false,
556
+ error: {
557
+ schemas: ["urn:ietf:params:scim:api:messages:2.0:Error"],
558
+ status: "400",
559
+ scimType: "invalidValue",
560
+ detail: `Response validation failed: ${validationResult.issues?.map((i) => i.message).join("; ")}`
561
+ },
562
+ status: 400
563
+ };
564
+ }
565
+ return {
566
+ success: true,
567
+ data: validationResult.value,
568
+ status: response.status
569
+ };
570
+ }
571
+ return {
572
+ success: true,
573
+ data: responseData,
574
+ status: response.status
575
+ };
576
+ } catch (error) {
577
+ return {
578
+ success: false,
579
+ error: {
580
+ schemas: ["urn:ietf:params:scim:api:messages:2.0:Error"],
581
+ status: "500",
582
+ detail: error instanceof Error ? error.message : "Unknown error occurred"
218
583
  },
219
- scope: token.scope,
220
- tokenType: token.token_type,
221
- sessionState: token.session_state,
222
- expires
584
+ status: 500
585
+ };
586
+ }
587
+ }
588
+ function getBaseUrl() {
589
+ return url;
590
+ }
591
+ let groups_outbound;
592
+ let createUser;
593
+ if (url) {
594
+ createUser = async (user, options) => {
595
+ const userPayload = {
596
+ ...user,
597
+ schemas: user.schemas ?? [
598
+ "urn:ietf:params:scim:schemas:core:2.0:User",
599
+ "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"
600
+ ]
601
+ };
602
+ const validator = options?.validation ?? userSchema("es-iam");
603
+ return scimRequest("POST", "/Users", userPayload, validator);
604
+ };
605
+ async function createGroup(displayName, options) {
606
+ const groupPayload = {
607
+ schemas: ["urn:ietf:params:scim:schemas:core:2.0:Group"],
608
+ displayName,
609
+ externalId: options?.externalId,
610
+ members: options?.members
611
+ };
612
+ const validator = options?.validation ?? groupResourceSchema("es-iam");
613
+ return scimRequest("POST", "/Groups", groupPayload, validator);
614
+ }
615
+ groups_outbound = {
616
+ createGroup
617
+ };
618
+ }
619
+ let groups_inbound;
620
+ if (group_store) {
621
+ const store = group_store;
622
+ const validateAuth = async (request) => {
623
+ const auth = request.headers.get("Authorization");
624
+ if (!auth || !auth.startsWith("Bearer ")) {
625
+ return false;
626
+ }
627
+ try {
628
+ const token = auth.substring(7);
629
+ const result = await workload.validateToken(token);
630
+ return result.valid;
631
+ } catch {
632
+ return false;
633
+ }
634
+ };
635
+ const handler = async (request, handlerConfig) => {
636
+ const isAuthorized = await validateAuth(request);
637
+ if (!isAuthorized) {
638
+ return scimErrorResponse(401, "Authorization required");
639
+ }
640
+ const urlObj = new URL(request.url);
641
+ const basePath = handlerConfig?.basePath ?? "/Groups";
642
+ let path = urlObj.pathname;
643
+ if (path.startsWith(basePath)) {
644
+ path = path.substring(basePath.length);
645
+ }
646
+ const groupIdMatch = path.match(/^\/([^/]+)$/);
647
+ const groupId = groupIdMatch?.[1];
648
+ const method = request.method;
649
+ try {
650
+ if (groupId) {
651
+ switch (method) {
652
+ case "GET":
653
+ return await handleGetGroup(groupId);
654
+ case "PUT":
655
+ return await handleReplaceGroup(request, groupId);
656
+ case "PATCH":
657
+ return await handlePatchGroup(request, groupId);
658
+ case "DELETE":
659
+ return await handleDeleteGroup(groupId);
660
+ default:
661
+ return scimErrorResponse(405, "Method not allowed");
662
+ }
663
+ } else if (path === "" || path === "/") {
664
+ switch (method) {
665
+ case "GET":
666
+ return await handleListGroups();
667
+ case "POST":
668
+ return await handleCreateGroup(request);
669
+ default:
670
+ return scimErrorResponse(405, "Method not allowed");
671
+ }
672
+ }
673
+ return scimErrorResponse(404, "Resource not found");
674
+ } catch (error) {
675
+ console.error("Groups inbound handler error:", error);
676
+ return scimErrorResponse(500, error instanceof Error ? error.message : "Internal server error");
677
+ }
678
+ };
679
+ const handleListGroups = async () => {
680
+ const groups = await store.list();
681
+ const resources = groups.map(storedGroupToResource);
682
+ return scimListResponse(resources);
683
+ };
684
+ const handleGetGroup = async (id) => {
685
+ const group = await store.get(id);
686
+ if (!group) {
687
+ return scimErrorResponse(404, `Group ${id} not found`, "invalidValue");
688
+ }
689
+ return scimResourceResponse(storedGroupToResource(group));
690
+ };
691
+ const handleCreateGroup = async (request) => {
692
+ const body = await request.json();
693
+ if (!body.displayName) {
694
+ return scimErrorResponse(400, "displayName is required", "invalidValue");
695
+ }
696
+ const now = new Date;
697
+ const storedGroup = {
698
+ id: generateId(),
699
+ displayName: body.displayName,
700
+ externalId: body.externalId,
701
+ members: body.members,
702
+ createdAt: now,
703
+ updatedAt: now
704
+ };
705
+ await store.upsert(storedGroup);
706
+ return scimResourceResponse(storedGroupToResource(storedGroup), 201);
707
+ };
708
+ const handleReplaceGroup = async (request, id) => {
709
+ const existing = await store.get(id);
710
+ if (!existing) {
711
+ return scimErrorResponse(404, `Group ${id} not found`, "invalidValue");
712
+ }
713
+ const body = await request.json();
714
+ const updatedGroup = {
715
+ ...existing,
716
+ displayName: body.displayName ?? existing.displayName,
717
+ externalId: body.externalId,
718
+ members: body.members,
719
+ updatedAt: new Date
720
+ };
721
+ await store.upsert(updatedGroup);
722
+ return scimResourceResponse(storedGroupToResource(updatedGroup));
723
+ };
724
+ const handlePatchGroup = async (request, id) => {
725
+ const existing = await store.get(id);
726
+ if (!existing) {
727
+ return scimErrorResponse(404, `Group ${id} not found`, "invalidValue");
728
+ }
729
+ const body = await request.json();
730
+ const operations = body.Operations ?? [];
731
+ const updated = { ...existing };
732
+ for (const op of operations) {
733
+ if (op.op === "replace" && op.path && op.value !== undefined) {
734
+ if (op.path === "displayName") {
735
+ updated.displayName = op.value;
736
+ }
737
+ } else if (op.op === "add" && op.path && op.value !== undefined) {
738
+ if (op.path === "members") {
739
+ const newMembers = op.value;
740
+ updated.members = [...updated.members ?? [], ...newMembers];
741
+ }
742
+ } else if (op.op === "remove" && op.path) {
743
+ if (op.path.startsWith("members[")) {
744
+ const match = op.path.match(/members\[value eq "([^"]+)"\]/);
745
+ if (match) {
746
+ updated.members = (updated.members ?? []).filter((m) => m.value !== match[1]);
747
+ }
748
+ }
749
+ }
223
750
  }
751
+ updated.updatedAt = new Date;
752
+ await store.upsert(updated);
753
+ return scimResourceResponse(storedGroupToResource(updated));
754
+ };
755
+ const handleDeleteGroup = async (id) => {
756
+ const existing = await store.get(id);
757
+ if (!existing) {
758
+ return scimErrorResponse(404, `Group ${id} not found`, "invalidValue");
759
+ }
760
+ await store.delete(id);
761
+ return new Response(null, { status: 204 });
762
+ };
763
+ groups_inbound = {
764
+ handler
765
+ };
766
+ }
767
+ let users_inbound;
768
+ if (config.user_store) {
769
+ const store = config.user_store;
770
+ async function validateAuth(request) {
771
+ const auth = request.headers.get("Authorization");
772
+ if (!auth || !auth.startsWith("Bearer ")) {
773
+ return false;
774
+ }
775
+ try {
776
+ const token = auth.substring(7);
777
+ const result = await workload.validateToken(token);
778
+ return result.valid;
779
+ } catch {
780
+ return false;
781
+ }
782
+ }
783
+ const storedUserToScimUser = (storedUser) => {
784
+ return {
785
+ schemas: [
786
+ "urn:ietf:params:scim:schemas:core:2.0:User",
787
+ "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"
788
+ ],
789
+ id: storedUser.id,
790
+ userName: storedUser.userName || storedUser.email || storedUser.id,
791
+ displayName: storedUser.name || storedUser.userName || storedUser.email,
792
+ name: storedUser.name ? {
793
+ givenName: storedUser.name.split(" ")[0],
794
+ familyName: storedUser.name.split(" ").slice(1).join(" ") || undefined
795
+ } : undefined,
796
+ emails: storedUser.email ? [{ value: storedUser.email, primary: true }] : [],
797
+ active: true,
798
+ meta: {
799
+ resourceType: "User",
800
+ created: storedUser.createdAt.toISOString(),
801
+ lastModified: storedUser.updatedAt.toISOString()
802
+ }
803
+ };
804
+ };
805
+ const scimUserToStoredUser = (scimUser) => {
806
+ const now = new Date;
807
+ const primaryEmail = scimUser.emails?.find((e) => e.primary)?.value || scimUser.emails?.[0]?.value;
808
+ const name = scimUser.name ? `${scimUser.name.givenName || ""} ${scimUser.name.familyName || ""}`.trim() : scimUser.displayName;
809
+ const userId = scimUser.id || generateId();
810
+ const userName = scimUser.userName || primaryEmail || userId;
811
+ return {
812
+ id: userId,
813
+ userName,
814
+ name: name || scimUser.displayName || userName,
815
+ email: primaryEmail || userName,
816
+ avatarUrl: scimUser.profileUrl,
817
+ sso: {
818
+ profile: {
819
+ sub: userId,
820
+ iss: "iam-provisioned",
821
+ aud: "iam-provisioned",
822
+ exp: Math.floor(Date.now() / 1000) + 3600,
823
+ iat: Math.floor(Date.now() / 1000),
824
+ email: primaryEmail || userName,
825
+ email_verified: true,
826
+ name: name || scimUser.displayName || userName,
827
+ preferred_username: userName
828
+ },
829
+ tenant: {
830
+ id: "iam-provisioned",
831
+ name: "IAM Provisioned"
832
+ },
833
+ scope: "openid profile email",
834
+ tokenType: "Bearer",
835
+ expires: new Date(Date.now() + 3600 * 1000)
836
+ },
837
+ createdAt: scimUser.meta?.created ? new Date(scimUser.meta.created) : now,
838
+ updatedAt: scimUser.meta?.lastModified ? new Date(scimUser.meta.lastModified) : now
839
+ };
840
+ };
841
+ const handler = async (request, handlerConfig) => {
842
+ const isAuthorized = await validateAuth(request);
843
+ if (!isAuthorized) {
844
+ return scimErrorResponse(401, "Authorization required");
845
+ }
846
+ const urlObj = new URL(request.url);
847
+ const basePath = handlerConfig?.basePath ?? "/Users";
848
+ let path = urlObj.pathname;
849
+ if (path.startsWith(basePath)) {
850
+ path = path.substring(basePath.length);
851
+ }
852
+ const userIdMatch = path.match(/^\/([^/]+)$/);
853
+ const userId = userIdMatch?.[1];
854
+ const method = request.method;
855
+ try {
856
+ if (userId) {
857
+ switch (method) {
858
+ case "GET":
859
+ return await handleGetUser(userId);
860
+ case "PUT":
861
+ return await handleReplaceUser(request, userId);
862
+ case "PATCH":
863
+ return await handlePatchUser(request, userId);
864
+ case "DELETE":
865
+ return await handleDeleteUser(userId);
866
+ default:
867
+ return scimErrorResponse(405, "Method not allowed");
868
+ }
869
+ } else if (path === "" || path === "/") {
870
+ switch (method) {
871
+ case "GET":
872
+ return await handleListUsers();
873
+ case "POST":
874
+ return await handleCreateUser(request);
875
+ default:
876
+ return scimErrorResponse(405, "Method not allowed");
877
+ }
878
+ }
879
+ return scimErrorResponse(404, "Resource not found");
880
+ } catch (error) {
881
+ console.error("Users inbound handler error:", error);
882
+ return scimErrorResponse(500, error instanceof Error ? error.message : "Internal server error");
883
+ }
884
+ };
885
+ const handleListUsers = async () => {
886
+ return scimListResponse([]);
887
+ };
888
+ const handleGetUser = async (id) => {
889
+ const user = await store.get(id);
890
+ if (!user) {
891
+ return scimErrorResponse(404, `User ${id} not found`, "invalidValue");
892
+ }
893
+ return scimResourceResponse(storedUserToScimUser(user));
894
+ };
895
+ const handleCreateUser = async (request) => {
896
+ const body = await request.json();
897
+ if (!body.userName && !body.emails?.[0]?.value) {
898
+ return scimErrorResponse(400, "userName or email is required", "invalidValue");
899
+ }
900
+ const storedUser = scimUserToStoredUser(body);
901
+ await store.upsert(storedUser);
902
+ return scimResourceResponse(storedUserToScimUser(storedUser), 201);
903
+ };
904
+ const handleReplaceUser = async (request, id) => {
905
+ const existing = await store.get(id);
906
+ if (!existing) {
907
+ return scimErrorResponse(404, `User ${id} not found`, "invalidValue");
908
+ }
909
+ const body = await request.json();
910
+ const updatedUser = scimUserToStoredUser({ ...body, id });
911
+ updatedUser.createdAt = existing.createdAt;
912
+ updatedUser.updatedAt = new Date;
913
+ await store.upsert(updatedUser);
914
+ return scimResourceResponse(storedUserToScimUser(updatedUser));
915
+ };
916
+ const handlePatchUser = async (request, id) => {
917
+ const existing = await store.get(id);
918
+ if (!existing) {
919
+ return scimErrorResponse(404, `User ${id} not found`, "invalidValue");
920
+ }
921
+ const body = await request.json();
922
+ const operations = body.Operations ?? [];
923
+ const updated = { ...existing };
924
+ for (const op of operations) {
925
+ if (op.op === "replace" && op.path && op.value !== undefined) {
926
+ if (op.path === "displayName") {
927
+ updated.name = op.value;
928
+ } else if (op.path === "userName") {
929
+ updated.userName = op.value;
930
+ } else if (op.path.startsWith("name.")) {
931
+ const namePart = op.path.split(".")[1];
932
+ if (!updated.name)
933
+ updated.name = "";
934
+ if (namePart === "givenName") {
935
+ updated.name = `${op.value} ${updated.name.split(" ").slice(1).join(" ")}`.trim();
936
+ } else if (namePart === "familyName") {
937
+ updated.name = `${updated.name.split(" ")[0]} ${op.value}`.trim();
938
+ }
939
+ } else if (op.path === "emails") {
940
+ const emails = op.value;
941
+ const primaryEmail = emails?.find((e) => e.primary)?.value || emails?.[0]?.value;
942
+ if (primaryEmail)
943
+ updated.email = primaryEmail;
944
+ }
945
+ } else if (op.op === "add" && op.path && op.value !== undefined) {
946
+ if (op.path === "emails") {
947
+ const newEmails = op.value;
948
+ const primaryEmail = newEmails?.find((e) => e.primary)?.value || newEmails?.[0]?.value;
949
+ if (primaryEmail)
950
+ updated.email = primaryEmail;
951
+ }
952
+ } else if (op.op === "remove" && op.path) {
953
+ if (op.path === "displayName") {
954
+ updated.name = "";
955
+ }
956
+ }
957
+ }
958
+ updated.updatedAt = new Date;
959
+ await store.upsert(updated);
960
+ return scimResourceResponse(storedUserToScimUser(updated));
961
+ };
962
+ const handleDeleteUser = async (id) => {
963
+ const existing = await store.get(id);
964
+ if (!existing) {
965
+ return scimErrorResponse(404, `User ${id} not found`, "invalidValue");
966
+ }
967
+ await store.delete(id);
968
+ return new Response(null, { status: 204 });
969
+ };
970
+ users_inbound = {
971
+ handler
972
+ };
973
+ }
974
+ async function topLevelHandler(request, handlerConfig) {
975
+ const urlObj = new URL(request.url);
976
+ const path = urlObj.pathname;
977
+ const usersUrl = handlerConfig?.usersUrl ?? config.usersUrl ?? "/api/iam/Users";
978
+ const groupsUrl = handlerConfig?.groupsUrl ?? config.groupsUrl ?? "/api/iam/Groups";
979
+ if (path.startsWith(usersUrl) && users_inbound) {
980
+ return users_inbound.handler(request, { basePath: usersUrl });
981
+ }
982
+ if (path.startsWith(groupsUrl) && groups_inbound) {
983
+ return groups_inbound.handler(request, { basePath: groupsUrl });
984
+ }
985
+ return scimErrorResponse(404, "Resource not found");
986
+ }
987
+ return {
988
+ ...config,
989
+ createUser,
990
+ getBaseUrl,
991
+ groups_outbound,
992
+ groups_inbound,
993
+ users_inbound,
994
+ handler: topLevelHandler
995
+ };
996
+ }
997
+
998
+ // packages/react/src/types/oidc-schema.ts
999
+ function oidcCallbackSchema(vendor) {
1000
+ return {
1001
+ "~standard": {
1002
+ version: 1,
1003
+ vendor,
1004
+ validate: (value) => {
1005
+ if (typeof value !== "object" || value === null) {
1006
+ return {
1007
+ issues: [
1008
+ {
1009
+ message: "Expected an object"
1010
+ }
1011
+ ]
1012
+ };
1013
+ }
1014
+ const params = value;
1015
+ const issues = [];
1016
+ const result = {};
1017
+ if ("code" in params) {
1018
+ if (typeof params.code === "string") {
1019
+ result.code = params.code;
1020
+ } else {
1021
+ issues.push({
1022
+ message: "code must be a string",
1023
+ path: ["code"]
1024
+ });
1025
+ }
1026
+ } else if (!("error" in params)) {
1027
+ issues.push({
1028
+ message: "code is required",
1029
+ path: ["code"]
1030
+ });
1031
+ }
1032
+ if ("state" in params) {
1033
+ if (typeof params.state === "string" || params.state === undefined) {
1034
+ result.state = params.state;
1035
+ } else {
1036
+ issues.push({
1037
+ message: "state must be a string",
1038
+ path: ["state"]
1039
+ });
1040
+ }
1041
+ }
1042
+ if ("session_state" in params) {
1043
+ if (typeof params.session_state === "string" || params.session_state === undefined) {
1044
+ result.session_state = params.session_state;
1045
+ } else {
1046
+ issues.push({
1047
+ message: "session_state must be a string",
1048
+ path: ["session_state"]
1049
+ });
1050
+ }
1051
+ }
1052
+ if ("error" in params) {
1053
+ if (typeof params.error === "string") {
1054
+ result.error = params.error;
1055
+ } else {
1056
+ issues.push({
1057
+ message: "error must be a string",
1058
+ path: ["error"]
1059
+ });
1060
+ }
1061
+ if ("error_description" in params) {
1062
+ if (typeof params.error_description === "string" || params.error_description === undefined) {
1063
+ result.error_description = params.error_description;
1064
+ } else {
1065
+ issues.push({
1066
+ message: "error_description must be a string",
1067
+ path: ["error_description"]
1068
+ });
1069
+ }
1070
+ }
1071
+ if ("error_uri" in params) {
1072
+ if (typeof params.error_uri === "string" || params.error_uri === undefined) {
1073
+ result.error_uri = params.error_uri;
1074
+ } else {
1075
+ issues.push({
1076
+ message: "error_uri must be a string",
1077
+ path: ["error_uri"]
1078
+ });
1079
+ }
1080
+ }
1081
+ }
1082
+ if ("iss" in params) {
1083
+ if (typeof params.iss === "string" || params.iss === undefined) {
1084
+ result.iss = params.iss;
1085
+ } else {
1086
+ issues.push({
1087
+ message: "iss must be a string",
1088
+ path: ["iss"]
1089
+ });
1090
+ }
1091
+ }
1092
+ if (issues.length > 0) {
1093
+ return { issues };
1094
+ }
1095
+ return { value: result };
1096
+ }
1097
+ }
1098
+ };
1099
+ }
1100
+ function tokenResponseSchema(vendor) {
1101
+ return {
1102
+ "~standard": {
1103
+ version: 1,
1104
+ vendor,
1105
+ validate: (value) => {
1106
+ if (typeof value !== "object" || value === null) {
1107
+ return {
1108
+ issues: [
1109
+ {
1110
+ message: "Expected an object"
1111
+ }
1112
+ ]
1113
+ };
1114
+ }
1115
+ const response = value;
1116
+ const issues = [];
1117
+ const result = {};
1118
+ if ("access_token" in response) {
1119
+ if (typeof response.access_token === "string") {
1120
+ result.access_token = response.access_token;
1121
+ } else {
1122
+ issues.push({
1123
+ message: "access_token must be a string",
1124
+ path: ["access_token"]
1125
+ });
1126
+ }
1127
+ } else {
1128
+ issues.push({
1129
+ message: "access_token is required",
1130
+ path: ["access_token"]
1131
+ });
1132
+ }
1133
+ if ("id_token" in response) {
1134
+ if (typeof response.id_token === "string") {
1135
+ result.id_token = response.id_token;
1136
+ } else {
1137
+ issues.push({
1138
+ message: "id_token must be a string",
1139
+ path: ["id_token"]
1140
+ });
1141
+ }
1142
+ } else {
1143
+ issues.push({
1144
+ message: "id_token is required",
1145
+ path: ["id_token"]
1146
+ });
1147
+ }
1148
+ if ("token_type" in response) {
1149
+ if (typeof response.token_type === "string") {
1150
+ result.token_type = response.token_type;
1151
+ } else {
1152
+ issues.push({
1153
+ message: "token_type must be a string",
1154
+ path: ["token_type"]
1155
+ });
1156
+ }
1157
+ } else {
1158
+ issues.push({
1159
+ message: "token_type is required",
1160
+ path: ["token_type"]
1161
+ });
1162
+ }
1163
+ if ("refresh_token" in response) {
1164
+ if (typeof response.refresh_token === "string" || response.refresh_token === undefined) {
1165
+ result.refresh_token = response.refresh_token;
1166
+ } else {
1167
+ issues.push({
1168
+ message: "refresh_token must be a string",
1169
+ path: ["refresh_token"]
1170
+ });
1171
+ }
1172
+ }
1173
+ if ("scope" in response) {
1174
+ if (typeof response.scope === "string" || response.scope === undefined) {
1175
+ result.scope = response.scope;
1176
+ } else {
1177
+ issues.push({
1178
+ message: "scope must be a string",
1179
+ path: ["scope"]
1180
+ });
1181
+ }
1182
+ }
1183
+ if ("session_state" in response) {
1184
+ if (typeof response.session_state === "string" || response.session_state === undefined) {
1185
+ result.session_state = response.session_state;
1186
+ } else {
1187
+ issues.push({
1188
+ message: "session_state must be a string",
1189
+ path: ["session_state"]
1190
+ });
1191
+ }
1192
+ }
1193
+ if ("expires" in response) {
1194
+ if (typeof response.expires === "string" || response.expires === undefined) {
1195
+ result.expires = response.expires;
1196
+ } else {
1197
+ issues.push({
1198
+ message: "expires must be a string",
1199
+ path: ["expires"]
1200
+ });
1201
+ }
1202
+ }
1203
+ if ("expires_in" in response) {
1204
+ if (typeof response.expires_in === "number" || response.expires_in === undefined) {
1205
+ result.expires_in = response.expires_in;
1206
+ } else {
1207
+ issues.push({
1208
+ message: "expires_in must be a number",
1209
+ path: ["expires_in"]
1210
+ });
1211
+ }
1212
+ }
1213
+ if ("refresh_expires_in" in response) {
1214
+ if (typeof response.refresh_expires_in === "number" || response.refresh_expires_in === undefined) {
1215
+ result.refresh_expires_in = response.refresh_expires_in;
1216
+ } else {
1217
+ issues.push({
1218
+ message: "refresh_expires_in must be a number",
1219
+ path: ["refresh_expires_in"]
1220
+ });
1221
+ }
1222
+ }
1223
+ if (issues.length > 0) {
1224
+ return { issues };
1225
+ }
1226
+ return { value: result };
1227
+ }
1228
+ }
1229
+ };
1230
+ }
1231
+ function idTokenClaimsSchema(vendor) {
1232
+ return {
1233
+ "~standard": {
1234
+ version: 1,
1235
+ vendor,
1236
+ validate: (value) => {
1237
+ if (typeof value !== "object" || value === null) {
1238
+ return {
1239
+ issues: [
1240
+ {
1241
+ message: "Expected an object"
1242
+ }
1243
+ ]
1244
+ };
1245
+ }
1246
+ const claims = value;
1247
+ const issues = [];
1248
+ const result = { ...claims };
1249
+ const stringFields = ["iss", "aud", "sub", "sid", "name", "email", "preferred_username", "picture"];
1250
+ for (const field of stringFields) {
1251
+ if (field in claims && claims[field] !== undefined) {
1252
+ if (typeof claims[field] !== "string") {
1253
+ issues.push({
1254
+ message: `${field} must be a string`,
1255
+ path: [field]
1256
+ });
1257
+ }
1258
+ }
1259
+ }
1260
+ const numberFields = ["exp", "iat"];
1261
+ for (const field of numberFields) {
1262
+ if (field in claims && claims[field] !== undefined) {
1263
+ if (typeof claims[field] !== "number") {
1264
+ issues.push({
1265
+ message: `${field} must be a number`,
1266
+ path: [field]
1267
+ });
1268
+ }
1269
+ }
1270
+ }
1271
+ if (issues.length > 0) {
1272
+ return { issues };
1273
+ }
1274
+ return { value: result };
1275
+ }
1276
+ }
1277
+ };
1278
+ }
1279
+
1280
+ // packages/react/src/utils.ts
1281
+ var defaultInstance;
1282
+ function must(value, message = "Assertion failed. Required value is null or undefined.") {
1283
+ if (value === undefined || value === null) {
1284
+ throw new Error(message);
1285
+ }
1286
+ return value;
1287
+ }
1288
+ function setDefaultInstance(es) {
1289
+ defaultInstance = es;
1290
+ }
1291
+ function getDefaultInstance() {
1292
+ return defaultInstance;
1293
+ }
1294
+ function getES(es) {
1295
+ if (es)
1296
+ return es;
1297
+ if (defaultInstance)
1298
+ return defaultInstance;
1299
+ throw new Error(`TODO standardize the error message when there isn't a default EntepriseStandard`);
1300
+ }
1301
+
1302
+ // packages/react/src/sso.ts
1303
+ var jwksCache = new Map;
1304
+ function sso(config) {
1305
+ let configWithDefaults;
1306
+ const handlerDefaults = {
1307
+ loginUrl: config?.loginUrl,
1308
+ userUrl: config?.userUrl,
1309
+ errorUrl: config?.errorUrl,
1310
+ landingUrl: config?.landingUrl,
1311
+ tokenUrl: config?.tokenUrl,
1312
+ refreshUrl: config?.refreshUrl,
1313
+ jwksUrl: config?.jwksUrl,
1314
+ logoutUrl: config?.logoutUrl,
1315
+ logoutBackChannelUrl: config?.logoutBackChannelUrl,
1316
+ validation: config?.validation
1317
+ };
1318
+ configWithDefaults = !config ? undefined : {
1319
+ ...config,
1320
+ authority: must(config.authority, "Missing 'authority' from SSO Config"),
1321
+ token_url: must(config.token_url, "Missing 'token_url' from SSO Config"),
1322
+ authorization_url: must(config.authorization_url, "Missing 'authorization_url' from SSO Config"),
1323
+ client_id: must(config.client_id, "Missing 'client_id' from SSO Config"),
1324
+ redirect_uri: must(config.redirect_uri, "Missing 'redirect_uri' from SSO Config"),
1325
+ scope: must(config.scope, "Missing 'scope' from SSO Config"),
1326
+ response_type: config.response_type ?? "code",
1327
+ cookies_secure: config.cookies_secure !== undefined ? config.cookies_secure : true,
1328
+ cookies_same_site: config.cookies_same_site !== undefined ? config.cookies_same_site : "Strict",
1329
+ cookies_prefix: config.cookies_prefix ?? `es.sso.${config.client_id}`,
1330
+ cookies_path: config.cookies_path ?? "/"
1331
+ };
1332
+ async function getUser(request) {
1333
+ if (!configWithDefaults) {
1334
+ throw new Error("Enterprise Standard SSO Manager not initialized");
1335
+ }
1336
+ try {
1337
+ const { tokens } = await getTokenFromCookies(request);
1338
+ if (!tokens)
1339
+ return;
1340
+ return await parseUser(tokens);
1341
+ } catch (error) {
1342
+ console.error("Error parsing user from cookies:", error);
1343
+ return;
1344
+ }
1345
+ }
1346
+ async function getRequiredUser(request) {
1347
+ const user = await getUser(request);
1348
+ if (user)
1349
+ return user;
1350
+ throw new Response("Unauthorized", {
1351
+ status: 401,
1352
+ statusText: "Unauthorized"
1353
+ });
1354
+ }
1355
+ async function initiateLogin({ landingUrl, errorUrl }, requestUrl) {
1356
+ if (!configWithDefaults) {
1357
+ throw new Error("Enterprise Standard SSO Manager not initialized");
1358
+ }
1359
+ const state = generateRandomString();
1360
+ const codeVerifier = generateRandomString(64);
1361
+ let normalizedRedirectUri = configWithDefaults.redirect_uri;
1362
+ try {
1363
+ new URL(normalizedRedirectUri);
1364
+ } catch {
1365
+ if (requestUrl) {
1366
+ try {
1367
+ const baseUrl = new URL(requestUrl);
1368
+ const path = normalizedRedirectUri.startsWith("//") ? normalizedRedirectUri.slice(1) : normalizedRedirectUri.startsWith("/") ? normalizedRedirectUri : `/${normalizedRedirectUri}`;
1369
+ normalizedRedirectUri = new URL(path, baseUrl.origin).toString();
1370
+ } catch {
1371
+ try {
1372
+ const authUrl = new URL(configWithDefaults.authorization_url);
1373
+ const path = normalizedRedirectUri.startsWith("//") ? normalizedRedirectUri.slice(1) : normalizedRedirectUri.startsWith("/") ? normalizedRedirectUri : `/${normalizedRedirectUri}`;
1374
+ normalizedRedirectUri = new URL(path, authUrl.origin).toString();
1375
+ } catch {
1376
+ throw new Error(`Invalid redirect_uri: "${configWithDefaults.redirect_uri}". It must be a valid absolute URL.`);
1377
+ }
1378
+ }
1379
+ }
1380
+ }
1381
+ const url = new URL(configWithDefaults.authorization_url);
1382
+ url.searchParams.append("client_id", configWithDefaults.client_id);
1383
+ url.searchParams.append("redirect_uri", normalizedRedirectUri);
1384
+ url.searchParams.append("response_type", "code");
1385
+ url.searchParams.append("scope", configWithDefaults.scope);
1386
+ url.searchParams.append("state", state);
1387
+ const codeChallenge = await pkceChallengeFromVerifier(codeVerifier);
1388
+ url.searchParams.append("code_challenge", codeChallenge);
1389
+ url.searchParams.append("code_challenge_method", "S256");
1390
+ const val = {
1391
+ state,
1392
+ codeVerifier,
1393
+ landingUrl,
1394
+ errorUrl
1395
+ };
1396
+ return new Response("Redirecting to SSO Provider", {
1397
+ status: 302,
1398
+ headers: {
1399
+ Location: url.toString(),
1400
+ "Set-Cookie": createCookie("state", val, 86400)
1401
+ }
1402
+ });
1403
+ }
1404
+ async function logout(request, _config) {
1405
+ if (!configWithDefaults) {
1406
+ throw new Error("Enterprise Standard SSO Manager not initialized");
1407
+ }
1408
+ try {
1409
+ const refreshToken2 = getCookie("refresh", request);
1410
+ if (refreshToken2) {
1411
+ await revokeToken(refreshToken2);
1412
+ }
1413
+ } catch (error) {
1414
+ console.warn("Failed to revoke token:", error);
1415
+ }
1416
+ if (configWithDefaults.session_store) {
1417
+ try {
1418
+ const user = await getUser(request);
1419
+ if (user?.sso?.profile.sid) {
1420
+ const sid = user.sso.profile.sid;
1421
+ await configWithDefaults.session_store.delete(sid);
1422
+ console.log(`Session ${sid} deleted from store`);
1423
+ }
1424
+ } catch (error) {
1425
+ console.warn("Failed to delete session:", error);
1426
+ }
1427
+ }
1428
+ const clearHeaders = [
1429
+ ["Set-Cookie", clearCookie("access")],
1430
+ ["Set-Cookie", clearCookie("id")],
1431
+ ["Set-Cookie", clearCookie("refresh")],
1432
+ ["Set-Cookie", clearCookie("control")],
1433
+ ["Set-Cookie", clearCookie("state")]
1434
+ ];
1435
+ const url = new URL(request.url);
1436
+ const redirectTo = url.searchParams.get("redirect");
1437
+ if (redirectTo) {
1438
+ return new Response("Logged out", {
1439
+ status: 302,
1440
+ headers: [["Location", redirectTo], ...clearHeaders]
1441
+ });
1442
+ }
1443
+ const accept = request.headers.get("accept");
1444
+ const isAjax = accept?.includes("application/json") || accept?.includes("text/javascript");
1445
+ if (isAjax) {
1446
+ return new Response(JSON.stringify({ success: true, message: "Logged out" }), {
1447
+ status: 200,
1448
+ headers: [["Content-Type", "application/json"], ...clearHeaders]
1449
+ });
1450
+ } else {
1451
+ return new Response(`
1452
+ <!DOCTYPE html><html lang="en"><body>
1453
+ <h1>Logout Complete</h1>
1454
+ <div style="display: none">
1455
+ It is not recommended to show the default logout page. Include '?redirect=/someHomePage' or logout asynchronously.
1456
+ Check the <a href="https://EnterpriseStandard.com/sso#logout">Enterprise Standard Packages</a> for more information.
1457
+ </div>
1458
+ </body></html>
1459
+ `, {
1460
+ status: 200,
1461
+ headers: [["Content-Type", "text/html"], ...clearHeaders]
1462
+ });
1463
+ }
1464
+ }
1465
+ async function logoutBackChannel(request) {
1466
+ if (!configWithDefaults) {
1467
+ throw new Error("Enterprise Standard SSO Manager not initialized");
1468
+ }
1469
+ if (!configWithDefaults.session_store) {
1470
+ throw new Error("Back-Channel Logout requires session_store configuration");
1471
+ }
1472
+ try {
1473
+ const contentType = request.headers.get("content-type");
1474
+ if (!contentType || !contentType.includes("application/x-www-form-urlencoded")) {
1475
+ return new Response("Invalid Content-Type, expected application/x-www-form-urlencoded", {
1476
+ status: 400
1477
+ });
1478
+ }
1479
+ const body = await request.text();
1480
+ const params = new URLSearchParams(body);
1481
+ const logoutToken = params.get("logout_token");
1482
+ if (!logoutToken) {
1483
+ return new Response("Missing logout_token parameter", { status: 400 });
1484
+ }
1485
+ const claims = await parseJwt(logoutToken);
1486
+ const sid = claims.sid;
1487
+ if (!sid) {
1488
+ console.warn("Back-Channel Logout: logout_token missing sid claim");
1489
+ return new Response("Invalid logout_token: missing sid claim", { status: 400 });
1490
+ }
1491
+ await configWithDefaults.session_store.delete(sid);
1492
+ console.log(`Back-Channel Logout: successfully deleted session ${sid}`);
1493
+ return new Response("OK", { status: 200 });
1494
+ } catch (error) {
1495
+ console.error("Error during back-channel logout:", error);
1496
+ return new Response("Internal Server Error", { status: 500 });
1497
+ }
1498
+ }
1499
+ async function callbackHandler(request, validation) {
1500
+ if (!configWithDefaults) {
1501
+ throw new Error("Enterprise Standard SSO Manager not initialized");
1502
+ }
1503
+ const url = new URL(request.url);
1504
+ const params = new URLSearchParams(url.search);
1505
+ const callbackParamsValidator = validation?.callbackParams ?? oidcCallbackSchema("builtin");
1506
+ const paramsObject = Object.fromEntries(params.entries());
1507
+ const paramsResult = await callbackParamsValidator["~standard"].validate(paramsObject);
1508
+ if ("issues" in paramsResult) {
1509
+ return new Response(JSON.stringify({
1510
+ error: "validation_failed",
1511
+ message: "OIDC callback parameters validation failed",
1512
+ issues: paramsResult.issues?.map((i) => ({
1513
+ path: i.path?.join("."),
1514
+ message: i.message
1515
+ }))
1516
+ }), {
1517
+ status: 400,
1518
+ headers: { "Content-Type": "application/json" }
1519
+ });
1520
+ }
1521
+ const { code: codeFromUrl, state: stateFromUrl } = paramsResult.value;
1522
+ try {
1523
+ const cookie = getCookie("state", request, true);
1524
+ const { codeVerifier, state, landingUrl } = cookie ?? {};
1525
+ must(codeVerifier, 'OIDC "codeVerifier" was not present in cookies, ensure that the SSO login was initiated correctly');
1526
+ must(state, 'OIDC "stateVerifier" was not present in cookies, ensure that the SSO login was initiated correctly');
1527
+ must(landingUrl, 'OIDC "landingUrl" was not present in cookies');
1528
+ if (stateFromUrl !== state) {
1529
+ throw new Error('SSO State Verifier failed, the "state" request parameter does not equal the "state" in the SSO cookie');
1530
+ }
1531
+ const tokenResponse = await exchangeCodeForToken(codeFromUrl, codeVerifier, validation, request.url);
1532
+ const user = await parseUser(tokenResponse, validation);
1533
+ if (configWithDefaults.session_store) {
1534
+ try {
1535
+ const sid = user.sso.profile.sid;
1536
+ const sub = user.id;
1537
+ if (sid && sub) {
1538
+ const session = {
1539
+ sid,
1540
+ sub,
1541
+ createdAt: new Date,
1542
+ lastActivityAt: new Date
1543
+ };
1544
+ await configWithDefaults.session_store.create(session);
1545
+ } else {
1546
+ console.warn("Session creation skipped: missing sid or sub in ID token claims");
1547
+ }
1548
+ } catch (error) {
1549
+ console.warn("Failed to create session:", error);
1550
+ }
1551
+ }
1552
+ if (configWithDefaults.user_store) {
1553
+ try {
1554
+ const sub = user.id;
1555
+ if (sub) {
1556
+ const now = new Date;
1557
+ const existingUser = await configWithDefaults.user_store.get(sub);
1558
+ if (existingUser || configWithDefaults.enable_jit_user_provisioning) {
1559
+ const storedUser = {
1560
+ ...existingUser ?? {},
1561
+ ...user,
1562
+ id: sub,
1563
+ createdAt: existingUser?.createdAt ?? now,
1564
+ updatedAt: now
1565
+ };
1566
+ await configWithDefaults.user_store.upsert(storedUser);
1567
+ } else {
1568
+ console.warn("JIT user provisioning disabled: user not found in store and will not be created");
1569
+ }
1570
+ } else {
1571
+ console.warn("User storage skipped: missing sub in ID token claims");
1572
+ }
1573
+ } catch (error) {
1574
+ console.warn("Failed to store user:", error);
1575
+ }
1576
+ }
1577
+ return new Response("Authentication successful, redirecting", {
1578
+ status: 302,
1579
+ headers: [
1580
+ ["Location", landingUrl],
1581
+ ["Set-Cookie", clearCookie("state")],
1582
+ ...createJwtCookies(tokenResponse, user.sso.expires)
1583
+ ]
1584
+ });
1585
+ } catch (error) {
1586
+ console.error("Error during sign-in callback:", error);
1587
+ try {
1588
+ const cookie = getCookie("state", request, true);
1589
+ const { errorUrl } = cookie ?? {};
1590
+ if (errorUrl) {
1591
+ return new Response("Redirecting to error url", {
1592
+ status: 302,
1593
+ headers: [["Location", errorUrl]]
1594
+ });
1595
+ }
1596
+ } catch (_err) {
1597
+ console.warn("Error parsing the errorUrl from the OIDC cookie");
1598
+ }
1599
+ console.warn("No error page was found in the cookies. The user will be shown a default error page.");
1600
+ return new Response("An error occurred during authentication, please return to the application homepage and try again.", {
1601
+ status: 500
1602
+ });
1603
+ }
1604
+ }
1605
+ async function parseUser(token, validation) {
1606
+ if (!configWithDefaults) {
1607
+ throw new Error("Enterprise Standard SSO Manager not initialized");
1608
+ }
1609
+ const idToken = await parseJwt(token.id_token, validation);
1610
+ const expiresIn = Number(token.refresh_expires_in ?? token.expires_in ?? 3600);
1611
+ const expires = token.expires ? new Date(token.expires) : new Date(Date.now() + expiresIn * 1000);
1612
+ return {
1613
+ id: idToken.sub,
1614
+ userName: idToken.preferred_username || "",
1615
+ name: idToken.name || "",
1616
+ email: idToken.email || "",
1617
+ emails: [
1618
+ {
1619
+ value: idToken.email || "",
1620
+ primary: true
1621
+ }
1622
+ ],
1623
+ avatarUrl: idToken.picture,
1624
+ sso: {
1625
+ profile: {
1626
+ ...idToken,
1627
+ iss: idToken.iss || configWithDefaults.authority,
1628
+ aud: idToken.aud || configWithDefaults.client_id
1629
+ },
1630
+ tenant: {
1631
+ id: idToken.idp || idToken.iss || configWithDefaults.authority,
1632
+ name: idToken.iss || configWithDefaults.authority
1633
+ },
1634
+ scope: token.scope,
1635
+ tokenType: token.token_type,
1636
+ sessionState: token.session_state,
1637
+ expires
1638
+ }
1639
+ };
1640
+ }
1641
+ async function exchangeCodeForToken(code, codeVerifier, validation, requestUrl) {
1642
+ if (!configWithDefaults) {
1643
+ throw new Error("Enterprise Standard SSO Manager not initialized");
1644
+ }
1645
+ const tokenUrl = configWithDefaults.token_url;
1646
+ let normalizedRedirectUri = configWithDefaults.redirect_uri;
1647
+ try {
1648
+ new URL(normalizedRedirectUri);
1649
+ } catch {
1650
+ if (requestUrl) {
1651
+ try {
1652
+ const baseUrl = new URL(requestUrl);
1653
+ const path = normalizedRedirectUri.startsWith("//") ? normalizedRedirectUri.slice(1) : normalizedRedirectUri.startsWith("/") ? normalizedRedirectUri : `/${normalizedRedirectUri}`;
1654
+ normalizedRedirectUri = new URL(path, baseUrl.origin).toString();
1655
+ } catch {
1656
+ try {
1657
+ const tokenUrlObj = new URL(tokenUrl);
1658
+ const path = normalizedRedirectUri.startsWith("//") ? normalizedRedirectUri.slice(1) : normalizedRedirectUri.startsWith("/") ? normalizedRedirectUri : `/${normalizedRedirectUri}`;
1659
+ normalizedRedirectUri = new URL(path, tokenUrlObj.origin).toString();
1660
+ } catch {
1661
+ throw new Error(`Invalid redirect_uri: "${configWithDefaults.redirect_uri}". It must be a valid absolute URL.`);
1662
+ }
1663
+ }
1664
+ }
1665
+ }
1666
+ const body = new URLSearchParams;
1667
+ body.append("grant_type", "authorization_code");
1668
+ body.append("code", code);
1669
+ body.append("redirect_uri", normalizedRedirectUri);
1670
+ body.append("client_id", configWithDefaults.client_id);
1671
+ if (configWithDefaults.client_secret) {
1672
+ body.append("client_secret", configWithDefaults.client_secret);
1673
+ }
1674
+ body.append("code_verifier", codeVerifier);
1675
+ try {
1676
+ const response = await fetch(tokenUrl, {
1677
+ method: "POST",
1678
+ headers: {
1679
+ "Content-Type": "application/x-www-form-urlencoded",
1680
+ Accept: "application/json"
1681
+ },
1682
+ body: body.toString()
1683
+ });
1684
+ const data = await response.json();
1685
+ if (!response.ok) {
1686
+ console.error("Token exchange error:", data);
1687
+ throw new Error(`Token exchange failed: ${data.error || response.statusText} - ${data.error_description || ""}`.trim());
1688
+ }
1689
+ const tokenResponseValidator = validation?.tokenResponse ?? tokenResponseSchema("builtin");
1690
+ const tokenResult = await tokenResponseValidator["~standard"].validate(data);
1691
+ if ("issues" in tokenResult) {
1692
+ console.error("Token response validation failed:", tokenResult.issues);
1693
+ throw new Error(`Token response validation failed: ${tokenResult.issues?.map((i) => i.message).join("; ")}`);
1694
+ }
1695
+ return tokenResult.value;
1696
+ } catch (error) {
1697
+ console.error("Error during token exchange:", error);
1698
+ throw error;
1699
+ }
1700
+ }
1701
+ async function refreshToken(refreshToken2) {
1702
+ return retryWithBackoff(async () => {
1703
+ if (!configWithDefaults) {
1704
+ throw new Error("Enterprise Standard SSO Manager not initialized");
1705
+ }
1706
+ const tokenUrl = configWithDefaults.token_url;
1707
+ const body = new URLSearchParams;
1708
+ body.append("grant_type", "refresh_token");
1709
+ body.append("refresh_token", refreshToken2);
1710
+ body.append("client_id", configWithDefaults.client_id);
1711
+ const response = await fetch(tokenUrl, {
1712
+ method: "POST",
1713
+ headers: {
1714
+ "Content-Type": "application/x-www-form-urlencoded",
1715
+ Accept: "application/json"
1716
+ },
1717
+ body: body.toString()
1718
+ });
1719
+ const data = await response.json();
1720
+ if (!response.ok) {
1721
+ console.error("Token refresh error:", data);
1722
+ throw new Error(`Token refresh failed: ${data.error || response.statusText} - ${data.error_description || ""}`.trim());
1723
+ }
1724
+ return data;
1725
+ });
1726
+ }
1727
+ async function revokeToken(token) {
1728
+ try {
1729
+ if (!configWithDefaults) {
1730
+ throw new Error("Enterprise Standard SSO Manager not initialized");
1731
+ }
1732
+ if (!configWithDefaults.revocation_endpoint) {
1733
+ return;
1734
+ }
1735
+ const body = new URLSearchParams;
1736
+ body.append("token", token);
1737
+ body.append("token_type_hint", "refresh_token");
1738
+ body.append("client_id", configWithDefaults.client_id);
1739
+ const response = await fetch(configWithDefaults.revocation_endpoint, {
1740
+ method: "POST",
1741
+ headers: {
1742
+ "Content-Type": "application/x-www-form-urlencoded"
1743
+ },
1744
+ body: body.toString()
1745
+ });
1746
+ if (!response.ok) {
1747
+ console.warn("Token revocation failed:", response.status, response.statusText);
1748
+ } else {
1749
+ console.log("Token revoked successfully");
1750
+ }
1751
+ } catch (error) {
1752
+ console.warn("Error revoking token:", error);
1753
+ }
1754
+ }
1755
+ async function fetchJwks() {
1756
+ if (!configWithDefaults) {
1757
+ throw new Error("Enterprise Standard SSO Manager not initialized");
1758
+ }
1759
+ const url = configWithDefaults.jwks_uri || `${configWithDefaults.authority}/protocol/openid-connect/certs`;
1760
+ const cached = jwksCache.get(url);
1761
+ if (cached)
1762
+ return cached;
1763
+ return retryWithBackoff(async () => {
1764
+ if (!configWithDefaults)
1765
+ throw new Error("SSO Manager not initialized");
1766
+ const response = await fetch(url);
1767
+ if (!response.ok)
1768
+ throw new Error("Failed to fetch JWKS");
1769
+ const jwks = await response.json();
1770
+ jwksCache.set(url, jwks);
1771
+ return jwks;
1772
+ });
1773
+ }
1774
+ async function retryWithBackoff(operation, maxRetries = 3, baseDelay = 1000, maxDelay = 30000) {
1775
+ let lastError = new Error("Placeholder Error");
1776
+ for (let attempt = 0;attempt <= maxRetries; attempt++) {
1777
+ try {
1778
+ return await operation();
1779
+ } catch (error) {
1780
+ lastError = error instanceof Error ? error : new Error(String(error));
1781
+ if (error instanceof Error && error.message.includes("400")) {
1782
+ throw error;
1783
+ }
1784
+ if (attempt === maxRetries) {
1785
+ throw lastError;
1786
+ }
1787
+ const delay = Math.min(baseDelay * 2 ** attempt, maxDelay);
1788
+ const jitter = Math.random() * 0.1 * delay;
1789
+ await new Promise((resolve) => setTimeout(resolve, delay + jitter));
1790
+ console.warn(`Retry attempt ${attempt + 1} after ${delay + jitter}ms delay`);
1791
+ }
1792
+ }
1793
+ throw lastError;
1794
+ }
1795
+ async function parseJwt(token, validation) {
1796
+ try {
1797
+ const parts = token.split(".");
1798
+ if (parts.length !== 3)
1799
+ throw new Error("Invalid JWT");
1800
+ const header = JSON.parse(atob(parts[0].replace(/-/g, "+").replace(/_/g, "/")));
1801
+ const payload = JSON.parse(atob(parts[1].replace(/-/g, "+").replace(/_/g, "/")));
1802
+ const signature = parts[2].replace(/-/g, "+").replace(/_/g, "/");
1803
+ const publicKey = await getPublicKey(header.kid);
1804
+ const encoder = new TextEncoder;
1805
+ const data = encoder.encode(`${parts[0]}.${parts[1]}`);
1806
+ const isValid = await crypto.subtle.verify("RSASSA-PKCS1-v1_5", publicKey, Uint8Array.from(atob(signature), (c) => c.charCodeAt(0)), data);
1807
+ if (!isValid)
1808
+ throw new Error("Invalid JWT signature");
1809
+ const idTokenClaimsValidator = validation?.idTokenClaims ?? idTokenClaimsSchema("builtin");
1810
+ const claimsResult = await idTokenClaimsValidator["~standard"].validate(payload);
1811
+ if ("issues" in claimsResult) {
1812
+ console.error("ID token claims validation failed:", claimsResult.issues);
1813
+ throw new Error(`ID token claims validation failed: ${claimsResult.issues?.map((i) => i.message).join("; ")}`);
1814
+ }
1815
+ return claimsResult.value;
1816
+ } catch (e) {
1817
+ console.error("Error verifying JWT:", e);
1818
+ throw e;
1819
+ }
1820
+ }
1821
+ function generateRandomString(length = 32) {
1822
+ const array = new Uint8Array(length);
1823
+ crypto.getRandomValues(array);
1824
+ return Array.from(array, (byte) => byte.toString(16).padStart(2, "0")).join("").substring(0, length);
1825
+ }
1826
+ async function pkceChallengeFromVerifier(verifier) {
1827
+ const encoder = new TextEncoder;
1828
+ const data = encoder.encode(verifier);
1829
+ const hashBuffer = await crypto.subtle.digest("SHA-256", data);
1830
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
1831
+ const hashBase64 = btoa(String.fromCharCode(...hashArray)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
1832
+ return hashBase64;
1833
+ }
1834
+ async function getPublicKey(kid) {
1835
+ const jwks = await fetchJwks();
1836
+ const key = jwks.keys.find((k) => k.kid === kid);
1837
+ if (!key)
1838
+ throw new Error("Public key not found");
1839
+ const publicKey = await crypto.subtle.importKey("jwk", {
1840
+ kty: key.kty,
1841
+ n: key.n,
1842
+ e: key.e
1843
+ }, { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" }, false, ["verify"]);
1844
+ return publicKey;
1845
+ }
1846
+ function createJwtCookies(token, expires) {
1847
+ const control = {
1848
+ expires_in: token.expires_in,
1849
+ refresh_expires_in: token.refresh_expires_in,
1850
+ scope: token.scope,
1851
+ session_state: token.session_state,
1852
+ token_type: token.token_type,
1853
+ expires: expires.toISOString()
1854
+ };
1855
+ return [
1856
+ ["Set-Cookie", createCookie("access", token.access_token, expires)],
1857
+ ["Set-Cookie", createCookie("id", token.id_token, expires)],
1858
+ ["Set-Cookie", createCookie("refresh", token.refresh_token ?? "", expires)],
1859
+ ["Set-Cookie", createCookie("control", control, expires)]
1860
+ ];
1861
+ }
1862
+ async function getTokenFromCookies(req) {
1863
+ const access_token = getCookie("access", req);
1864
+ const id_token = getCookie("id", req);
1865
+ const refresh_token = getCookie("refresh", req);
1866
+ const control = getCookie("control", req, true);
1867
+ if (!access_token || !id_token || !refresh_token || !control) {
1868
+ return { tokens: undefined, refreshHeaders: [] };
1869
+ }
1870
+ let tokenResponse = {
1871
+ access_token,
1872
+ id_token,
1873
+ refresh_token,
1874
+ ...control
1875
+ };
1876
+ if (control.expires && refresh_token && Date.now() > new Date(control.expires).getTime()) {
1877
+ tokenResponse = await refreshToken(refresh_token);
1878
+ const user = await parseUser(tokenResponse);
1879
+ const refreshHeaders = createJwtCookies(tokenResponse, user.sso.expires);
1880
+ return { tokens: tokenResponse, refreshHeaders };
1881
+ }
1882
+ return { tokens: tokenResponse, refreshHeaders: [] };
1883
+ }
1884
+ async function getJwt(request) {
1885
+ const { tokens } = await getTokenFromCookies(request);
1886
+ if (!tokens)
1887
+ return;
1888
+ return tokens.access_token;
1889
+ }
1890
+ function createCookie(name, value, expires) {
1891
+ if (!configWithDefaults) {
1892
+ throw new Error("Enterprise Standard SSO Manager not initialized");
1893
+ }
1894
+ name = `${configWithDefaults.cookies_prefix}.${name}`;
1895
+ if (typeof value !== "string") {
1896
+ value = btoa(JSON.stringify(value));
1897
+ }
1898
+ let exp;
1899
+ if (expires instanceof Date) {
1900
+ exp = `Expires=${expires.toUTCString()}`;
1901
+ } else if (typeof expires === "number") {
1902
+ exp = `Max-Age=${expires}`;
1903
+ } else {
1904
+ throw new Error("Invalid expires type", expires);
1905
+ }
1906
+ if (value.length > 4000) {
1907
+ throw new Error(`Error setting cookie: ${name}. Cookie length is: ${value.length}`);
1908
+ }
1909
+ return `${name}=${value}; ${exp}; Path=${configWithDefaults.cookies_path}; HttpOnly;${configWithDefaults.cookies_secure ? " Secure;" : ""} SameSite=${configWithDefaults.cookies_same_site};`;
1910
+ }
1911
+ function clearCookie(name) {
1912
+ if (!configWithDefaults) {
1913
+ throw new Error("Enterprise Standard SSO Manager not initialized");
1914
+ }
1915
+ return `${configWithDefaults.cookies_prefix}.${name}=; Max-Age=0; Path=${configWithDefaults.cookies_path}; HttpOnly;${configWithDefaults.cookies_secure ? " Secure;" : ""} SameSite=${configWithDefaults.cookies_same_site};`;
1916
+ }
1917
+ function getCookie(name, req, parse = false) {
1918
+ if (!configWithDefaults) {
1919
+ throw new Error("Enterprise Standard SSO Manager not initialized");
1920
+ }
1921
+ const header = req.headers.get("cookie");
1922
+ if (!header)
1923
+ return null;
1924
+ const cookie = header.split(";").find((row) => row.trim().startsWith(`${configWithDefaults.cookies_prefix}.${name}=`));
1925
+ if (!cookie)
1926
+ return null;
1927
+ const val = cookie.split("=")[1].trim();
1928
+ if (!parse)
1929
+ return val;
1930
+ const str = atob(val);
1931
+ return JSON.parse(str);
1932
+ }
1933
+ async function handler(request, handlerConfig) {
1934
+ const {
1935
+ loginUrl,
1936
+ userUrl,
1937
+ errorUrl,
1938
+ landingUrl,
1939
+ tokenUrl,
1940
+ refreshUrl,
1941
+ logoutUrl,
1942
+ logoutBackChannelUrl,
1943
+ jwksUrl,
1944
+ validation
1945
+ } = { ...handlerDefaults, ...handlerConfig };
1946
+ if (!configWithDefaults) {
1947
+ throw new Error("Enterprise Standard SSO Manager not initialized");
1948
+ }
1949
+ if (!loginUrl) {
1950
+ console.error("loginUrl is required");
1951
+ }
1952
+ const path = new URL(request.url).pathname;
1953
+ let redirectUriPath;
1954
+ try {
1955
+ redirectUriPath = new URL(configWithDefaults.redirect_uri).pathname;
1956
+ } catch {
1957
+ try {
1958
+ const requestUrl = new URL(request.url);
1959
+ const redirectUri = configWithDefaults.redirect_uri.startsWith("//") ? configWithDefaults.redirect_uri.slice(1) : configWithDefaults.redirect_uri;
1960
+ redirectUriPath = new URL(redirectUri, requestUrl.origin).pathname;
1961
+ } catch {
1962
+ redirectUriPath = configWithDefaults.redirect_uri.startsWith("/") ? configWithDefaults.redirect_uri : `/${configWithDefaults.redirect_uri}`;
1963
+ }
1964
+ }
1965
+ if (redirectUriPath === path) {
1966
+ return callbackHandler(request, validation);
1967
+ }
1968
+ if (loginUrl === path) {
1969
+ return initiateLogin({
1970
+ landingUrl: landingUrl || "/",
1971
+ errorUrl
1972
+ }, request.url);
1973
+ }
1974
+ if (userUrl === path) {
1975
+ const { tokens, refreshHeaders } = await getTokenFromCookies(request);
1976
+ if (!tokens) {
1977
+ return new Response("User not logged in", { status: 401 });
1978
+ }
1979
+ const user = await parseUser(tokens);
1980
+ return new Response(JSON.stringify(user), {
1981
+ headers: [["Content-Type", "application/json"], ...refreshHeaders]
1982
+ });
1983
+ }
1984
+ if (tokenUrl === path) {
1985
+ const { tokens, refreshHeaders } = await getTokenFromCookies(request);
1986
+ if (!tokens) {
1987
+ return new Response("User not logged in", { status: 401 });
1988
+ }
1989
+ return new Response(JSON.stringify({
1990
+ token: tokens.access_token,
1991
+ expires: tokens.expires
1992
+ }), {
1993
+ headers: [["Content-Type", "application/json"], ...refreshHeaders]
1994
+ });
1995
+ }
1996
+ if (refreshUrl === path) {
1997
+ const refresh_token = getCookie("refresh", request);
1998
+ if (!refresh_token) {
1999
+ return new Response("User not logged in", { status: 401 });
2000
+ }
2001
+ const newTokenResponse = await refreshToken(refresh_token);
2002
+ const user = await parseUser(newTokenResponse);
2003
+ const refreshHeaders = createJwtCookies(newTokenResponse, user.sso.expires);
2004
+ return new Response("Refresh Complete", {
2005
+ status: 200,
2006
+ headers: refreshHeaders
2007
+ });
2008
+ }
2009
+ if (logoutUrl === path) {
2010
+ return logout(request, { landingUrl: landingUrl || "/" });
2011
+ }
2012
+ if (logoutBackChannelUrl === path) {
2013
+ return logoutBackChannel(request);
2014
+ }
2015
+ if (jwksUrl === path) {
2016
+ const jwks = await fetchJwks();
2017
+ return new Response(JSON.stringify(jwks), {
2018
+ headers: [["Content-Type", "application/json"]]
2019
+ });
2020
+ }
2021
+ return new Response("Not Found", { status: 404 });
2022
+ }
2023
+ if (!configWithDefaults) {
2024
+ throw new Error("Enterprise Standard SSO Manager not initialized");
2025
+ }
2026
+ return {
2027
+ ...configWithDefaults,
2028
+ getUser,
2029
+ getRequiredUser,
2030
+ getJwt,
2031
+ initiateLogin,
2032
+ logout,
2033
+ callbackHandler,
2034
+ handler
2035
+ };
2036
+ }
2037
+
2038
+ // packages/react/src/vault.ts
2039
+ function vault(url) {
2040
+ async function getFullSecret(path, token) {
2041
+ const resp = await fetch(`${url}/${path}`, { headers: { "X-Vault-Token": token } });
2042
+ if (resp.status !== 200) {
2043
+ throw new Error(`Vault returned invalid status, ${resp.status}: '${resp.statusText}' from URL: ${url}`);
2044
+ }
2045
+ try {
2046
+ const secret = await resp.json();
2047
+ return secret.data;
2048
+ } catch (cause) {
2049
+ throw new Error("Error retrieving secret", { cause });
2050
+ }
2051
+ }
2052
+ return {
2053
+ url,
2054
+ getFullSecret,
2055
+ getSecret: async (path, token) => {
2056
+ return (await getFullSecret(path, token)).data;
2057
+ }
2058
+ };
2059
+ }
2060
+
2061
+ // packages/react/src/types/workload-schema.ts
2062
+ function jwtAssertionClaimsSchema(vendor) {
2063
+ return {
2064
+ "~standard": {
2065
+ version: 1,
2066
+ vendor,
2067
+ validate: (value) => {
2068
+ if (typeof value !== "object" || value === null) {
2069
+ return {
2070
+ issues: [
2071
+ {
2072
+ message: "Expected an object"
2073
+ }
2074
+ ]
2075
+ };
2076
+ }
2077
+ const claims = value;
2078
+ const issues = [];
2079
+ const result = { ...claims };
2080
+ const requiredStringFields = ["iss", "sub"];
2081
+ for (const field of requiredStringFields) {
2082
+ if (field in claims) {
2083
+ if (typeof claims[field] !== "string") {
2084
+ issues.push({
2085
+ message: `${field} must be a string`,
2086
+ path: [field]
2087
+ });
2088
+ }
2089
+ } else {
2090
+ issues.push({
2091
+ message: `${field} is required`,
2092
+ path: [field]
2093
+ });
2094
+ }
2095
+ }
2096
+ if ("aud" in claims && claims.aud !== undefined) {
2097
+ const aud = claims.aud;
2098
+ if (typeof aud !== "string" && !Array.isArray(aud)) {
2099
+ issues.push({
2100
+ message: "aud must be a string or array of strings",
2101
+ path: ["aud"]
2102
+ });
2103
+ } else if (Array.isArray(aud) && !aud.every((a) => typeof a === "string")) {
2104
+ issues.push({
2105
+ message: "aud array must contain only strings",
2106
+ path: ["aud"]
2107
+ });
2108
+ }
2109
+ }
2110
+ const optionalStringFields = ["jti", "scope"];
2111
+ for (const field of optionalStringFields) {
2112
+ if (field in claims && claims[field] !== undefined) {
2113
+ if (typeof claims[field] !== "string") {
2114
+ issues.push({
2115
+ message: `${field} must be a string`,
2116
+ path: [field]
2117
+ });
2118
+ }
2119
+ }
2120
+ }
2121
+ const requiredNumberFields = ["exp", "iat"];
2122
+ for (const field of requiredNumberFields) {
2123
+ if (field in claims) {
2124
+ if (typeof claims[field] !== "number") {
2125
+ issues.push({
2126
+ message: `${field} must be a number`,
2127
+ path: [field]
2128
+ });
2129
+ }
2130
+ } else {
2131
+ issues.push({
2132
+ message: `${field} is required`,
2133
+ path: [field]
2134
+ });
2135
+ }
2136
+ }
2137
+ if (issues.length > 0) {
2138
+ return { issues };
2139
+ }
2140
+ return { value: result };
2141
+ }
2142
+ }
2143
+ };
2144
+ }
2145
+ function workloadTokenResponseSchema(vendor) {
2146
+ return {
2147
+ "~standard": {
2148
+ version: 1,
2149
+ vendor,
2150
+ validate: (value) => {
2151
+ if (typeof value !== "object" || value === null) {
2152
+ return {
2153
+ issues: [
2154
+ {
2155
+ message: "Expected an object"
2156
+ }
2157
+ ]
2158
+ };
2159
+ }
2160
+ const response = value;
2161
+ const issues = [];
2162
+ const result = {};
2163
+ if ("access_token" in response) {
2164
+ if (typeof response.access_token === "string") {
2165
+ result.access_token = response.access_token;
2166
+ } else {
2167
+ issues.push({
2168
+ message: "access_token must be a string",
2169
+ path: ["access_token"]
2170
+ });
2171
+ }
2172
+ } else {
2173
+ issues.push({
2174
+ message: "access_token is required",
2175
+ path: ["access_token"]
2176
+ });
2177
+ }
2178
+ if ("token_type" in response) {
2179
+ if (typeof response.token_type === "string") {
2180
+ result.token_type = response.token_type;
2181
+ } else {
2182
+ issues.push({
2183
+ message: "token_type must be a string",
2184
+ path: ["token_type"]
2185
+ });
2186
+ }
2187
+ } else {
2188
+ issues.push({
2189
+ message: "token_type is required",
2190
+ path: ["token_type"]
2191
+ });
2192
+ }
2193
+ if ("scope" in response) {
2194
+ if (typeof response.scope === "string" || response.scope === undefined) {
2195
+ result.scope = response.scope;
2196
+ } else {
2197
+ issues.push({
2198
+ message: "scope must be a string",
2199
+ path: ["scope"]
2200
+ });
2201
+ }
2202
+ }
2203
+ if ("refresh_token" in response) {
2204
+ if (typeof response.refresh_token === "string" || response.refresh_token === undefined) {
2205
+ result.refresh_token = response.refresh_token;
2206
+ } else {
2207
+ issues.push({
2208
+ message: "refresh_token must be a string",
2209
+ path: ["refresh_token"]
2210
+ });
2211
+ }
2212
+ }
2213
+ if ("expires" in response) {
2214
+ if (typeof response.expires === "string" || response.expires === undefined) {
2215
+ result.expires = response.expires;
2216
+ } else {
2217
+ issues.push({
2218
+ message: "expires must be a string",
2219
+ path: ["expires"]
2220
+ });
2221
+ }
2222
+ }
2223
+ if ("expires_in" in response) {
2224
+ if (typeof response.expires_in === "number" || response.expires_in === undefined) {
2225
+ result.expires_in = response.expires_in;
2226
+ } else {
2227
+ issues.push({
2228
+ message: "expires_in must be a number",
2229
+ path: ["expires_in"]
2230
+ });
2231
+ }
2232
+ }
2233
+ if (issues.length > 0) {
2234
+ return { issues };
2235
+ }
2236
+ return { value: result };
2237
+ }
2238
+ }
2239
+ };
2240
+ }
2241
+
2242
+ // packages/react/src/workload-token-store.ts
2243
+ class InMemoryWorkloadTokenStore {
2244
+ tokens = new Map;
2245
+ async set(token) {
2246
+ this.tokens.set(token.workload_id, token);
2247
+ }
2248
+ async get(workload_id) {
2249
+ const token = this.tokens.get(workload_id);
2250
+ if (!token)
2251
+ return null;
2252
+ if (Date.now() > token.expires_at.getTime()) {
2253
+ this.tokens.delete(workload_id);
2254
+ return null;
2255
+ }
2256
+ return token;
2257
+ }
2258
+ async delete(workload_id) {
2259
+ this.tokens.delete(workload_id);
2260
+ }
2261
+ async isValid(workload_id) {
2262
+ const token = await this.get(workload_id);
2263
+ return token !== null;
2264
+ }
2265
+ async cleanup() {
2266
+ const now = Date.now();
2267
+ for (const [workload_id, token] of this.tokens.entries()) {
2268
+ if (now > token.expires_at.getTime()) {
2269
+ this.tokens.delete(workload_id);
2270
+ }
2271
+ }
2272
+ }
2273
+ }
2274
+
2275
+ // packages/react/src/workload.ts
2276
+ var jwksCache2 = new Map;
2277
+ function isJwtBearerConfig(config) {
2278
+ return "workload_id" in config || "private_key" in config;
2279
+ }
2280
+ function isClientCredentialsConfig(config) {
2281
+ return "client_id" in config || "client_secret" in config;
2282
+ }
2283
+ function isServerOnlyConfig(config) {
2284
+ return "jwks_uri" in config && !("workload_id" in config) && !("client_id" in config);
2285
+ }
2286
+ function validateWorkloadConfig(config) {
2287
+ const hasJwtBearer = "workload_id" in config && "private_key" in config;
2288
+ const hasClientCreds = "client_id" in config && "client_secret" in config;
2289
+ const hasServerOnly = "jwks_uri" in config;
2290
+ if (!hasJwtBearer && !hasClientCreds && !hasServerOnly) {
2291
+ throw new Error("Invalid WorkloadConfig: must provide either (workload_id + private_key) for JWT Bearer Grant, " + "(client_id + client_secret) for OAuth2 Client Credentials, or (jwks_uri) for server-only token validation");
2292
+ }
2293
+ }
2294
+ function workload(config) {
2295
+ validateWorkloadConfig(config);
2296
+ let configWithDefaults;
2297
+ if (isJwtBearerConfig(config)) {
2298
+ configWithDefaults = {
2299
+ ...config,
2300
+ token_url: must(config.token_url, "Missing 'token_url' from Workload Config"),
2301
+ workload_id: must(config.workload_id, "Missing 'workload_id' from Workload Config"),
2302
+ audience: must(config.audience, "Missing 'audience' from Workload Config"),
2303
+ scope: config.scope ?? "",
2304
+ algorithm: config.algorithm ?? "RS256",
2305
+ token_lifetime: config.token_lifetime ?? 300,
2306
+ refresh_threshold: config.refresh_threshold ?? 60,
2307
+ auto_refresh: config.auto_refresh !== undefined ? config.auto_refresh : true,
2308
+ token_store: config.token_store ?? new InMemoryWorkloadTokenStore
2309
+ };
2310
+ } else if (isClientCredentialsConfig(config)) {
2311
+ configWithDefaults = {
2312
+ ...config,
2313
+ token_url: must(config.token_url, "Missing 'token_url' from Workload Config"),
2314
+ client_id: must(config.client_id, "Missing 'client_id' from Workload Config"),
2315
+ client_secret: must(config.client_secret, "Missing 'client_secret' from Workload Config"),
2316
+ scope: config.scope ?? "",
2317
+ token_lifetime: config.token_lifetime ?? 300,
2318
+ refresh_threshold: config.refresh_threshold ?? 60,
2319
+ auto_refresh: config.auto_refresh !== undefined ? config.auto_refresh : true,
2320
+ token_store: config.token_store ?? new InMemoryWorkloadTokenStore
2321
+ };
2322
+ } else {
2323
+ configWithDefaults = config;
2324
+ }
2325
+ const initialized = true;
2326
+ function ensureInitialized() {
2327
+ if (!initialized) {
2328
+ throw new Error("Enterprise Standard Workload Manager not initialized");
2329
+ }
2330
+ }
2331
+ function generateJTI() {
2332
+ const array = new Uint8Array(16);
2333
+ crypto.getRandomValues(array);
2334
+ return Array.from(array, (byte) => byte.toString(16).padStart(2, "0")).join("");
2335
+ }
2336
+ function base64UrlEncode(data) {
2337
+ let base64;
2338
+ if (typeof data === "string") {
2339
+ base64 = btoa(data);
2340
+ } else {
2341
+ base64 = btoa(String.fromCharCode(...data));
2342
+ }
2343
+ return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
2344
+ }
2345
+ function base64UrlDecode(base64url) {
2346
+ let base64 = base64url.replace(/-/g, "+").replace(/_/g, "/");
2347
+ while (base64.length % 4) {
2348
+ base64 += "=";
2349
+ }
2350
+ return atob(base64);
2351
+ }
2352
+ function getAlgorithmParams(alg) {
2353
+ if (alg.startsWith("RS")) {
2354
+ const hash = alg === "RS256" ? "SHA-256" : alg === "RS384" ? "SHA-384" : "SHA-512";
2355
+ return { name: "RSASSA-PKCS1-v1_5", hash };
2356
+ } else if (alg.startsWith("ES")) {
2357
+ const namedCurve = alg === "ES256" ? "P-256" : alg === "ES384" ? "P-384" : "P-521";
2358
+ const hash = alg === "ES256" ? "SHA-256" : alg === "ES384" ? "SHA-384" : "SHA-512";
2359
+ return { name: "ECDSA", namedCurve, hash };
2360
+ }
2361
+ throw new Error(`Unsupported algorithm: ${alg}`);
2362
+ }
2363
+ async function importPrivateKey(pemKey, algorithm) {
2364
+ const pemContents = pemKey.replace(/-----BEGIN PRIVATE KEY-----/, "").replace(/-----END PRIVATE KEY-----/, "").replace(/\s/g, "");
2365
+ const binaryDer = Uint8Array.from(atob(pemContents), (c) => c.charCodeAt(0));
2366
+ const algorithmParams = getAlgorithmParams(algorithm);
2367
+ return crypto.subtle.importKey("pkcs8", binaryDer, algorithmParams, false, ["sign"]);
2368
+ }
2369
+ async function signJWT(data, privateKeyPEM, algorithm) {
2370
+ const privateKey = await importPrivateKey(privateKeyPEM, algorithm);
2371
+ const encoder = new TextEncoder;
2372
+ const dataBuffer = encoder.encode(data);
2373
+ const algorithmParams = getAlgorithmParams(algorithm);
2374
+ const signatureBuffer = await crypto.subtle.sign(algorithmParams, privateKey, dataBuffer);
2375
+ return base64UrlEncode(new Uint8Array(signatureBuffer));
2376
+ }
2377
+ async function retryWithBackoff(operation, maxRetries = 3, baseDelay = 1000, maxDelay = 30000) {
2378
+ let lastError = new Error("Placeholder Error");
2379
+ for (let attempt = 0;attempt <= maxRetries; attempt++) {
2380
+ try {
2381
+ return await operation();
2382
+ } catch (error) {
2383
+ lastError = error instanceof Error ? error : new Error(String(error));
2384
+ if (lastError.message.includes("400") || lastError.message.includes("401") || lastError.message.includes("403") || lastError.message.includes("404")) {
2385
+ throw lastError;
2386
+ }
2387
+ if (attempt < maxRetries) {
2388
+ const delay = Math.min(baseDelay * 2 ** attempt, maxDelay);
2389
+ const jitter = Math.random() * delay * 0.1;
2390
+ await new Promise((resolve) => setTimeout(resolve, delay + jitter));
2391
+ }
2392
+ }
2393
+ }
2394
+ throw lastError;
2395
+ }
2396
+ async function generateJWTAssertion(scope) {
2397
+ ensureInitialized();
2398
+ if (!isJwtBearerConfig(config)) {
2399
+ throw new Error("generateJWTAssertion is only available in JWT Bearer Grant mode");
2400
+ }
2401
+ const cfg = configWithDefaults;
2402
+ const now = Math.floor(Date.now() / 1000);
2403
+ const claims = {
2404
+ iss: cfg.workload_id,
2405
+ sub: cfg.workload_id,
2406
+ aud: cfg.audience ?? "",
2407
+ exp: now + cfg.token_lifetime,
2408
+ iat: now,
2409
+ jti: generateJTI(),
2410
+ scope: scope ?? cfg.scope
2411
+ };
2412
+ const header = {
2413
+ alg: cfg.algorithm,
2414
+ typ: "JWT",
2415
+ kid: cfg.key_id
224
2416
  };
2417
+ const encodedHeader = base64UrlEncode(JSON.stringify(header));
2418
+ const encodedPayload = base64UrlEncode(JSON.stringify(claims));
2419
+ const signatureInput = `${encodedHeader}.${encodedPayload}`;
2420
+ const signature = await signJWT(signatureInput, cfg.private_key, cfg.algorithm);
2421
+ return `${signatureInput}.${signature}`;
225
2422
  }
226
- async function exchangeCodeForToken(code, codeVerifier) {
227
- if (!configWithDefaults)
228
- throw new Error("SSO Manager not initialized");
229
- const tokenUrl = configWithDefaults.token_url;
230
- const body = new URLSearchParams;
231
- body.append("grant_type", "authorization_code");
232
- body.append("code", code);
233
- body.append("redirect_uri", configWithDefaults.redirect_uri);
234
- body.append("client_id", configWithDefaults.client_id);
235
- body.append("code_verifier", codeVerifier);
236
- try {
2423
+ async function acquireTokenJwtBearer(scope, validation) {
2424
+ const cfg = configWithDefaults;
2425
+ return retryWithBackoff(async () => {
2426
+ const tokenUrl = cfg.token_url;
2427
+ const assertion = await generateJWTAssertion(scope);
2428
+ const body = new URLSearchParams;
2429
+ body.append("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer");
2430
+ body.append("assertion", assertion);
2431
+ if (scope)
2432
+ body.append("scope", scope);
237
2433
  const response = await fetch(tokenUrl, {
238
2434
  method: "POST",
239
2435
  headers: {
@@ -244,24 +2440,41 @@ function sso(config) {
244
2440
  });
245
2441
  const data = await response.json();
246
2442
  if (!response.ok) {
247
- console.error("Token exchange error:", data);
248
- throw new Error(`Token exchange failed: ${data.error || response.statusText} - ${data.error_description || ""}`.trim());
2443
+ console.error("Token acquisition error:", data);
2444
+ throw new Error(`Token acquisition failed: ${data.error || response.statusText} - ${data.error_description || ""}`.trim());
249
2445
  }
250
- return data;
251
- } catch (error) {
252
- console.error("Error during token exchange:", error);
253
- throw new Error("Error during token exchange");
254
- }
2446
+ const validator = validation?.tokenResponse ?? workloadTokenResponseSchema("builtin");
2447
+ const result = await validator["~standard"].validate(data);
2448
+ if ("issues" in result) {
2449
+ console.error("Token response validation failed:", result.issues);
2450
+ throw new Error(`Token response validation failed: ${result.issues?.map((i) => i.message).join("; ")}`);
2451
+ }
2452
+ if (cfg.token_store) {
2453
+ const expiresAt = new Date(Date.now() + (result.value.expires_in ?? 300) * 1000);
2454
+ const cachedToken = {
2455
+ workload_id: cfg.workload_id,
2456
+ access_token: result.value.access_token,
2457
+ token_type: result.value.token_type,
2458
+ scope: result.value.scope,
2459
+ expires_at: expiresAt,
2460
+ created_at: new Date,
2461
+ refresh_token: result.value.refresh_token
2462
+ };
2463
+ await cfg.token_store.set(cachedToken);
2464
+ }
2465
+ return result.value;
2466
+ });
255
2467
  }
256
- async function refreshToken(refreshToken2) {
2468
+ async function acquireTokenClientCredentials(scope, validation) {
2469
+ const cfg = configWithDefaults;
257
2470
  return retryWithBackoff(async () => {
258
- if (!configWithDefaults)
259
- throw new Error("SSO Manager not initialized");
260
- const tokenUrl = configWithDefaults.token_url;
2471
+ const tokenUrl = cfg.token_url;
261
2472
  const body = new URLSearchParams;
262
- body.append("grant_type", "refresh_token");
263
- body.append("refresh_token", refreshToken2);
264
- body.append("client_id", configWithDefaults.client_id);
2473
+ body.append("grant_type", "client_credentials");
2474
+ body.append("client_id", cfg.client_id);
2475
+ body.append("client_secret", cfg.client_secret);
2476
+ if (scope)
2477
+ body.append("scope", scope);
265
2478
  const response = await fetch(tokenUrl, {
266
2479
  method: "POST",
267
2480
  headers: {
@@ -272,22 +2485,110 @@ function sso(config) {
272
2485
  });
273
2486
  const data = await response.json();
274
2487
  if (!response.ok) {
275
- console.error("Token refresh error:", data);
276
- throw new Error(`Token refresh failed: ${data.error || response.statusText} - ${data.error_description || ""}`.trim());
2488
+ console.error("Token acquisition error:", data);
2489
+ throw new Error(`Token acquisition failed: ${data.error || response.statusText} - ${data.error_description || ""}`.trim());
277
2490
  }
278
- return data;
2491
+ const validator = validation?.tokenResponse ?? workloadTokenResponseSchema("builtin");
2492
+ const result = await validator["~standard"].validate(data);
2493
+ if ("issues" in result) {
2494
+ console.error("Token response validation failed:", result.issues);
2495
+ throw new Error(`Token response validation failed: ${result.issues?.map((i) => i.message).join("; ")}`);
2496
+ }
2497
+ if (cfg.token_store) {
2498
+ const expiresAt = new Date(Date.now() + (result.value.expires_in ?? 300) * 1000);
2499
+ const cachedToken = {
2500
+ workload_id: cfg.client_id,
2501
+ access_token: result.value.access_token,
2502
+ token_type: result.value.token_type,
2503
+ scope: result.value.scope,
2504
+ expires_at: expiresAt,
2505
+ created_at: new Date,
2506
+ refresh_token: result.value.refresh_token
2507
+ };
2508
+ await cfg.token_store.set(cachedToken);
2509
+ }
2510
+ return result.value;
279
2511
  });
280
2512
  }
2513
+ async function getToken(scope) {
2514
+ ensureInitialized();
2515
+ if (isServerOnlyConfig(config)) {
2516
+ throw new Error("Cannot acquire tokens: Workload is configured in server-only mode (validation only). " + "To acquire tokens, configure client_id + client_secret for OAuth2 Client Credentials, " + "or workload_id + private_key for JWT Bearer Grant.");
2517
+ }
2518
+ if (!configWithDefaults.token_url) {
2519
+ throw new Error("Cannot acquire tokens: Missing token_url in WorkloadConfig. " + "Client role requires token_url to be configured in vault.");
2520
+ }
2521
+ if (isJwtBearerConfig(configWithDefaults)) {
2522
+ if (!configWithDefaults.private_key) {
2523
+ throw new Error("Cannot acquire tokens: Missing private_key in WorkloadConfig. " + "JWT Bearer Grant client role requires private_key to be configured in vault.");
2524
+ }
2525
+ } else if (isClientCredentialsConfig(configWithDefaults)) {
2526
+ if (!configWithDefaults.client_secret) {
2527
+ throw new Error("Cannot acquire tokens: Missing client_secret in WorkloadConfig. " + "OAuth2 Client Credentials client role requires client_secret to be configured in vault.");
2528
+ }
2529
+ }
2530
+ const requestedScope = scope ?? configWithDefaults.scope ?? "";
2531
+ let cacheKey;
2532
+ if (isJwtBearerConfig(configWithDefaults)) {
2533
+ cacheKey = configWithDefaults.workload_id;
2534
+ } else if (isClientCredentialsConfig(configWithDefaults)) {
2535
+ cacheKey = configWithDefaults.client_id;
2536
+ } else {
2537
+ throw new Error("Invalid WorkloadConfig");
2538
+ }
2539
+ const cfg = configWithDefaults;
2540
+ if (cfg.token_store) {
2541
+ const cachedToken = await cfg.token_store.get(cacheKey);
2542
+ if (cachedToken) {
2543
+ const now = Date.now();
2544
+ const expiresAt = cachedToken.expires_at.getTime();
2545
+ const refreshThreshold = cfg.refresh_threshold * 1000;
2546
+ if (now + refreshThreshold < expiresAt) {
2547
+ return cachedToken.access_token;
2548
+ }
2549
+ if (cfg.auto_refresh) {
2550
+ try {
2551
+ const newToken = isJwtBearerConfig(config) ? await acquireTokenJwtBearer(requestedScope) : await acquireTokenClientCredentials(requestedScope);
2552
+ return newToken.access_token;
2553
+ } catch (error) {
2554
+ if (now < expiresAt) {
2555
+ console.warn("Token refresh failed, using cached token:", error);
2556
+ return cachedToken.access_token;
2557
+ }
2558
+ throw error;
2559
+ }
2560
+ }
2561
+ }
2562
+ }
2563
+ const tokenResponse = isJwtBearerConfig(config) ? await acquireTokenJwtBearer(requestedScope) : await acquireTokenClientCredentials(requestedScope);
2564
+ return tokenResponse.access_token;
2565
+ }
2566
+ async function refreshToken() {
2567
+ ensureInitialized();
2568
+ if (isServerOnlyConfig(config)) {
2569
+ throw new Error("Cannot refresh tokens: Workload is configured in server-only mode (validation only).");
2570
+ }
2571
+ const cfg = configWithDefaults;
2572
+ return isJwtBearerConfig(cfg) ? await acquireTokenJwtBearer(cfg.scope) : await acquireTokenClientCredentials(cfg.scope);
2573
+ }
281
2574
  async function revokeToken(token) {
2575
+ ensureInitialized();
282
2576
  try {
283
- if (!configWithDefaults)
284
- throw new Error("SSO Manager not initialized");
285
- const revokeUrl = `${configWithDefaults.authority}/protocol/openid-connect/revoke`;
2577
+ if (!config.revocation_endpoint) {
2578
+ return;
2579
+ }
286
2580
  const body = new URLSearchParams;
287
2581
  body.append("token", token);
288
- body.append("token_type_hint", "refresh_token");
289
- body.append("client_id", configWithDefaults.client_id);
290
- const response = await fetch(revokeUrl, {
2582
+ body.append("token_type_hint", "access_token");
2583
+ if (isJwtBearerConfig(config)) {
2584
+ const cfg = configWithDefaults;
2585
+ body.append("client_id", cfg.workload_id);
2586
+ } else if (isClientCredentialsConfig(config)) {
2587
+ const cfg = configWithDefaults;
2588
+ body.append("client_id", cfg.client_id);
2589
+ body.append("client_secret", cfg.client_secret);
2590
+ }
2591
+ const response = await fetch(config.revocation_endpoint, {
291
2592
  method: "POST",
292
2593
  headers: {
293
2594
  "Content-Type": "application/x-www-form-urlencoded"
@@ -299,368 +2600,499 @@ function sso(config) {
299
2600
  } else {
300
2601
  console.log("Token revoked successfully");
301
2602
  }
2603
+ if (config.token_store) {
2604
+ let cacheKey;
2605
+ if (isJwtBearerConfig(config)) {
2606
+ cacheKey = configWithDefaults.workload_id;
2607
+ } else if (isClientCredentialsConfig(config)) {
2608
+ cacheKey = configWithDefaults.client_id;
2609
+ } else {
2610
+ return;
2611
+ }
2612
+ await config.token_store.delete(cacheKey);
2613
+ }
302
2614
  } catch (error) {
303
2615
  console.warn("Error revoking token:", error);
304
2616
  }
305
2617
  }
306
2618
  async function fetchJwks() {
307
- const url = configWithDefaults.jwks_uri || `${configWithDefaults.authority}/protocol/openid-connect/certs`;
308
- const cached = jwksCache.get(url);
2619
+ ensureInitialized();
2620
+ const url = config.jwks_uri;
2621
+ if (!url) {
2622
+ throw new Error("Cannot validate tokens: Missing jwks_uri in WorkloadConfig. " + "Server role requires jwks_uri to be configured in vault to fetch public keys for token validation.");
2623
+ }
2624
+ const cached = jwksCache2.get(url);
309
2625
  if (cached)
310
2626
  return cached;
311
2627
  return retryWithBackoff(async () => {
312
- if (!configWithDefaults)
313
- throw new Error("SSO Manager not initialized");
314
2628
  const response = await fetch(url);
315
2629
  if (!response.ok)
316
2630
  throw new Error("Failed to fetch JWKS");
317
2631
  const jwks = await response.json();
318
- jwksCache.set(url, jwks);
2632
+ jwksCache2.set(url, jwks);
319
2633
  return jwks;
320
2634
  });
321
2635
  }
322
- async function retryWithBackoff(operation, maxRetries = 3, baseDelay = 1000, maxDelay = 30000) {
323
- let lastError = new Error("Placeholder Error");
324
- for (let attempt = 0;attempt <= maxRetries; attempt++) {
325
- try {
326
- return await operation();
327
- } catch (error) {
328
- lastError = error instanceof Error ? error : new Error(String(error));
329
- if (error instanceof Error && error.message.includes("400")) {
330
- throw error;
331
- }
332
- if (attempt === maxRetries) {
333
- throw lastError;
334
- }
335
- const delay = Math.min(baseDelay * 2 ** attempt, maxDelay);
336
- const jitter = Math.random() * 0.1 * delay;
337
- await new Promise((resolve) => setTimeout(resolve, delay + jitter));
338
- console.warn(`Retry attempt ${attempt + 1} after ${delay + jitter}ms delay`);
339
- }
340
- }
341
- throw lastError;
2636
+ async function getPublicKey(kid) {
2637
+ const jwks = await fetchJwks();
2638
+ const key = jwks.keys.find((k) => k.kid === kid);
2639
+ if (!key)
2640
+ throw new Error("Public key not found");
2641
+ const defaultAlg = isJwtBearerConfig(config) ? configWithDefaults.algorithm : "RS256";
2642
+ const algorithmParams = getAlgorithmParams(key.alg || defaultAlg);
2643
+ return crypto.subtle.importKey("jwk", key, algorithmParams, false, ["verify"]);
342
2644
  }
343
- async function parseJwt(token) {
2645
+ async function parseJWT(token, validation) {
2646
+ ensureInitialized();
344
2647
  try {
345
2648
  const parts = token.split(".");
346
2649
  if (parts.length !== 3)
347
2650
  throw new Error("Invalid JWT");
348
- const header = JSON.parse(atob(parts[0].replace(/-/g, "+").replace(/_/g, "/")));
349
- const payload = JSON.parse(atob(parts[1].replace(/-/g, "+").replace(/_/g, "/")));
350
- const signature = parts[2].replace(/-/g, "+").replace(/_/g, "/");
2651
+ const header = JSON.parse(base64UrlDecode(parts[0]));
2652
+ const payload = JSON.parse(base64UrlDecode(parts[1]));
351
2653
  const publicKey = await getPublicKey(header.kid);
2654
+ const signature = parts[2];
352
2655
  const encoder = new TextEncoder;
353
2656
  const data = encoder.encode(`${parts[0]}.${parts[1]}`);
354
- const isValid = await crypto.subtle.verify("RSASSA-PKCS1-v1_5", publicKey, Uint8Array.from(atob(signature), (c) => c.charCodeAt(0)), data);
2657
+ const signatureBytes = Uint8Array.from(base64UrlDecode(signature), (c) => c.charCodeAt(0));
2658
+ const algorithmParams = getAlgorithmParams(header.alg);
2659
+ const isValid = await crypto.subtle.verify(algorithmParams, publicKey, signatureBytes, data);
355
2660
  if (!isValid)
356
2661
  throw new Error("Invalid JWT signature");
357
- return payload;
358
- } catch (e) {
359
- console.error("Error verifying JWT:", e);
360
- throw e;
2662
+ const validator = validation?.jwtAssertionClaims ?? jwtAssertionClaimsSchema("builtin");
2663
+ const result = await validator["~standard"].validate(payload);
2664
+ if ("issues" in result) {
2665
+ console.error("JWT claims validation failed:", result.issues);
2666
+ throw new Error(`JWT claims validation failed: ${result.issues?.map((i) => i.message).join("; ")}`);
2667
+ }
2668
+ return result.value;
2669
+ } catch (error) {
2670
+ console.error("Error verifying JWT:", error);
2671
+ throw error;
361
2672
  }
362
2673
  }
363
- function generateRandomString(length = 32) {
364
- const array = new Uint8Array(length);
365
- crypto.getRandomValues(array);
366
- return Array.from(array, (byte) => byte.toString(16).padStart(2, "0")).join("").substring(0, length);
367
- }
368
- async function pkceChallengeFromVerifier(verifier) {
369
- const encoder = new TextEncoder;
370
- const data = encoder.encode(verifier);
371
- const hashBuffer = await crypto.subtle.digest("SHA-256", data);
372
- const hashArray = Array.from(new Uint8Array(hashBuffer));
373
- const hashBase64 = btoa(String.fromCharCode(...hashArray)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
374
- return hashBase64;
375
- }
376
- async function getPublicKey(kid) {
377
- const jwks = await fetchJwks();
378
- const key = jwks.keys.find((k) => k.kid === kid);
379
- if (!key)
380
- throw new Error("Public key not found");
381
- const publicKey = await crypto.subtle.importKey("jwk", {
382
- kty: key.kty,
383
- n: key.n,
384
- e: key.e
385
- }, { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" }, false, ["verify"]);
386
- return publicKey;
387
- }
388
- function createJwtCookies(token, expires) {
389
- const control = {
390
- expires_in: token.expires_in,
391
- refresh_expires_in: token.refresh_expires_in,
392
- scope: token.scope,
393
- session_state: token.session_state,
394
- token_type: token.token_type,
395
- expires: expires.toISOString()
396
- };
397
- return [
398
- ["Set-Cookie", createCookie("access", token.access_token, expires)],
399
- ["Set-Cookie", createCookie("id", token.id_token, expires)],
400
- ["Set-Cookie", createCookie("refresh", token.refresh_token ?? "", expires)],
401
- ["Set-Cookie", createCookie("control", control, expires)]
402
- ];
403
- }
404
- async function getTokenFromCookies(req) {
405
- const access_token = getCookie("access", req);
406
- const id_token = getCookie("id", req);
407
- const refresh_token = getCookie("refresh", req);
408
- const control = getCookie("control", req, true);
409
- if (!access_token || !id_token || !refresh_token || !control) {
410
- return { tokens: undefined, refreshHeaders: [] };
411
- }
412
- let tokenResponse = {
413
- access_token,
414
- id_token,
415
- refresh_token,
416
- ...control
417
- };
418
- if (control.expires && refresh_token && Date.now() > new Date(control.expires).getTime()) {
419
- tokenResponse = await refreshToken(refresh_token);
420
- const user = await parseUser(tokenResponse);
421
- const refreshHeaders = createJwtCookies(tokenResponse, user.sso.expires);
422
- return { tokens: tokenResponse, refreshHeaders };
2674
+ async function validateToken(token, validation) {
2675
+ ensureInitialized();
2676
+ try {
2677
+ const claims = await parseJWT(token, validation);
2678
+ const now = Math.floor(Date.now() / 1000);
2679
+ if (claims.exp && claims.exp < now) {
2680
+ return { valid: false, error: "Token expired" };
2681
+ }
2682
+ if (isJwtBearerConfig(config)) {
2683
+ if (config.audience && claims.aud !== config.audience) {
2684
+ return { valid: false, error: "Invalid audience" };
2685
+ }
2686
+ } else if (isClientCredentialsConfig(config)) {
2687
+ if (config.issuer && claims.iss !== config.issuer) {
2688
+ return { valid: false, error: "Invalid issuer" };
2689
+ }
2690
+ if (config.audience && claims.aud !== config.audience) {
2691
+ return { valid: false, error: "Invalid audience" };
2692
+ }
2693
+ } else {
2694
+ const serverConfig = config;
2695
+ if (serverConfig.issuer && claims.iss !== serverConfig.issuer) {
2696
+ return { valid: false, error: "Invalid issuer" };
2697
+ }
2698
+ }
2699
+ return {
2700
+ valid: true,
2701
+ claims,
2702
+ expiresAt: claims.exp ? new Date(claims.exp * 1000) : undefined
2703
+ };
2704
+ } catch (error) {
2705
+ return {
2706
+ valid: false,
2707
+ error: error instanceof Error ? error.message : String(error)
2708
+ };
423
2709
  }
424
- return { tokens: tokenResponse, refreshHeaders: [] };
425
- }
426
- async function getJwt(request) {
427
- const { tokens } = await getTokenFromCookies(request);
428
- if (!tokens)
429
- return;
430
- return tokens.access_token;
431
2710
  }
432
- function createCookie(name, value, expires) {
433
- name = `${configWithDefaults.cookies_prefix}.${name}`;
434
- if (typeof value !== "string") {
435
- value = btoa(JSON.stringify(value));
2711
+ async function getWorkload(request) {
2712
+ ensureInitialized();
2713
+ if (!config.jwks_uri) {
2714
+ throw new Error("Cannot validate tokens: Missing jwks_uri in WorkloadConfig. " + "Server role requires jwks_uri to be configured in vault to fetch public keys for token validation.");
436
2715
  }
437
- let exp;
438
- if (expires instanceof Date) {
439
- exp = `Expires=${expires.toUTCString()}`;
440
- } else if (typeof expires === "number") {
441
- exp = `Max-Age=${expires}`;
442
- } else {
443
- throw new Error("Invalid expires type", expires);
2716
+ const authHeader = request.headers.get("Authorization");
2717
+ if (!authHeader || !authHeader.startsWith("Bearer ")) {
2718
+ return;
444
2719
  }
445
- if (value.length > 4000) {
446
- throw new Error(`Error setting cookie: ${name}. Cookie length is: ${value.length}`);
2720
+ const token = authHeader.substring(7);
2721
+ const result = await validateToken(token, configWithDefaults.validation);
2722
+ if (!result.valid || !result.claims) {
2723
+ return;
447
2724
  }
448
- return `${name}=${value}; ${exp}; Path=${configWithDefaults.cookies_path}; HttpOnly;${configWithDefaults.cookies_secure ? " Secure;" : ""} SameSite=${configWithDefaults.cookies_same_site};`;
449
- }
450
- function clearCookie(name) {
451
- return `${configWithDefaults.cookies_prefix}.${name}=; Max-Age=0; Path=${configWithDefaults.cookies_path}; HttpOnly;${configWithDefaults.cookies_secure ? " Secure;" : ""} SameSite=${configWithDefaults.cookies_same_site};`;
452
- }
453
- function getCookie(name, req, parse = false) {
454
- const header = req.headers.get("cookie");
455
- if (!header)
456
- return null;
457
- const cookie = header.split(";").find((row) => row.trim().startsWith(`${configWithDefaults.cookies_prefix}.${name}=`));
458
- if (!cookie)
459
- return null;
460
- const val = cookie.split("=")[1].trim();
461
- if (!parse)
462
- return val;
463
- const str = atob(val);
464
- return JSON.parse(str);
2725
+ return {
2726
+ workload_id: result.claims.sub,
2727
+ client_id: typeof result.claims.client_id === "string" ? result.claims.client_id : undefined,
2728
+ scope: result.claims.scope,
2729
+ claims: result.claims
2730
+ };
465
2731
  }
466
- async function handler(request, handlerConfig) {
467
- const { loginUrl, userUrl, errorUrl, landingUrl, tokenUrl, refreshUrl, logoutUrl, jwksUrl } = handlerConfig ?? {};
468
- if (!loginUrl) {
469
- console.error("loginUrl is required");
470
- }
2732
+ async function handler(request) {
2733
+ ensureInitialized();
2734
+ const tokenUrl = configWithDefaults.tokenUrl;
2735
+ const validateUrl = configWithDefaults.validateUrl;
2736
+ const jwksUrl = configWithDefaults.jwksUrl;
2737
+ const refreshUrl = configWithDefaults.refreshUrl;
2738
+ const validation = configWithDefaults.validation;
471
2739
  const path = new URL(request.url).pathname;
472
- if (new URL(config.redirect_uri).pathname === path) {
473
- return callbackHandler(request);
474
- }
475
- if (loginUrl === path) {
476
- return initiateLogin({
477
- landingUrl: landingUrl || "/",
478
- errorUrl
479
- });
480
- }
481
- if (userUrl === path) {
482
- const { tokens, refreshHeaders } = await getTokenFromCookies(request);
483
- if (!tokens) {
484
- return new Response("User not logged in", { status: 401 });
485
- }
486
- const user = await parseUser(tokens);
487
- return new Response(JSON.stringify(user), {
488
- headers: [["Content-Type", "application/json"], ...refreshHeaders]
489
- });
490
- }
491
- if (tokenUrl === path) {
492
- const { tokens, refreshHeaders } = await getTokenFromCookies(request);
493
- if (!tokens) {
494
- return new Response("User not logged in", { status: 401 });
495
- }
496
- return new Response(JSON.stringify({
497
- token: tokens.access_token,
498
- expires: tokens.expires
499
- }), {
500
- headers: [["Content-Type", "application/json"], ...refreshHeaders]
2740
+ if (tokenUrl === path && request.method === "GET") {
2741
+ const url = new URL(request.url);
2742
+ const scope = url.searchParams.get("scope") || undefined;
2743
+ const token = await getToken(scope);
2744
+ return new Response(JSON.stringify({ access_token: token, token_type: "Bearer" }), {
2745
+ headers: [["Content-Type", "application/json"]]
501
2746
  });
502
2747
  }
503
- if (refreshUrl === path) {
504
- const refresh_token = getCookie("refresh", request);
505
- if (!refresh_token) {
506
- return new Response("User not logged in", { status: 401 });
2748
+ if (validateUrl === path && request.method === "POST") {
2749
+ const authHeader = request.headers.get("Authorization");
2750
+ if (!authHeader || !authHeader.startsWith("Bearer ")) {
2751
+ return new Response(JSON.stringify({ valid: false, error: "Missing Authorization header" }), {
2752
+ status: 401,
2753
+ headers: [["Content-Type", "application/json"]]
2754
+ });
507
2755
  }
508
- const newTokenResponse = await refreshToken(refresh_token);
509
- const user = await parseUser(newTokenResponse);
510
- const refreshHeaders = createJwtCookies(newTokenResponse, user.sso.expires);
511
- return new Response("Refresh Complete", {
512
- status: 200,
513
- headers: refreshHeaders
2756
+ const token = authHeader.substring(7);
2757
+ const result = await validateToken(token, validation);
2758
+ return new Response(JSON.stringify(result), {
2759
+ status: result.valid ? 200 : 401,
2760
+ headers: [["Content-Type", "application/json"]]
514
2761
  });
515
2762
  }
516
- if (logoutUrl === path) {
517
- return logout(request, { landingUrl: landingUrl || "/" });
518
- }
519
- if (jwksUrl === path) {
2763
+ if (jwksUrl === path && request.method === "GET") {
520
2764
  const jwks = await fetchJwks();
521
2765
  return new Response(JSON.stringify(jwks), {
522
2766
  headers: [["Content-Type", "application/json"]]
523
2767
  });
524
2768
  }
2769
+ if (refreshUrl === path && request.method === "POST") {
2770
+ const tokenResponse = await refreshToken();
2771
+ return new Response(JSON.stringify(tokenResponse), {
2772
+ headers: [["Content-Type", "application/json"]]
2773
+ });
2774
+ }
525
2775
  return new Response("Not Found", { status: 404 });
526
2776
  }
527
2777
  return {
528
- getUser,
529
- getRequiredUser,
530
- getJwt,
531
- initiateLogin,
532
- logout,
533
- callbackHandler,
2778
+ ...configWithDefaults,
2779
+ getToken,
2780
+ refreshToken,
2781
+ generateJWTAssertion,
2782
+ revokeToken,
2783
+ validateToken,
2784
+ getWorkload,
2785
+ parseJWT,
534
2786
  handler
535
2787
  };
536
2788
  }
537
2789
 
538
- // src/vault.ts
539
- function vault(url) {
540
- async function getFullSecret(path, token) {
541
- const resp = await fetch(`${url}/${path}`, { headers: { "X-Vault-Token": token } });
542
- if (resp.status !== 200) {
543
- throw new Error(`Vault returned invalid status, ${resp.status}: '${resp.statusText}' from URL: ${url}`);
2790
+ // packages/react/src/group-store.ts
2791
+ class InMemoryGroupStore {
2792
+ groups = new Map;
2793
+ externalIdIndex = new Map;
2794
+ displayNameIndex = new Map;
2795
+ async get(id) {
2796
+ return this.groups.get(id) ?? null;
2797
+ }
2798
+ async getByExternalId(externalId) {
2799
+ const id = this.externalIdIndex.get(externalId);
2800
+ if (!id)
2801
+ return null;
2802
+ return this.groups.get(id) ?? null;
2803
+ }
2804
+ async getByDisplayName(displayName) {
2805
+ const id = this.displayNameIndex.get(displayName.toLowerCase());
2806
+ if (!id)
2807
+ return null;
2808
+ return this.groups.get(id) ?? null;
2809
+ }
2810
+ async list() {
2811
+ return Array.from(this.groups.values());
2812
+ }
2813
+ async upsert(group) {
2814
+ const existing = this.groups.get(group.id);
2815
+ if (existing) {
2816
+ if (existing.externalId && existing.externalId !== group.externalId) {
2817
+ this.externalIdIndex.delete(existing.externalId);
2818
+ }
2819
+ if (existing.displayName.toLowerCase() !== group.displayName.toLowerCase()) {
2820
+ this.displayNameIndex.delete(existing.displayName.toLowerCase());
2821
+ }
544
2822
  }
545
- try {
546
- const secret = await resp.json();
547
- return secret.data;
548
- } catch (cause) {
549
- throw new Error("Error retrieving secret", { cause });
2823
+ this.groups.set(group.id, group);
2824
+ if (group.externalId) {
2825
+ this.externalIdIndex.set(group.externalId, group.id);
550
2826
  }
2827
+ this.displayNameIndex.set(group.displayName.toLowerCase(), group.id);
551
2828
  }
552
- return {
553
- url,
554
- getFullSecret,
555
- getSecret: async (path, token) => {
556
- return (await getFullSecret(path, token)).data;
2829
+ async delete(id) {
2830
+ const group = this.groups.get(id);
2831
+ if (group) {
2832
+ if (group.externalId) {
2833
+ this.externalIdIndex.delete(group.externalId);
2834
+ }
2835
+ this.displayNameIndex.delete(group.displayName.toLowerCase());
2836
+ }
2837
+ this.groups.delete(id);
2838
+ }
2839
+ async addMember(groupId, member) {
2840
+ const group = this.groups.get(groupId);
2841
+ if (!group) {
2842
+ throw new Error(`Group ${groupId} not found`);
557
2843
  }
2844
+ const members = group.members ?? [];
2845
+ if (!members.some((m) => m.value === member.value)) {
2846
+ members.push(member);
2847
+ group.members = members;
2848
+ group.updatedAt = new Date;
2849
+ }
2850
+ }
2851
+ async removeMember(groupId, memberId) {
2852
+ const group = this.groups.get(groupId);
2853
+ if (!group) {
2854
+ throw new Error(`Group ${groupId} not found`);
2855
+ }
2856
+ if (group.members) {
2857
+ group.members = group.members.filter((m) => m.value !== memberId);
2858
+ group.updatedAt = new Date;
2859
+ }
2860
+ }
2861
+ }
2862
+ // packages/react/src/tenant.ts
2863
+ class TenantRequestError extends Error {
2864
+ constructor(message) {
2865
+ super(message);
2866
+ this.name = "TenantRequestError";
2867
+ }
2868
+ }
2869
+ function serializeESConfig(configOrES) {
2870
+ if (configOrES && typeof configOrES === "object" && "sso" in configOrES && "workload" in configOrES && "iam" in configOrES) {
2871
+ const config2 = {
2872
+ defaultInstance: configOrES.defaultInstance,
2873
+ sso: configOrES.sso ? serializeESConfig(configOrES.sso) : undefined,
2874
+ iam: configOrES.iam ? serializeESConfig(configOrES.iam) : undefined,
2875
+ workload: configOrES.workload ? serializeESConfig(configOrES.workload) : undefined,
2876
+ validation: configOrES.validation
2877
+ };
2878
+ return config2;
2879
+ }
2880
+ const config = configOrES;
2881
+ if (config === null || config === undefined) {
2882
+ return config;
2883
+ }
2884
+ if (typeof config !== "object") {
2885
+ return config;
2886
+ }
2887
+ if (Array.isArray(config)) {
2888
+ return config.map((item) => serializeESConfig(item));
2889
+ }
2890
+ const isStore = typeof config.get === "function" || typeof config.set === "function" || typeof config.create === "function" || typeof config.delete === "function" || typeof config.upsert === "function" || typeof config.list === "function";
2891
+ const isValidator = config["~standard"] !== undefined;
2892
+ if (typeof config === "function" || isStore || isValidator) {
2893
+ return;
2894
+ }
2895
+ if (config instanceof Date) {
2896
+ return config.toISOString();
2897
+ }
2898
+ const serialized = {};
2899
+ for (const key in config) {
2900
+ if (Object.prototype.hasOwnProperty.call(config, key)) {
2901
+ if (key === "session_store" || key === "user_store" || key === "token_store" || key === "group_store" || key === "validation" || key === "vault" || key === "getUser" || key === "getRequiredUser" || key === "getJwt" || key === "initiateLogin" || key === "logout" || key === "callbackHandler" || key === "handler" || key === "getToken" || key === "refreshToken" || key === "generateJWTAssertion" || key === "revokeToken" || key === "validateToken" || key === "getWorkload" || key === "parseJWT" || key === "createUser" || key === "getBaseUrl" || key === "groups_outbound" || key === "groups_inbound") {
2902
+ continue;
2903
+ }
2904
+ const value = serializeESConfig(config[key]);
2905
+ if (value !== undefined) {
2906
+ serialized[key] = value;
2907
+ }
2908
+ }
2909
+ }
2910
+ return serialized;
2911
+ }
2912
+ async function parseTenantRequest(request) {
2913
+ if (request.method !== "POST") {
2914
+ throw new TenantRequestError("Only POST method is supported");
2915
+ }
2916
+ let body;
2917
+ try {
2918
+ body = await request.json();
2919
+ } catch (_error) {
2920
+ throw new TenantRequestError("Invalid JSON in request body");
2921
+ }
2922
+ if (typeof body !== "object" || body === null) {
2923
+ throw new TenantRequestError("Request body must be an object");
2924
+ }
2925
+ const tenantRequest = body;
2926
+ if (!tenantRequest.appId) {
2927
+ throw new TenantRequestError("Missing required field: appId");
2928
+ }
2929
+ if (!tenantRequest.companyId) {
2930
+ throw new TenantRequestError("Missing required field: companyId");
2931
+ }
2932
+ if (!tenantRequest.companyName) {
2933
+ throw new TenantRequestError("Missing required field: companyName");
2934
+ }
2935
+ if (!tenantRequest.environmentType) {
2936
+ throw new TenantRequestError("Missing required field: environmentType");
2937
+ }
2938
+ if (!tenantRequest.email) {
2939
+ throw new TenantRequestError("Missing required field: email");
2940
+ }
2941
+ if (!tenantRequest.webhookUrl) {
2942
+ throw new TenantRequestError("Missing required field: webhookUrl");
2943
+ }
2944
+ const validEnvironmentTypes = ["POC", "DEV", "QA", "PROD"];
2945
+ if (!validEnvironmentTypes.includes(tenantRequest.environmentType)) {
2946
+ throw new TenantRequestError(`Invalid environmentType: ${tenantRequest.environmentType}. Must be one of: ${validEnvironmentTypes.join(", ")}`);
2947
+ }
2948
+ try {
2949
+ new URL(tenantRequest.webhookUrl);
2950
+ } catch {
2951
+ throw new TenantRequestError("Invalid webhookUrl: must be a valid URL");
2952
+ }
2953
+ const appId = tenantRequest.appId;
2954
+ const companyId = tenantRequest.companyId;
2955
+ const companyName = tenantRequest.companyName;
2956
+ const environmentType = tenantRequest.environmentType;
2957
+ const email = tenantRequest.email;
2958
+ const webhookUrl = tenantRequest.webhookUrl;
2959
+ return {
2960
+ appId,
2961
+ companyId,
2962
+ companyName,
2963
+ environmentType,
2964
+ email,
2965
+ webhookUrl
558
2966
  };
559
2967
  }
2968
+ async function sendTenantWebhook(webhookUrl, payload) {
2969
+ try {
2970
+ const response = await fetch(webhookUrl, {
2971
+ method: "POST",
2972
+ headers: {
2973
+ "Content-Type": "application/json"
2974
+ },
2975
+ body: JSON.stringify(payload)
2976
+ });
2977
+ if (!response.ok) {
2978
+ console.error(`Failed to send webhook update: ${response.status} ${response.statusText}`);
2979
+ }
2980
+ } catch (error) {
2981
+ console.error("Failed to send webhook update:", error);
2982
+ }
2983
+ }
560
2984
 
561
- // src/oidc-schema.ts
562
- function oidcCallbackSchema(vendor) {
563
- return {
564
- "~standard": {
565
- version: 1,
566
- vendor,
567
- validate: (value) => {
568
- if (typeof value !== "object" || value === null) {
569
- return {
570
- issues: [
571
- {
572
- message: "Expected an object"
573
- }
574
- ]
575
- };
576
- }
577
- const params = value;
578
- const issues = [];
579
- const result = {};
580
- if ("code" in params) {
581
- if (typeof params.code === "string") {
582
- result.code = params.code;
583
- } else {
584
- issues.push({
585
- message: "code must be a string",
586
- path: ["code"]
587
- });
588
- }
589
- } else if (!("error" in params)) {
590
- issues.push({
591
- message: "code is required",
592
- path: ["code"]
593
- });
594
- }
595
- if ("state" in params) {
596
- if (typeof params.state === "string" || params.state === undefined) {
597
- result.state = params.state;
598
- } else {
599
- issues.push({
600
- message: "state must be a string",
601
- path: ["state"]
602
- });
603
- }
604
- }
605
- if ("session_state" in params) {
606
- if (typeof params.session_state === "string" || params.session_state === undefined) {
607
- result.session_state = params.session_state;
608
- } else {
609
- issues.push({
610
- message: "session_state must be a string",
611
- path: ["session_state"]
612
- });
613
- }
614
- }
615
- if ("error" in params) {
616
- if (typeof params.error === "string") {
617
- result.error = params.error;
618
- } else {
619
- issues.push({
620
- message: "error must be a string",
621
- path: ["error"]
622
- });
623
- }
624
- if ("error_description" in params) {
625
- if (typeof params.error_description === "string" || params.error_description === undefined) {
626
- result.error_description = params.error_description;
627
- } else {
628
- issues.push({
629
- message: "error_description must be a string",
630
- path: ["error_description"]
631
- });
632
- }
633
- }
634
- if ("error_uri" in params) {
635
- if (typeof params.error_uri === "string" || params.error_uri === undefined) {
636
- result.error_uri = params.error_uri;
637
- } else {
638
- issues.push({
639
- message: "error_uri must be a string",
640
- path: ["error_uri"]
641
- });
642
- }
643
- }
644
- }
645
- if ("iss" in params) {
646
- if (typeof params.iss === "string" || params.iss === undefined) {
647
- result.iss = params.iss;
648
- } else {
649
- issues.push({
650
- message: "iss must be a string",
651
- path: ["iss"]
652
- });
653
- }
2985
+ class InMemoryTenantStore {
2986
+ tenants = new Map;
2987
+ companyIdIndex = new Map;
2988
+ async get(appId) {
2989
+ return this.tenants.get(appId) ?? null;
2990
+ }
2991
+ async getByCompanyId(companyId) {
2992
+ const appIds = this.companyIdIndex.get(companyId);
2993
+ if (!appIds || appIds.size === 0)
2994
+ return [];
2995
+ const tenants = [];
2996
+ for (const appId of appIds) {
2997
+ const tenant = this.tenants.get(appId);
2998
+ if (tenant) {
2999
+ tenants.push(tenant);
3000
+ }
3001
+ }
3002
+ return tenants;
3003
+ }
3004
+ async list() {
3005
+ return Array.from(this.tenants.values());
3006
+ }
3007
+ async upsert(tenant) {
3008
+ const existing = this.tenants.get(tenant.appId);
3009
+ if (existing && existing.companyId !== tenant.companyId) {
3010
+ const oldAppIds = this.companyIdIndex.get(existing.companyId);
3011
+ if (oldAppIds) {
3012
+ oldAppIds.delete(tenant.appId);
3013
+ if (oldAppIds.size === 0) {
3014
+ this.companyIdIndex.delete(existing.companyId);
654
3015
  }
655
- if (issues.length > 0) {
656
- return { issues };
3016
+ }
3017
+ }
3018
+ this.tenants.set(tenant.appId, tenant);
3019
+ let appIds = this.companyIdIndex.get(tenant.companyId);
3020
+ if (!appIds) {
3021
+ appIds = new Set;
3022
+ this.companyIdIndex.set(tenant.companyId, appIds);
3023
+ }
3024
+ appIds.add(tenant.appId);
3025
+ return tenant;
3026
+ }
3027
+ async delete(appId) {
3028
+ const tenant = this.tenants.get(appId);
3029
+ if (tenant) {
3030
+ const appIds = this.companyIdIndex.get(tenant.companyId);
3031
+ if (appIds) {
3032
+ appIds.delete(appId);
3033
+ if (appIds.size === 0) {
3034
+ this.companyIdIndex.delete(tenant.companyId);
657
3035
  }
658
- return { value: result };
659
3036
  }
660
3037
  }
661
- };
3038
+ this.tenants.delete(appId);
3039
+ }
3040
+ }
3041
+ // packages/react/src/workload-server.ts
3042
+ function getWorkloadInstance(config) {
3043
+ const es = getES(config?.es);
3044
+ if (!es.workload) {
3045
+ console.error("Workload authentication not configured in EnterpriseStandard");
3046
+ return;
3047
+ }
3048
+ return es.workload;
3049
+ }
3050
+ function unavailable() {
3051
+ return new Response(JSON.stringify({ error: "Workload authentication unavailable" }), {
3052
+ status: 503,
3053
+ statusText: "Workload authentication unavailable",
3054
+ headers: { "Content-Type": "application/json" }
3055
+ });
3056
+ }
3057
+ async function getWorkload(request, config) {
3058
+ const workloadAuth = getWorkloadInstance(config);
3059
+ if (!workloadAuth) {
3060
+ return;
3061
+ }
3062
+ return workloadAuth.getWorkload(request);
3063
+ }
3064
+ async function getWorkloadToken(scope, config) {
3065
+ const workloadAuth = getWorkloadInstance(config);
3066
+ if (!workloadAuth)
3067
+ throw unavailable();
3068
+ return workloadAuth.getToken(scope);
3069
+ }
3070
+ async function validateWorkloadToken(request, config) {
3071
+ const workloadAuth = getWorkloadInstance(config);
3072
+ if (!workloadAuth) {
3073
+ return { valid: false, error: "Workload authentication unavailable" };
3074
+ }
3075
+ const authHeader = request.headers.get("Authorization");
3076
+ if (!authHeader || !authHeader.startsWith("Bearer ")) {
3077
+ return { valid: false, error: "Missing or invalid Authorization header" };
3078
+ }
3079
+ const token = authHeader.substring(7);
3080
+ return workloadAuth.validateToken(token);
3081
+ }
3082
+ async function revokeWorkloadToken(token, config) {
3083
+ const workloadAuth = getWorkloadInstance(config);
3084
+ if (!workloadAuth)
3085
+ throw unavailable();
3086
+ return workloadAuth.revokeToken(token);
3087
+ }
3088
+ async function workloadHandler(request, config) {
3089
+ const workloadAuth = getWorkloadInstance(config);
3090
+ if (!workloadAuth)
3091
+ throw unavailable();
3092
+ return workloadAuth.handler(request);
662
3093
  }
663
- // src/server.ts
3094
+
3095
+ // packages/react/src/server.ts
664
3096
  function getSSO(config) {
665
3097
  const es = getES(config?.es);
666
3098
  if (!es.sso) {
@@ -669,7 +3101,7 @@ function getSSO(config) {
669
3101
  }
670
3102
  return es.sso;
671
3103
  }
672
- function unavailable() {
3104
+ function unavailable2() {
673
3105
  new Response(JSON.stringify({ error: "SSO Unavailable" }), {
674
3106
  status: 503,
675
3107
  statusText: "SSO Unavailable",
@@ -682,60 +3114,84 @@ async function getUser(request, config) {
682
3114
  async function getRequiredUser(request, config) {
683
3115
  const sso2 = getSSO(config);
684
3116
  if (!sso2)
685
- throw unavailable();
3117
+ throw unavailable2();
686
3118
  return sso2.getRequiredUser(request);
687
3119
  }
688
3120
  async function initiateLogin(config) {
689
3121
  const sso2 = getSSO(config);
690
3122
  if (!sso2)
691
- throw unavailable();
3123
+ throw unavailable2();
692
3124
  return sso2.initiateLogin(config);
693
3125
  }
694
3126
  async function callback(request, config) {
695
3127
  const sso2 = getSSO(config);
696
3128
  if (!sso2)
697
- throw unavailable();
3129
+ throw unavailable2();
698
3130
  return sso2.callbackHandler(request);
699
3131
  }
700
3132
  async function handler(request, config) {
701
3133
  const sso2 = getSSO(config);
702
3134
  if (!sso2)
703
- throw unavailable();
704
- return sso2.handler(request, config);
3135
+ throw unavailable2();
3136
+ return sso2.handler(request);
3137
+ }
3138
+ // packages/react/src/session-store.ts
3139
+ class InMemorySessionStore {
3140
+ sessions = new Map;
3141
+ async create(session) {
3142
+ if (this.sessions.has(session.sid)) {
3143
+ throw new Error(`Session with sid ${session.sid} already exists`);
3144
+ }
3145
+ this.sessions.set(session.sid, session);
3146
+ }
3147
+ async get(sid) {
3148
+ return this.sessions.get(sid) ?? null;
3149
+ }
3150
+ async update(sid, data) {
3151
+ const session = this.sessions.get(sid);
3152
+ if (!session) {
3153
+ throw new Error(`Session with sid ${sid} not found`);
3154
+ }
3155
+ const updated = { ...session, ...data };
3156
+ this.sessions.set(sid, updated);
3157
+ }
3158
+ async delete(sid) {
3159
+ this.sessions.delete(sid);
3160
+ }
705
3161
  }
706
- // src/ui/sign-in-loading.tsx
707
- import { jsx, Fragment } from "react/jsx-runtime";
3162
+ // packages/react/src/ui/sign-in-loading.tsx
3163
+ import { jsxDEV, Fragment } from "react/jsx-dev-runtime";
708
3164
  function SignInLoading({ complete = false, children }) {
709
3165
  const { isLoading } = useUser();
710
3166
  if (isLoading && !complete)
711
- return /* @__PURE__ */ jsx(Fragment, {
3167
+ return /* @__PURE__ */ jsxDEV(Fragment, {
712
3168
  children
713
- });
3169
+ }, undefined, false, undefined, this);
714
3170
  return null;
715
3171
  }
716
- // src/ui/signed-in.tsx
717
- import { jsx as jsx2, Fragment as Fragment2 } from "react/jsx-runtime";
3172
+ // packages/react/src/ui/signed-in.tsx
3173
+ import { jsxDEV as jsxDEV2, Fragment as Fragment2 } from "react/jsx-dev-runtime";
718
3174
  function SignedIn({ children }) {
719
3175
  const { user } = useUser();
720
3176
  if (user)
721
- return /* @__PURE__ */ jsx2(Fragment2, {
3177
+ return /* @__PURE__ */ jsxDEV2(Fragment2, {
722
3178
  children
723
- });
3179
+ }, undefined, false, undefined, this);
724
3180
  return null;
725
3181
  }
726
- // src/ui/signed-out.tsx
727
- import { jsx as jsx3, Fragment as Fragment3 } from "react/jsx-runtime";
3182
+ // packages/react/src/ui/signed-out.tsx
3183
+ import { jsxDEV as jsxDEV3, Fragment as Fragment3 } from "react/jsx-dev-runtime";
728
3184
  function SignedOut({ children }) {
729
3185
  const { user, isLoading } = useUser();
730
3186
  if (user || isLoading)
731
3187
  return null;
732
- return /* @__PURE__ */ jsx3(Fragment3, {
3188
+ return /* @__PURE__ */ jsxDEV3(Fragment3, {
733
3189
  children
734
- });
3190
+ }, undefined, false, undefined, this);
735
3191
  }
736
- // src/ui/sso-provider.tsx
3192
+ // packages/react/src/ui/sso-provider.tsx
737
3193
  import { createContext, useCallback, useContext, useEffect, useState } from "react";
738
- import { jsx as jsx4 } from "react/jsx-runtime";
3194
+ import { jsxDEV as jsxDEV4 } from "react/jsx-dev-runtime";
739
3195
  var CTX = createContext(undefined);
740
3196
  var generateStorageKey = (tenantId) => {
741
3197
  return `es-sso-user-${tenantId.replace(/[^a-zA-Z0-9]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "")}`;
@@ -875,10 +3331,10 @@ function SSOProvider({
875
3331
  tokenUrl,
876
3332
  refreshUrl
877
3333
  };
878
- return /* @__PURE__ */ jsx4(CTX.Provider, {
3334
+ return /* @__PURE__ */ jsxDEV4(CTX.Provider, {
879
3335
  value: contextValue,
880
3336
  children
881
- });
3337
+ }, undefined, false, undefined, this);
882
3338
  }
883
3339
  function useUser() {
884
3340
  const context = useContext(CTX);
@@ -999,38 +3455,146 @@ async function logout(logoutUrl) {
999
3455
  };
1000
3456
  }
1001
3457
  }
3458
+ // packages/react/src/user-store.ts
3459
+ class InMemoryUserStore {
3460
+ users = new Map;
3461
+ emailIndex = new Map;
3462
+ userNameIndex = new Map;
3463
+ async get(sub) {
3464
+ return this.users.get(sub) ?? null;
3465
+ }
3466
+ async getByEmail(email) {
3467
+ const sub = this.emailIndex.get(email.toLowerCase());
3468
+ if (!sub)
3469
+ return null;
3470
+ return this.users.get(sub) ?? null;
3471
+ }
3472
+ async getByUserName(userName) {
3473
+ const sub = this.userNameIndex.get(userName.toLowerCase());
3474
+ if (!sub)
3475
+ return null;
3476
+ return this.users.get(sub) ?? null;
3477
+ }
3478
+ async upsert(user) {
3479
+ const existing = this.users.get(user.id);
3480
+ if (existing) {
3481
+ if (existing.email && existing.email.toLowerCase() !== user.email?.toLowerCase()) {
3482
+ this.emailIndex.delete(existing.email.toLowerCase());
3483
+ }
3484
+ if (existing.userName && existing.userName.toLowerCase() !== user.userName?.toLowerCase()) {
3485
+ this.userNameIndex.delete(existing.userName.toLowerCase());
3486
+ }
3487
+ }
3488
+ this.users.set(user.id, user);
3489
+ if (user.email) {
3490
+ this.emailIndex.set(user.email.toLowerCase(), user.id);
3491
+ }
3492
+ if (user.userName) {
3493
+ this.userNameIndex.set(user.userName.toLowerCase(), user.id);
3494
+ }
3495
+ }
3496
+ async delete(sub) {
3497
+ const user = this.users.get(sub);
3498
+ if (user) {
3499
+ if (user.email) {
3500
+ this.emailIndex.delete(user.email.toLowerCase());
3501
+ }
3502
+ if (user.userName) {
3503
+ this.userNameIndex.delete(user.userName.toLowerCase());
3504
+ }
3505
+ }
3506
+ this.users.delete(sub);
3507
+ }
3508
+ }
1002
3509
 
1003
- // src/index.ts
1004
- async function enterpriseStandard(appId, appKey, initConfig) {
1005
- let vaultUrl;
1006
- let vaultToken;
1007
- let secrets;
1008
- const ioniteUrl = initConfig?.ioniteUrl ?? "https://ionite.com";
1009
- if (appId === "IONITE_PUBLIC_DEMO") {
3510
+ // packages/react/src/index.ts
3511
+ function extractSsoValidation(validation) {
3512
+ if (!validation)
3513
+ return;
3514
+ const val = validation;
3515
+ if (val.callbackParams || val.idTokenClaims || val.tokenResponse) {
3516
+ return val;
3517
+ }
3518
+ if (typeof val === "object" && "sso" in val) {
3519
+ return val.sso;
3520
+ }
3521
+ return;
3522
+ }
3523
+ function extractWorkloadValidation(validation) {
3524
+ if (!validation)
3525
+ return;
3526
+ const val = validation;
3527
+ if (val.jwtAssertionClaims || val.tokenResponse) {
3528
+ return val;
3529
+ }
3530
+ if (typeof val === "object" && "workload" in val) {
3531
+ return val.workload;
3532
+ }
3533
+ return;
3534
+ }
3535
+ async function enterpriseStandard(appId, initConfig) {
3536
+ const ioniteUrl = process.env.IONITE_URL ?? "https://ionite.com";
3537
+ let vaultUrl = process.env.ES_VAULT_URL;
3538
+ const vaultToken = process.env.ES_VAULT_TOKEN;
3539
+ const vaultPath = process.env.ES_VAULT_PATH;
3540
+ let secret;
3541
+ if (appId?.startsWith("IONITE_PUBLIC_DEMO_") && !vaultUrl) {
1010
3542
  vaultUrl = "https://vault-ionite.ionite.dev/v1/secret/data";
1011
- secrets = {
1012
- sso: {
1013
- path: "public/IONITE_PUBLIC_DEMO_SSO",
1014
- token: "hvs.VGhD2hmXDH9PmZjTacZx0G5K"
1015
- }
3543
+ secret = {
3544
+ path: `public/${appId}`,
3545
+ token: "hvs.VGhD2hmXDH9PmZjTacZx0G5K"
1016
3546
  };
1017
- } else if (appKey) {
1018
- if (!vaultUrl || !vaultToken) {
1019
- throw new Error("TODO something is wrong with the ionite config, handle this error");
1020
- }
1021
- secrets = {};
1022
- } else {
1023
- throw new Error("TODO tell them how to connect to ionite");
3547
+ } else if (vaultUrl && vaultToken && vaultPath) {
3548
+ secret = {
3549
+ path: vaultPath,
3550
+ token: vaultToken
3551
+ };
3552
+ } else if (!vaultUrl || !vaultToken || !vaultPath) {
3553
+ const cmd = `${process.versions.bun ? "bun" : "npm"} ionite login --app ${appId}`;
3554
+ throw new Error(`@enterprisestandard configuration missing.
3555
+ For development, login with the ionite CLI using "${cmd}" or visit ${ioniteUrl}/api/applications/apiKeys/create?appId=${appId}. If this is a non-development environment, ensure that you are deployed with the correct tenant pattern.`);
1024
3556
  }
1025
3557
  const defaultInstance2 = getDefaultInstance();
1026
- const vaultClient = await vault(vaultUrl);
3558
+ const vaultClient = vault(vaultUrl);
3559
+ const ssoValidation = extractSsoValidation(initConfig?.validation);
3560
+ const workloadValidation = extractWorkloadValidation(initConfig?.validation);
3561
+ let vaultData = {};
3562
+ if (secret) {
3563
+ vaultData = await vaultClient.getSecret(secret.path, secret.token);
3564
+ }
3565
+ const workloadConfig = {
3566
+ ...vaultData.workload,
3567
+ ...initConfig?.workload,
3568
+ jwks_uri: initConfig?.workload?.jwks_uri ?? vaultData.workload?.jwks_uri,
3569
+ token_url: initConfig?.workload?.token_url ?? vaultData.workload?.token_url,
3570
+ validation: initConfig?.workload?.validation ?? workloadValidation
3571
+ };
3572
+ const workloadInstance = workload(workloadConfig);
3573
+ const ssoConfig = {
3574
+ ...vaultData.sso,
3575
+ ...initConfig?.sso,
3576
+ validation: initConfig?.sso?.validation ?? ssoValidation
3577
+ };
3578
+ const ssoInstance = sso(ssoConfig);
3579
+ const iamConfig = {
3580
+ ...vaultData.iam,
3581
+ ...initConfig?.iam
3582
+ };
3583
+ const iamInstance = iam(iamConfig, workloadInstance);
3584
+ const mergedConfig = {
3585
+ defaultInstance: initConfig?.defaultInstance,
3586
+ sso: ssoConfig,
3587
+ iam: iamConfig,
3588
+ workload: workloadConfig,
3589
+ validation: initConfig?.validation
3590
+ };
1027
3591
  const result = {
1028
- appId,
1029
- ioniteUrl,
3592
+ ...mergedConfig,
1030
3593
  defaultInstance: initConfig?.defaultInstance || initConfig?.defaultInstance !== false && !defaultInstance2,
1031
3594
  vault: vaultClient,
1032
- sso: secrets.sso ? sso(await vaultClient.getSecret(secrets.sso.path, secrets.sso.token)) : undefined,
1033
- iam: secrets.iam ? await iam(await vaultClient.getSecret(secrets.iam.path, secrets.iam.token)) : undefined
3595
+ sso: ssoInstance,
3596
+ iam: iamInstance,
3597
+ workload: workloadInstance
1034
3598
  };
1035
3599
  if (result.defaultInstance) {
1036
3600
  if (defaultInstance2) {
@@ -1041,18 +3605,46 @@ async function enterpriseStandard(appId, appKey, initConfig) {
1041
3605
  return result;
1042
3606
  }
1043
3607
  export {
3608
+ workloadTokenResponseSchema,
3609
+ workloadHandler,
3610
+ workload,
3611
+ vault,
3612
+ validateWorkloadToken,
3613
+ userSchema,
1044
3614
  useUser,
1045
3615
  useToken,
3616
+ tokenResponseSchema,
3617
+ sso,
3618
+ serializeESConfig,
3619
+ sendTenantWebhook,
3620
+ revokeWorkloadToken,
3621
+ parseTenantRequest,
1046
3622
  oidcCallbackSchema,
1047
3623
  logout,
3624
+ jwtAssertionClaimsSchema,
1048
3625
  initiateLogin,
3626
+ idTokenClaimsSchema,
3627
+ iam,
1049
3628
  handler,
3629
+ groupResourceSchema,
3630
+ getWorkloadToken,
3631
+ getWorkload,
1050
3632
  getUser,
1051
3633
  getRequiredUser,
3634
+ getES,
3635
+ getDefaultInstance,
1052
3636
  enterpriseStandard,
1053
3637
  callback,
3638
+ TenantRequestError,
1054
3639
  SignedOut,
1055
3640
  SignedIn,
1056
3641
  SignInLoading,
1057
- SSOProvider
3642
+ SSOProvider,
3643
+ InMemoryWorkloadTokenStore,
3644
+ InMemoryUserStore,
3645
+ InMemoryTenantStore,
3646
+ InMemorySessionStore,
3647
+ InMemoryGroupStore
1058
3648
  };
3649
+
3650
+ //# debugId=BB2BA44916C99DF364756E2164756E21