@enterprisestandard/react 0.0.5-beta.20251125.1 → 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 (48) 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 -5
  4. package/dist/iam.d.ts.map +1 -1
  5. package/dist/index.d.ts +41 -13
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +2837 -555
  8. package/dist/index.js.map +17 -9
  9. package/dist/server.d.ts +6 -4
  10. package/dist/server.d.ts.map +1 -1
  11. package/dist/session-store.d.ts +3 -3
  12. package/dist/session-store.d.ts.map +1 -1
  13. package/dist/sso.d.ts +58 -10
  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/types/oidc-schema.d.ts.map +1 -0
  24. package/dist/{scim-schema.d.ts → types/scim-schema.d.ts} +66 -3
  25. package/dist/types/scim-schema.d.ts.map +1 -0
  26. package/dist/types/standard-schema.d.ts.map +1 -0
  27. package/dist/types/user.d.ts +41 -0
  28. package/dist/types/user.d.ts.map +1 -0
  29. package/dist/types/workload-schema.d.ts +106 -0
  30. package/dist/types/workload-schema.d.ts.map +1 -0
  31. package/dist/ui/sso-provider.d.ts +3 -3
  32. package/dist/ui/sso-provider.d.ts.map +1 -1
  33. package/dist/user-store.d.ts +161 -0
  34. package/dist/user-store.d.ts.map +1 -0
  35. package/dist/workload-server.d.ts +126 -0
  36. package/dist/workload-server.d.ts.map +1 -0
  37. package/dist/workload-token-store.d.ts +187 -0
  38. package/dist/workload-token-store.d.ts.map +1 -0
  39. package/dist/workload.d.ts +227 -0
  40. package/dist/workload.d.ts.map +1 -0
  41. package/package.json +1 -2
  42. package/dist/enterprise-user.d.ts +0 -126
  43. package/dist/enterprise-user.d.ts.map +0 -1
  44. package/dist/oidc-schema.d.ts.map +0 -1
  45. package/dist/scim-schema.d.ts.map +0 -1
  46. package/dist/standard-schema.d.ts.map +0 -1
  47. /package/dist/{oidc-schema.d.ts → types/oidc-schema.d.ts} +0 -0
  48. /package/dist/{standard-schema.d.ts → types/standard-schema.d.ts} +0 -0
package/dist/index.js CHANGED
@@ -1,10 +1,288 @@
1
- // packages/react/src/iam.ts
2
- async function iam(config) {
3
- return {};
1
+ // packages/react/src/types/scim-schema.ts
2
+ function validateString(value, fieldName, required, issues, path) {
3
+ if (value === undefined || value === null) {
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;
18
+ }
19
+ return value;
4
20
  }
5
-
6
- // packages/react/src/oidc-schema.ts
7
- function oidcCallbackSchema(vendor) {
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;
33
+ }
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;
60
+ }
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;
95
+ }
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;
117
+ }
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
+ });
127
+ }
128
+ }
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
139
+ });
140
+ return;
141
+ }
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;
152
+ }
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"])
166
+ });
167
+ }
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;
191
+ }
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"])
200
+ });
201
+ }
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
224
+ });
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"])
235
+ });
236
+ }
237
+ }
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"]
269
+ });
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) {
8
286
  return {
9
287
  "~standard": {
10
288
  version: 1,
@@ -19,81 +297,62 @@ function oidcCallbackSchema(vendor) {
19
297
  ]
20
298
  };
21
299
  }
22
- const params = value;
300
+ const user = value;
23
301
  const issues = [];
24
302
  const result = {};
25
- if ("code" in params) {
26
- if (typeof params.code === "string") {
27
- result.code = params.code;
28
- } else {
29
- issues.push({
30
- message: "code must be a string",
31
- path: ["code"]
32
- });
33
- }
34
- } else if (!("error" in params)) {
35
- issues.push({
36
- message: "code is required",
37
- path: ["code"]
38
- });
39
- }
40
- if ("state" in params) {
41
- if (typeof params.state === "string" || params.state === undefined) {
42
- result.state = params.state;
43
- } else {
44
- issues.push({
45
- message: "state must be a string",
46
- path: ["state"]
47
- });
48
- }
303
+ const userName = validateString(user.userName, "userName", true, issues, ["userName"]);
304
+ if (!userName) {
305
+ return { issues };
49
306
  }
50
- if ("session_state" in params) {
51
- if (typeof params.session_state === "string" || params.session_state === undefined) {
52
- result.session_state = params.session_state;
53
- } else {
54
- issues.push({
55
- message: "session_state must be a string",
56
- path: ["session_state"]
57
- });
58
- }
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]);
59
331
  }
60
- if ("error" in params) {
61
- if (typeof params.error === "string") {
62
- result.error = params.error;
332
+ if (user.schemas !== undefined) {
333
+ if (Array.isArray(user.schemas)) {
334
+ result.schemas = user.schemas.filter((s) => typeof s === "string");
63
335
  } else {
64
336
  issues.push({
65
- message: "error must be a string",
66
- path: ["error"]
337
+ message: "schemas must be an array",
338
+ path: ["schemas"]
67
339
  });
68
340
  }
69
- if ("error_description" in params) {
70
- if (typeof params.error_description === "string" || params.error_description === undefined) {
71
- result.error_description = params.error_description;
72
- } else {
73
- issues.push({
74
- message: "error_description must be a string",
75
- path: ["error_description"]
76
- });
77
- }
78
- }
79
- if ("error_uri" in params) {
80
- if (typeof params.error_uri === "string" || params.error_uri === undefined) {
81
- result.error_uri = params.error_uri;
82
- } else {
83
- issues.push({
84
- message: "error_uri must be a string",
85
- path: ["error_uri"]
86
- });
87
- }
88
- }
89
341
  }
90
- if ("iss" in params) {
91
- if (typeof params.iss === "string" || params.iss === undefined) {
92
- result.iss = params.iss;
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
+ };
93
352
  } else {
94
353
  issues.push({
95
- message: "iss must be a string",
96
- path: ["iss"]
354
+ message: "meta must be an object",
355
+ path: ["meta"]
97
356
  });
98
357
  }
99
358
  }
@@ -105,7 +364,43 @@ function oidcCallbackSchema(vendor) {
105
364
  }
106
365
  };
