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