107
366
  }
108
- function tokenResponseSchema(vendor) {
367
+ function validateMembers(value, issues, basePath) {
368
+ if (value === undefined || value === null) {
369
+ return;
370
+ }
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) {
109
404
  return {
110
405
  "~standard": {
111
406
  version: 1,
@@ -120,123 +415,588 @@ function tokenResponseSchema(vendor) {
120
415
  ]
121
416
  };
122
417
  }
123
- const response = value;
418
+ const group = value;
124
419
  const issues = [];
125
420
  const result = {};
126
- if ("access_token" in response) {
127
- if (typeof response.access_token === "string") {
128
- result.access_token = response.access_token;
129
- } else {
130
- issues.push({
131
- message: "access_token must be a string",
132
- path: ["access_token"]
133
- });
134
- }
135
- } else {
136
- issues.push({
137
- message: "access_token is required",
138
- path: ["access_token"]
139
- });
140
- }
141
- if ("id_token" in response) {
142
- if (typeof response.id_token === "string") {
143
- result.id_token = response.id_token;
144
- } else {
145
- issues.push({
146
- message: "id_token must be a string",
147
- path: ["id_token"]
148
- });
149
- }
150
- } else {
151
- issues.push({
152
- message: "id_token is required",
153
- path: ["id_token"]
154
- });
155
- }
156
- if ("token_type" in response) {
157
- if (typeof response.token_type === "string") {
158
- result.token_type = response.token_type;
159
- } else {
160
- issues.push({
161
- message: "token_type must be a string",
162
- path: ["token_type"]
163
- });
164
- }
165
- } else {
166
- issues.push({
167
- message: "token_type is required",
168
- path: ["token_type"]
169
- });
170
- }
171
- if ("refresh_token" in response) {
172
- if (typeof response.refresh_token === "string" || response.refresh_token === undefined) {
173
- result.refresh_token = response.refresh_token;
174
- } else {
175
- issues.push({
176
- message: "refresh_token must be a string",
177
- path: ["refresh_token"]
178
- });
179
- }
421
+ const displayName = validateString(group.displayName, "displayName", true, issues, ["displayName"]);
422
+ if (!displayName) {
423
+ return { issues };
180
424
  }
181
- if ("scope" in response) {
182
- if (typeof response.scope === "string" || response.scope === undefined) {
183
- result.scope = response.scope;
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");
184
432
  } else {
185
433
  issues.push({
186
- message: "scope must be a string",
187
- path: ["scope"]
434
+ message: "schemas must be an array",
435
+ path: ["schemas"]
188
436
  });
189
437
  }
190
438
  }
191
- if ("session_state" in response) {
192
- if (typeof response.session_state === "string" || response.session_state === undefined) {
193
- result.session_state = response.session_state;
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
+ };
194
449
  } else {
195
450
  issues.push({
196
- message: "session_state must be a string",
197
- path: ["session_state"]
451
+ message: "meta must be an object",
452
+ path: ["meta"]
198
453
  });
199
454
  }
200
455
  }
201
- if ("expires" in response) {
202
- if (typeof response.expires === "string" || response.expires === undefined) {
203
- result.expires = response.expires;
204
- } else {
205
- issues.push({
206
- message: "expires must be a string",
207
- path: ["expires"]
208
- });
209
- }
456
+ if (issues.length > 0) {
457
+ return { issues };
210
458
  }
211
- if ("expires_in" in response) {
212
- if (typeof response.expires_in === "number" || response.expires_in === undefined) {
213
- result.expires_in = response.expires_in;
214
- } else {
215
- issues.push({
216
- message: "expires_in must be a number",
217
- path: ["expires_in"]
218
- });
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"
531
+ },
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"
583
+ },
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");
219
671
  }
220
672
  }
221
- if ("refresh_expires_in" in response) {
222
- if (typeof response.refresh_expires_in === "number" || response.refresh_expires_in === undefined) {
223
- result.refresh_expires_in = response.refresh_expires_in;
224
- } else {
225
- issues.push({
226
- message: "refresh_expires_in must be a number",
227
- path: ["refresh_expires_in"]
228
- });
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
+ }
229
748
  }
230
749
  }
231
- if (issues.length > 0) {
232
- return { issues };
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()
233
802
  }
234
- return { value: result };
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");
235
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 });
236
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
237
995
  };
238
996
  }
239
- function idTokenClaimsSchema(vendor) {
997
+
998
+ // packages/react/src/types/oidc-schema.ts
999
+ function oidcCallbackSchema(vendor) {
240
1000
  return {
241
1001
  "~standard": {
242
1002
  version: 1,
@@ -251,31 +1011,84 @@ function idTokenClaimsSchema(vendor) {
251
1011
  ]
252
1012
  };
253
1013
  }
254
- const claims = value;
1014
+ const params = value;
255
1015
  const issues = [];
256
- const result = { ...claims };
257
- const stringFields = ["iss", "aud", "sub", "sid", "name", "email", "preferred_username", "picture"];
258
- for (const field of stringFields) {
259
- if (field in claims && claims[field] !== undefined) {
260
- if (typeof claims[field] !== "string") {
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 {
261
1065
  issues.push({
262
- message: `${field} must be a string`,
263
- path: [field]
1066
+ message: "error_description must be a string",
1067
+ path: ["error_description"]
264
1068
  });
265
1069
  }
266
1070
  }
267
- }
268
- const numberFields = ["exp", "iat"];
269
- for (const field of numberFields) {
270
- if (field in claims && claims[field] !== undefined) {
271
- if (typeof claims[field] !== "number") {
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 {
272
1075
  issues.push({
273
- message: `${field} must be a number`,
274
- path: [field]
1076
+ message: "error_uri must be a string",
1077
+ path: ["error_uri"]
275
1078
  });
276
1079
  }
277
1080
  }
278
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
+ }
279
1092
  if (issues.length > 0) {
280
1093
  return { issues };
281
1094
  }
@@ -284,50 +1097,241 @@ function idTokenClaimsSchema(vendor) {
284
1097
  }
285
1098
  };
286
1099
  }
287
-
288
- // packages/react/src/utils.ts
289
- var defaultInstance;
290
- function must(value, message = "Assertion failed. Required value is null or undefined.") {
291
- if (value === undefined || value === null) {
292
- throw new Error(message);
293
- }
294
- return value;
295
- }
296
- function setDefaultInstance(es) {
297
- defaultInstance = es;
298
- }
299
- function getDefaultInstance() {
300
- return defaultInstance;
301
- }
302
- function getES(es) {
303
- if (es)
304
- return es;
305
- if (defaultInstance)
306
- return defaultInstance;
307
- throw new Error(`TODO standardize the error message when there isn't a default EntepriseStandard`);
308
- }
309
-
310
- // packages/react/src/sso.ts
311
- var jwksCache = new Map;
312
- function sso(config) {
313
- const configWithDefaults = {
314
- ...config,
315
- authority: must(config.authority, "Missing 'authority' from SSO Config"),
316
- token_url: must(config.token_url, "Missing 'token_url' from SSO Config"),
317
- authorization_url: must(config.authorization_url, "Missing 'authorization_url' from SSO Config"),
318
- client_id: must(config.client_id, "Missing 'client_id' from SSO Config"),
319
- redirect_uri: must(config.redirect_uri, "Missing 'redirect_uri' from SSO Config"),
320
- scope: must(config.scope, "Missing 'scope' from SSO Config"),
321
- response_type: config.response_type ?? "code",
322
- cookies_secure: config.cookies_secure !== undefined ? config.cookies_secure : true,
323
- cookies_same_site: config.cookies_same_site !== undefined ? config.cookies_same_site : "Strict",
324
- cookies_prefix: config.cookies_prefix ?? `es.sso.${config.client_id}`,
325
- cookies_path: config.cookies_path ?? "/"
326
- };
327
- async function getUser(request) {
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) {
328
1333
  if (!configWithDefaults) {
329
- console.error("SSO Manager not initialized");
330
- return;
1334
+ throw new Error("Enterprise Standard SSO Manager not initialized");
331
1335
  }
332
1336
  try {
333
1337
  const { tokens } = await getTokenFromCookies(request);
@@ -348,16 +1352,35 @@ function sso(config) {
348
1352
  statusText: "Unauthorized"
349
1353
  });
350
1354
  }
351
- async function initiateLogin({ landingUrl, errorUrl }) {
1355
+ async function initiateLogin({ landingUrl, errorUrl }, requestUrl) {
352
1356
  if (!configWithDefaults) {
353
- console.error("SSO Manager not initialized");
354
- return Promise.resolve(new Response("SSO Manager not initialized", { status: 503 }));
1357
+ throw new Error("Enterprise Standard SSO Manager not initialized");
355
1358
  }
356
1359
  const state = generateRandomString();
357
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
+ }
358
1381
  const url = new URL(configWithDefaults.authorization_url);
359
1382
  url.searchParams.append("client_id", configWithDefaults.client_id);
360
- url.searchParams.append("redirect_uri", configWithDefaults.redirect_uri);
1383
+ url.searchParams.append("redirect_uri", normalizedRedirectUri);
361
1384
  url.searchParams.append("response_type", "code");
362
1385
  url.searchParams.append("scope", configWithDefaults.scope);
363
1386
  url.searchParams.append("state", state);
@@ -379,6 +1402,9 @@ function sso(config) {
379
1402
  });
380
1403
  }
381
1404
  async function logout(request, _config) {
1405
+ if (!configWithDefaults) {
1406
+ throw new Error("Enterprise Standard SSO Manager not initialized");
1407
+ }
382
1408
  try {
383
1409
  const refreshToken2 = getCookie("refresh", request);
384
1410
  if (refreshToken2) {
@@ -387,12 +1413,12 @@ function sso(config) {
387
1413
  } catch (error) {
388
1414
  console.warn("Failed to revoke token:", error);
389
1415
  }
390
- if (config.session_store) {
1416
+ if (configWithDefaults.session_store) {
391
1417
  try {
392
1418
  const user = await getUser(request);
393
1419
  if (user?.sso?.profile.sid) {
394
1420
  const sid = user.sso.profile.sid;
395
- await config.session_store.delete(sid);
1421
+ await configWithDefaults.session_store.delete(sid);
396
1422
  console.log(`Session ${sid} deleted from store`);
397
1423
  }
398
1424
  } catch (error) {
@@ -437,11 +1463,11 @@ function sso(config) {
437
1463
  }
438
1464
  }
439
1465
  async function logoutBackChannel(request) {
1466
+ if (!configWithDefaults) {
1467
+ throw new Error("Enterprise Standard SSO Manager not initialized");
1468
+ }
440
1469
  if (!configWithDefaults.session_store) {
441
- return new Response("Back-Channel Logout requires session_store configuration", {
442
- status: 400,
443
- statusText: "Bad Request"
444
- });
1470
+ throw new Error("Back-Channel Logout requires session_store configuration");
445
1471
  }
446
1472
  try {
447
1473
  const contentType = request.headers.get("content-type");
@@ -472,8 +1498,7 @@ function sso(config) {
472
1498
  }
473
1499
  async function callbackHandler(request, validation) {
474
1500
  if (!configWithDefaults) {
475
- console.error("SSO Manager not initialized");
476
- return Promise.resolve(new Response("SSO Manager not initialized", { status: 503 }));
1501
+ throw new Error("Enterprise Standard SSO Manager not initialized");
477
1502
  }
478
1503
  const url = new URL(request.url);
479
1504
  const params = new URLSearchParams(url.search);
@@ -503,9 +1528,9 @@ function sso(config) {
503
1528
  if (stateFromUrl !== state) {
504
1529
  throw new Error('SSO State Verifier failed, the "state" request parameter does not equal the "state" in the SSO cookie');
505
1530
  }
506
- const tokenResponse = await exchangeCodeForToken(codeFromUrl, codeVerifier, validation);
1531
+ const tokenResponse = await exchangeCodeForToken(codeFromUrl, codeVerifier, validation, request.url);
507
1532
  const user = await parseUser(tokenResponse, validation);
508
- if (config.session_store) {
1533
+ if (configWithDefaults.session_store) {
509
1534
  try {
510
1535
  const sid = user.sso.profile.sid;
511
1536
  const sub = user.id;
@@ -516,7 +1541,7 @@ function sso(config) {
516
1541
  createdAt: new Date,
517
1542
  lastActivityAt: new Date
518
1543
  };
519
- await config.session_store.create(session);
1544
+ await configWithDefaults.session_store.create(session);
520
1545
  } else {
521
1546
  console.warn("Session creation skipped: missing sid or sub in ID token claims");
522
1547
  }
@@ -524,6 +1549,31 @@ function sso(config) {
524
1549
  console.warn("Failed to create session:", error);
525
1550
  }
526
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
+ }
527
1577
  return new Response("Authentication successful, redirecting", {
528
1578
  status: 302,
529
1579
  headers: [
@@ -543,61 +1593,843 @@ function sso(config) {
543
1593
  headers: [["Location", errorUrl]]
544
1594
  });
545
1595
  }
546
- } catch (_err) {
547
- console.warn("Error parsing the errorUrl from the OIDC cookie");
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 };
548
2237
  }
549
- console.warn("No error page was found in the cookies. The user will be shown a default error page.");
550
- return new Response("An error occurred during authentication, please return to the application homepage and try again.", {
551
- status: 500
552
- });
553
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);
554
2247
  }
555
- async function parseUser(token, validation) {
556
- if (!configWithDefaults)
557
- throw new Error("SSO Manager not initialized");
558
- const idToken = await parseJwt(token.id_token, validation);
559
- const expiresIn = Number(token.refresh_expires_in ?? token.expires_in ?? 3600);
560
- const expires = token.expires ? new Date(token.expires) : new Date(Date.now() + expiresIn * 1000);
561
- return {
562
- id: idToken.sub,
563
- userName: idToken.preferred_username || "",
564
- name: idToken.name || "",
565
- email: idToken.email || "",
566
- emails: [
567
- {
568
- value: idToken.email || "",
569
- primary: true
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));
570
2391
  }
571
- ],
572
- avatarUrl: idToken.picture,
573
- sso: {
574
- profile: {
575
- ...idToken,
576
- iss: idToken.iss || configWithDefaults.authority,
577
- aud: idToken.aud || configWithDefaults.client_id
578
- },
579
- tenant: {
580
- id: idToken.idp || idToken.iss || configWithDefaults.authority,
581
- name: idToken.iss || configWithDefaults.authority
582
- },
583
- scope: token.scope,
584
- tokenType: token.token_type,
585
- sessionState: token.session_state,
586
- expires
587
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
588
2411
  };
2412
+ const header = {
2413
+ alg: cfg.algorithm,
2414
+ typ: "JWT",
2415
+ kid: cfg.key_id
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}`;
589
2422
  }
590
- async function exchangeCodeForToken(code, codeVerifier, validation) {
591
- if (!configWithDefaults)
592
- throw new Error("SSO Manager not initialized");
593
- const tokenUrl = configWithDefaults.token_url;
594
- const body = new URLSearchParams;
595
- body.append("grant_type", "authorization_code");
596
- body.append("code", code);
597
- body.append("redirect_uri", configWithDefaults.redirect_uri);
598
- body.append("client_id", configWithDefaults.client_id);
599
- body.append("code_verifier", codeVerifier);
600
- 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);
601
2433
  const response = await fetch(tokenUrl, {
602
2434
  method: "POST",
603
2435
  headers: {
@@ -608,30 +2440,41 @@ function sso(config) {
608
2440
  });
609
2441
  const data = await response.json();
610
2442
  if (!response.ok) {
611
- console.error("Token exchange error:", data);
612
- 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());
613
2445
  }
614
- const tokenResponseValidator = validation?.tokenResponse ?? tokenResponseSchema("builtin");
615
- const tokenResult = await tokenResponseValidator["~standard"].validate(data);
616
- if ("issues" in tokenResult) {
617
- console.error("Token response validation failed:", tokenResult.issues);
618
- throw new Error(`Token response validation failed: ${tokenResult.issues?.map((i) => i.message).join("; ")}`);
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("; ")}`);
619
2451
  }
620
- return tokenResult.value;
621
- } catch (error) {
622
- console.error("Error during token exchange:", error);
623
- throw error;
624
- }
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
+ });
625
2467
  }
626
- async function refreshToken(refreshToken2) {
2468
+ async function acquireTokenClientCredentials(scope, validation) {
2469
+ const cfg = configWithDefaults;
627
2470
  return retryWithBackoff(async () => {
628
- if (!configWithDefaults)
629
- throw new Error("SSO Manager not initialized");
630
- const tokenUrl = configWithDefaults.token_url;
2471
+ const tokenUrl = cfg.token_url;
631
2472
  const body = new URLSearchParams;
632
- body.append("grant_type", "refresh_token");
633
- body.append("refresh_token", refreshToken2);
634
- 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);
635
2478
  const response = await fetch(tokenUrl, {
636
2479
  method: "POST",
637
2480
  headers: {
@@ -642,24 +2485,110 @@ function sso(config) {
642
2485
  });
643
2486
  const data = await response.json();
644
2487
  if (!response.ok) {
645
- console.error("Token refresh error:", data);
646
- 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());
647
2490
  }
648
- 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;
649
2511
  });
650
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
+ }
651
2574
  async function revokeToken(token) {
2575
+ ensureInitialized();
652
2576
  try {
653
- if (!configWithDefaults)
654
- throw new Error("SSO Manager not initialized");
655
- if (!configWithDefaults.revocation_endpoint) {
2577
+ if (!config.revocation_endpoint) {
656
2578
  return;
657
2579
  }
658
2580
  const body = new URLSearchParams;
659
2581
  body.append("token", token);
660
- body.append("token_type_hint", "refresh_token");
661
- body.append("client_id", configWithDefaults.client_id);
662
- const response = await fetch(configWithDefaults.revocation_endpoint, {
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, {
663
2592
  method: "POST",
664
2593
  headers: {
665
2594
  "Content-Type": "application/x-www-form-urlencoded"
@@ -671,273 +2600,498 @@ function sso(config) {
671
2600
  } else {
672
2601
  console.log("Token revoked successfully");
673
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
+ }
674
2614
  } catch (error) {
675
2615
  console.warn("Error revoking token:", error);
676
2616
  }
677
2617
  }
678
2618
  async function fetchJwks() {
679
- const url = configWithDefaults.jwks_uri || `${configWithDefaults.authority}/protocol/openid-connect/certs`;
680
- 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);
681
2625
  if (cached)
682
2626
  return cached;
683
2627
  return retryWithBackoff(async () => {
684
- if (!configWithDefaults)
685
- throw new Error("SSO Manager not initialized");
686
2628
  const response = await fetch(url);
687
2629
  if (!response.ok)
688
2630
  throw new Error("Failed to fetch JWKS");
689
2631
  const jwks = await response.json();
690
- jwksCache.set(url, jwks);
2632
+ jwksCache2.set(url, jwks);
691
2633
  return jwks;
692
2634
  });
693
2635
  }
694
- async function retryWithBackoff(operation, maxRetries = 3, baseDelay = 1000, maxDelay = 30000) {
695
- let lastError = new Error("Placeholder Error");
696
- for (let attempt = 0;attempt <= maxRetries; attempt++) {
697
- try {
698
- return await operation();
699
- } catch (error) {
700
- lastError = error instanceof Error ? error : new Error(String(error));
701
- if (error instanceof Error && error.message.includes("400")) {
702
- throw error;
703
- }
704
- if (attempt === maxRetries) {
705
- throw lastError;
706
- }
707
- const delay = Math.min(baseDelay * 2 ** attempt, maxDelay);
708
- const jitter = Math.random() * 0.1 * delay;
709
- await new Promise((resolve) => setTimeout(resolve, delay + jitter));
710
- console.warn(`Retry attempt ${attempt + 1} after ${delay + jitter}ms delay`);
711
- }
712
- }
713
- 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"]);
714
2644
  }
715
- async function parseJwt(token, validation) {
2645
+ async function parseJWT(token, validation) {
2646
+ ensureInitialized();
716
2647
  try {
717
2648
  const parts = token.split(".");
718
2649
  if (parts.length !== 3)
719
2650
  throw new Error("Invalid JWT");
720
- const header = JSON.parse(atob(parts[0].replace(/-/g, "+").replace(/_/g, "/")));
721
- const payload = JSON.parse(atob(parts[1].replace(/-/g, "+").replace(/_/g, "/")));
722
- const signature = parts[2].replace(/-/g, "+").replace(/_/g, "/");
2651
+ const header = JSON.parse(base64UrlDecode(parts[0]));
2652
+ const payload = JSON.parse(base64UrlDecode(parts[1]));
723
2653
  const publicKey = await getPublicKey(header.kid);
2654
+ const signature = parts[2];
724
2655
  const encoder = new TextEncoder;
725
2656
  const data = encoder.encode(`${parts[0]}.${parts[1]}`);
726
- 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);
727
2660
  if (!isValid)
728
2661
  throw new Error("Invalid JWT signature");
729
- const idTokenClaimsValidator = validation?.idTokenClaims ?? idTokenClaimsSchema("builtin");
730
- const claimsResult = await idTokenClaimsValidator["~standard"].validate(payload);
731
- if ("issues" in claimsResult) {
732
- console.error("ID token claims validation failed:", claimsResult.issues);
733
- throw new Error(`ID token claims validation failed: ${claimsResult.issues?.map((i) => i.message).join("; ")}`);
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("; ")}`);
734
2667
  }
735
- return claimsResult.value;
736
- } catch (e) {
737
- console.error("Error verifying JWT:", e);
738
- throw e;
2668
+ return result.value;
2669
+ } catch (error) {
2670
+ console.error("Error verifying JWT:", error);
2671
+ throw error;
739
2672
  }
740
2673
  }
741
- function generateRandomString(length = 32) {
742
- const array = new Uint8Array(length);
743
- crypto.getRandomValues(array);
744
- return Array.from(array, (byte) => byte.toString(16).padStart(2, "0")).join("").substring(0, length);
745
- }
746
- async function pkceChallengeFromVerifier(verifier) {
747
- const encoder = new TextEncoder;
748
- const data = encoder.encode(verifier);
749
- const hashBuffer = await crypto.subtle.digest("SHA-256", data);
750
- const hashArray = Array.from(new Uint8Array(hashBuffer));
751
- const hashBase64 = btoa(String.fromCharCode(...hashArray)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
752
- return hashBase64;
753
- }
754
- async function getPublicKey(kid) {
755
- const jwks = await fetchJwks();
756
- const key = jwks.keys.find((k) => k.kid === kid);
757
- if (!key)
758
- throw new Error("Public key not found");
759
- const publicKey = await crypto.subtle.importKey("jwk", {
760
- kty: key.kty,
761
- n: key.n,
762
- e: key.e
763
- }, { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" }, false, ["verify"]);
764
- return publicKey;
765
- }
766
- function createJwtCookies(token, expires) {
767
- const control = {
768
- expires_in: token.expires_in,
769
- refresh_expires_in: token.refresh_expires_in,
770
- scope: token.scope,
771
- session_state: token.session_state,
772
- token_type: token.token_type,
773
- expires: expires.toISOString()
774
- };
775
- return [
776
- ["Set-Cookie", createCookie("access", token.access_token, expires)],
777
- ["Set-Cookie", createCookie("id", token.id_token, expires)],
778
- ["Set-Cookie", createCookie("refresh", token.refresh_token ?? "", expires)],
779
- ["Set-Cookie", createCookie("control", control, expires)]
780
- ];
781
- }
782
- async function getTokenFromCookies(req) {
783
- const access_token = getCookie("access", req);
784
- const id_token = getCookie("id", req);
785
- const refresh_token = getCookie("refresh", req);
786
- const control = getCookie("control", req, true);
787
- if (!access_token || !id_token || !refresh_token || !control) {
788
- return { tokens: undefined, refreshHeaders: [] };
789
- }
790
- let tokenResponse = {
791
- access_token,
792
- id_token,
793
- refresh_token,
794
- ...control
795
- };
796
- if (control.expires && refresh_token && Date.now() > new Date(control.expires).getTime()) {
797
- tokenResponse = await refreshToken(refresh_token);
798
- const user = await parseUser(tokenResponse);
799
- const refreshHeaders = createJwtCookies(tokenResponse, user.sso.expires);
800
- 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
+ };
801
2709
  }
802
- return { tokens: tokenResponse, refreshHeaders: [] };
803
2710
  }
804
- async function getJwt(request) {
805
- const { tokens } = await getTokenFromCookies(request);
806
- if (!tokens)
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.");
2715
+ }
2716
+ const authHeader = request.headers.get("Authorization");
2717
+ if (!authHeader || !authHeader.startsWith("Bearer ")) {
807
2718
  return;
808
- return tokens.access_token;
2719
+ }
2720
+ const token = authHeader.substring(7);
2721
+ const result = await validateToken(token, configWithDefaults.validation);
2722
+ if (!result.valid || !result.claims) {
2723
+ return;
2724
+ }
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
+ };
809
2731
  }
810
- function createCookie(name, value, expires) {
811
- name = `${configWithDefaults.cookies_prefix}.${name}`;
812
- if (typeof value !== "string") {
813
- value = btoa(JSON.stringify(value));
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;
2739
+ const path = new URL(request.url).pathname;
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"]]
2746
+ });
814
2747
  }
815
- let exp;
816
- if (expires instanceof Date) {
817
- exp = `Expires=${expires.toUTCString()}`;
818
- } else if (typeof expires === "number") {
819
- exp = `Max-Age=${expires}`;
820
- } else {
821
- throw new Error("Invalid expires type", expires);
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
+ });
2755
+ }
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"]]
2761
+ });
822
2762
  }
823
- if (value.length > 4000) {
824
- throw new Error(`Error setting cookie: ${name}. Cookie length is: ${value.length}`);
2763
+ if (jwksUrl === path && request.method === "GET") {
2764
+ const jwks = await fetchJwks();
2765
+ return new Response(JSON.stringify(jwks), {
2766
+ headers: [["Content-Type", "application/json"]]
2767
+ });
825
2768
  }
826
- return `${name}=${value}; ${exp}; Path=${configWithDefaults.cookies_path}; HttpOnly;${configWithDefaults.cookies_secure ? " Secure;" : ""} SameSite=${configWithDefaults.cookies_same_site};`;
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
+ }
2775
+ return new Response("Not Found", { status: 404 });
827
2776
  }
828
- function clearCookie(name) {
829
- return `${configWithDefaults.cookies_prefix}.${name}=; Max-Age=0; Path=${configWithDefaults.cookies_path}; HttpOnly;${configWithDefaults.cookies_secure ? " Secure;" : ""} SameSite=${configWithDefaults.cookies_same_site};`;
2777
+ return {
2778
+ ...configWithDefaults,
2779
+ getToken,
2780
+ refreshToken,
2781
+ generateJWTAssertion,
2782
+ revokeToken,
2783
+ validateToken,
2784
+ getWorkload,
2785
+ parseJWT,
2786
+ handler
2787
+ };
2788
+ }
2789
+
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;
830
2797
  }
831
- function getCookie(name, req, parse = false) {
832
- const header = req.headers.get("cookie");
833
- if (!header)
2798
+ async getByExternalId(externalId) {
2799
+ const id = this.externalIdIndex.get(externalId);
2800
+ if (!id)
834
2801
  return null;
835
- const cookie = header.split(";").find((row) => row.trim().startsWith(`${configWithDefaults.cookies_prefix}.${name}=`));
836
- if (!cookie)
2802
+ return this.groups.get(id) ?? null;
2803
+ }
2804
+ async getByDisplayName(displayName) {
2805
+ const id = this.displayNameIndex.get(displayName.toLowerCase());
2806
+ if (!id)
837
2807
  return null;
838
- const val = cookie.split("=")[1].trim();
839
- if (!parse)
840
- return val;
841
- const str = atob(val);
842
- return JSON.parse(str);
2808
+ return this.groups.get(id) ?? null;
843
2809
  }
844
- async function handler(request, handlerConfig) {
845
- const { loginUrl, userUrl, errorUrl, landingUrl, tokenUrl, refreshUrl, logoutUrl, logoutBackChannelUrl, jwksUrl, validation } = handlerConfig ?? {};
846
- if (!loginUrl) {
847
- console.error("loginUrl is required");
848
- }
849
- const path = new URL(request.url).pathname;
850
- if (new URL(configWithDefaults.redirect_uri).pathname === path) {
851
- return callbackHandler(request, validation);
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
+ }
852
2822
  }
853
- if (loginUrl === path) {
854
- return initiateLogin({
855
- landingUrl: landingUrl || "/",
856
- errorUrl
857
- });
2823
+ this.groups.set(group.id, group);
2824
+ if (group.externalId) {
2825
+ this.externalIdIndex.set(group.externalId, group.id);
858
2826
  }
859
- if (userUrl === path) {
860
- const { tokens, refreshHeaders } = await getTokenFromCookies(request);
861
- if (!tokens) {
862
- return new Response("User not logged in", { status: 401 });
2827
+ this.displayNameIndex.set(group.displayName.toLowerCase(), group.id);
2828
+ }
2829
+ async delete(id) {
2830
+ const group = this.groups.get(id);
2831
+ if (group) {
2832
+ if (group.externalId) {
2833
+ this.externalIdIndex.delete(group.externalId);
863
2834
  }
864
- const user = await parseUser(tokens);
865
- return new Response(JSON.stringify(user), {
866
- headers: [["Content-Type", "application/json"], ...refreshHeaders]
867
- });
2835
+ this.displayNameIndex.delete(group.displayName.toLowerCase());
868
2836
  }
869
- if (tokenUrl === path) {
870
- const { tokens, refreshHeaders } = await getTokenFromCookies(request);
871
- if (!tokens) {
872
- return new Response("User not logged in", { status: 401 });
873
- }
874
- return new Response(JSON.stringify({
875
- token: tokens.access_token,
876
- expires: tokens.expires
877
- }), {
878
- headers: [["Content-Type", "application/json"], ...refreshHeaders]
879
- });
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`);
880
2843
  }
881
- if (refreshUrl === path) {
882
- const refresh_token = getCookie("refresh", request);
883
- if (!refresh_token) {
884
- return new Response("User not logged in", { status: 401 });
885
- }
886
- const newTokenResponse = await refreshToken(refresh_token);
887
- const user = await parseUser(newTokenResponse);
888
- const refreshHeaders = createJwtCookies(newTokenResponse, user.sso.expires);
889
- return new Response("Refresh Complete", {
890
- status: 200,
891
- headers: refreshHeaders
892
- });
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;
893
2849
  }
894
- if (logoutUrl === path) {
895
- return logout(request, { landingUrl: landingUrl || "/" });
2850
+ }
2851
+ async removeMember(groupId, memberId) {
2852
+ const group = this.groups.get(groupId);
2853
+ if (!group) {
2854
+ throw new Error(`Group ${groupId} not found`);
896
2855
  }
897
- if (logoutBackChannelUrl === path) {
898
- return logoutBackChannel(request);
2856
+ if (group.members) {
2857
+ group.members = group.members.filter((m) => m.value !== memberId);
2858
+ group.updatedAt = new Date;
899
2859
  }
900
- if (jwksUrl === path) {
901
- const jwks = await fetchJwks();
902
- return new Response(JSON.stringify(jwks), {
903
- headers: [["Content-Type", "application/json"]]
904
- });
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
+ }
905
2908
  }
906
- return new Response("Not Found", { status: 404 });
907
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;
908
2959
  return {
909
- getUser,
910
- getRequiredUser,
911
- getJwt,
912
- initiateLogin,
913
- logout,
914
- callbackHandler,
915
- handler
2960
+ appId,
2961
+ companyId,
2962
+ companyName,
2963
+ environmentType,
2964
+ email,
2965
+ webhookUrl
916
2966
  };
917
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
+ }
918
2984
 
919
- // packages/react/src/vault.ts
920
- function vault(url) {
921
- async function getFullSecret(path, token) {
922
- const resp = await fetch(`${url}/${path}`, { headers: { "X-Vault-Token": token } });
923
- if (resp.status !== 200) {
924
- throw new Error(`Vault returned invalid status, ${resp.status}: '${resp.statusText}' from URL: ${url}`);
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
+ }
925
3001
  }
926
- try {
927
- const secret = await resp.json();
928
- return secret.data;
929
- } catch (cause) {
930
- throw new Error("Error retrieving secret", { cause });
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);
3015
+ }
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);
931
3023
  }
3024
+ appIds.add(tenant.appId);
3025
+ return tenant;
932
3026
  }
933
- return {
934
- url,
935
- getFullSecret,
936
- getSecret: async (path, token) => {
937
- return (await getFullSecret(path, token)).data;
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);
3035
+ }
3036
+ }
938
3037
  }
939
- };
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);
940
3093
  }
3094
+
941
3095
  // packages/react/src/server.ts
942
3096
  function getSSO(config) {
943
3097
  const es = getES(config?.es);
@@ -947,7 +3101,7 @@ function getSSO(config) {
947
3101
  }
948
3102
  return es.sso;
949
3103
  }
950
- function unavailable() {
3104
+ function unavailable2() {
951
3105
  new Response(JSON.stringify({ error: "SSO Unavailable" }), {
952
3106
  status: 503,
953
3107
  statusText: "SSO Unavailable",
@@ -960,26 +3114,26 @@ async function getUser(request, config) {
960
3114
  async function getRequiredUser(request, config) {
961
3115
  const sso2 = getSSO(config);
962
3116
  if (!sso2)
963
- throw unavailable();
3117
+ throw unavailable2();
964
3118
  return sso2.getRequiredUser(request);
965
3119
  }
966
3120
  async function initiateLogin(config) {
967
3121
  const sso2 = getSSO(config);
968
3122
  if (!sso2)
969
- throw unavailable();
3123
+ throw unavailable2();
970
3124
  return sso2.initiateLogin(config);
971
3125
  }
972
3126
  async function callback(request, config) {
973
3127
  const sso2 = getSSO(config);
974
3128
  if (!sso2)
975
- throw unavailable();
3129
+ throw unavailable2();
976
3130
  return sso2.callbackHandler(request);
977
3131
  }
978
3132
  async function handler(request, config) {
979
3133
  const sso2 = getSSO(config);
980
3134
  if (!sso2)
981
- throw unavailable();
982
- return sso2.handler(request, config);
3135
+ throw unavailable2();
3136
+ return sso2.handler(request);
983
3137
  }
984
3138
  // packages/react/src/session-store.ts
985
3139
  class InMemorySessionStore {
@@ -1301,41 +3455,146 @@ async function logout(logoutUrl) {
1301
3455
  };
1302
3456
  }
1303
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
+ }
1304
3509
 
1305
3510
  // packages/react/src/index.ts
1306
- async function enterpriseStandard(appKey, initConfig) {
1307
- let vaultUrl;
1308
- let vaultToken;
1309
- let secrets;
1310
- const ioniteUrl = initConfig?.ioniteUrl ?? "https://ionite.com";
1311
- if (appKey?.startsWith("IONITE_PUBLIC_DEMO_")) {
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) {
1312
3542
  vaultUrl = "https://vault-ionite.ionite.dev/v1/secret/data";
1313
- const port = appKey.slice("IONITE_PUBLIC_DEMO_".length);
1314
- secrets = {
1315
- sso: {
1316
- path: `public/IONITE_PUBLIC_DEMO_SSO_${port}`,
1317
- token: "hvs.VGhD2hmXDH9PmZjTacZx0G5K"
1318
- }
3543
+ secret = {
3544
+ path: `public/${appId}`,
3545
+ token: "hvs.VGhD2hmXDH9PmZjTacZx0G5K"
1319
3546
  };
1320
- } else if (appKey) {
1321
- if (!vaultUrl || !vaultToken) {
1322
- throw new Error("TODO something is wrong with the ionite config, handle this error");
1323
- }
1324
- secrets = {};
1325
- } else {
1326
- 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.`);
1327
3556
  }
1328
3557
  const defaultInstance2 = getDefaultInstance();
1329
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
+ };
1330
3591
  const result = {
1331
- ioniteUrl,
3592
+ ...mergedConfig,
1332
3593
  defaultInstance: initConfig?.defaultInstance || initConfig?.defaultInstance !== false && !defaultInstance2,
1333
3594
  vault: vaultClient,
1334
- sso: secrets.sso ? sso({
1335
- ...await vaultClient.getSecret(secrets.sso.path, secrets.sso.token),
1336
- ...initConfig
1337
- }) : undefined,
1338
- iam: secrets.iam ? await iam(await vaultClient.getSecret(secrets.iam.path, secrets.iam.token)) : undefined
3595
+ sso: ssoInstance,
3596
+ iam: iamInstance,
3597
+ workload: workloadInstance
1339
3598
  };
1340
3599
  if (result.defaultInstance) {
1341
3600
  if (defaultInstance2) {
@@ -1346,23 +3605,46 @@ async function enterpriseStandard(appKey, initConfig) {
1346
3605
  return result;
1347
3606
  }
1348
3607
  export {
3608
+ workloadTokenResponseSchema,
3609
+ workloadHandler,
3610
+ workload,
3611
+ vault,
3612
+ validateWorkloadToken,
3613
+ userSchema,
1349
3614
  useUser,
1350
3615
  useToken,
1351
3616
  tokenResponseSchema,
3617
+ sso,
3618
+ serializeESConfig,
3619
+ sendTenantWebhook,
3620
+ revokeWorkloadToken,
3621
+ parseTenantRequest,
1352
3622
  oidcCallbackSchema,
1353
3623
  logout,
3624
+ jwtAssertionClaimsSchema,
1354
3625
  initiateLogin,
1355
3626
  idTokenClaimsSchema,
3627
+ iam,
1356
3628
  handler,
3629
+ groupResourceSchema,
3630
+ getWorkloadToken,
3631
+ getWorkload,
1357
3632
  getUser,
1358
3633
  getRequiredUser,
3634
+ getES,
3635
+ getDefaultInstance,
1359
3636
  enterpriseStandard,
1360
3637
  callback,
3638
+ TenantRequestError,
1361
3639
  SignedOut,
1362
3640
  SignedIn,
1363
3641
  SignInLoading,
1364
3642
  SSOProvider,
1365
- InMemorySessionStore
3643
+ InMemoryWorkloadTokenStore,
3644
+ InMemoryUserStore,
3645
+ InMemoryTenantStore,
3646
+ InMemorySessionStore,
3647
+ InMemoryGroupStore
1366
3648
  };
1367
3649
 
1368
- //# debugId=11B14D9E3E6DAB9F64756E2164756E21
3650
+ //# debugId=BB2BA44916C99DF364756E2164756E21