@enterprisestandard/react 0.0.4 → 0.0.5
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/enterprise-user.d.ts +1 -0
- package/dist/enterprise-user.d.ts.map +1 -1
- package/dist/iam.d.ts +4 -11
- package/dist/iam.d.ts.map +1 -1
- package/dist/index.d.ts +9 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +451 -143
- package/dist/oidc-schema.d.ts +42 -0
- package/dist/oidc-schema.d.ts.map +1 -1
- package/dist/scim-schema.d.ts +356 -0
- package/dist/scim-schema.d.ts.map +1 -0
- package/dist/session-store.d.ts +179 -0
- package/dist/session-store.d.ts.map +1 -0
- package/dist/sso.d.ts +21 -11
- package/dist/sso.d.ts.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,9 +1,287 @@
|
|
|
1
1
|
// src/iam.ts
|
|
2
2
|
async function iam(config) {
|
|
3
|
+
return {};
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
// src/oidc-schema.ts
|
|
7
|
+
function oidcCallbackSchema(vendor) {
|
|
8
|
+
return {
|
|
9
|
+
"~standard": {
|
|
10
|
+
version: 1,
|
|
11
|
+
vendor,
|
|
12
|
+
validate: (value) => {
|
|
13
|
+
if (typeof value !== "object" || value === null) {
|
|
14
|
+
return {
|
|
15
|
+
issues: [
|
|
16
|
+
{
|
|
17
|
+
message: "Expected an object"
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
const params = value;
|
|
23
|
+
const issues = [];
|
|
24
|
+
const result = {};
|
|
25
|
+
if ("code" in params) {
|
|
26
|
+
if (typeof params.code === "string") {
|
|
27
|
+
result.code = params.code;
|
|
28
|
+
} else {
|
|
29
|
+
issues.push({
|
|
30
|
+
message: "code must be a string",
|
|
31
|
+
path: ["code"]
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
} else if (!("error" in params)) {
|
|
35
|
+
issues.push({
|
|
36
|
+
message: "code is required",
|
|
37
|
+
path: ["code"]
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
if ("state" in params) {
|
|
41
|
+
if (typeof params.state === "string" || params.state === undefined) {
|
|
42
|
+
result.state = params.state;
|
|
43
|
+
} else {
|
|
44
|
+
issues.push({
|
|
45
|
+
message: "state must be a string",
|
|
46
|
+
path: ["state"]
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if ("session_state" in params) {
|
|
51
|
+
if (typeof params.session_state === "string" || params.session_state === undefined) {
|
|
52
|
+
result.session_state = params.session_state;
|
|
53
|
+
} else {
|
|
54
|
+
issues.push({
|
|
55
|
+
message: "session_state must be a string",
|
|
56
|
+
path: ["session_state"]
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if ("error" in params) {
|
|
61
|
+
if (typeof params.error === "string") {
|
|
62
|
+
result.error = params.error;
|
|
63
|
+
} else {
|
|
64
|
+
issues.push({
|
|
65
|
+
message: "error must be a string",
|
|
66
|
+
path: ["error"]
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
if ("error_description" in params) {
|
|
70
|
+
if (typeof params.error_description === "string" || params.error_description === undefined) {
|
|
71
|
+
result.error_description = params.error_description;
|
|
72
|
+
} else {
|
|
73
|
+
issues.push({
|
|
74
|
+
message: "error_description must be a string",
|
|
75
|
+
path: ["error_description"]
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if ("error_uri" in params) {
|
|
80
|
+
if (typeof params.error_uri === "string" || params.error_uri === undefined) {
|
|
81
|
+
result.error_uri = params.error_uri;
|
|
82
|
+
} else {
|
|
83
|
+
issues.push({
|
|
84
|
+
message: "error_uri must be a string",
|
|
85
|
+
path: ["error_uri"]
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if ("iss" in params) {
|
|
91
|
+
if (typeof params.iss === "string" || params.iss === undefined) {
|
|
92
|
+
result.iss = params.iss;
|
|
93
|
+
} else {
|
|
94
|
+
issues.push({
|
|
95
|
+
message: "iss must be a string",
|
|
96
|
+
path: ["iss"]
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (issues.length > 0) {
|
|
101
|
+
return { issues };
|
|
102
|
+
}
|
|
103
|
+
return { value: result };
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function tokenResponseSchema(vendor) {
|
|
109
|
+
return {
|
|
110
|
+
"~standard": {
|
|
111
|
+
version: 1,
|
|
112
|
+
vendor,
|
|
113
|
+
validate: (value) => {
|
|
114
|
+
if (typeof value !== "object" || value === null) {
|
|
115
|
+
return {
|
|
116
|
+
issues: [
|
|
117
|
+
{
|
|
118
|
+
message: "Expected an object"
|
|
119
|
+
}
|
|
120
|
+
]
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
const response = value;
|
|
124
|
+
const issues = [];
|
|
125
|
+
const result = {};
|
|
126
|
+
if ("access_token" in response) {
|
|
127
|
+
if (typeof response.access_token === "string") {
|
|
128
|
+
result.access_token = response.access_token;
|
|
129
|
+
} else {
|
|
130
|
+
issues.push({
|
|
131
|
+
message: "access_token must be a string",
|
|
132
|
+
path: ["access_token"]
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
} else {
|
|
136
|
+
issues.push({
|
|
137
|
+
message: "access_token is required",
|
|
138
|
+
path: ["access_token"]
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
if ("id_token" in response) {
|
|
142
|
+
if (typeof response.id_token === "string") {
|
|
143
|
+
result.id_token = response.id_token;
|
|
144
|
+
} else {
|
|
145
|
+
issues.push({
|
|
146
|
+
message: "id_token must be a string",
|
|
147
|
+
path: ["id_token"]
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
} else {
|
|
151
|
+
issues.push({
|
|
152
|
+
message: "id_token is required",
|
|
153
|
+
path: ["id_token"]
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
if ("token_type" in response) {
|
|
157
|
+
if (typeof response.token_type === "string") {
|
|
158
|
+
result.token_type = response.token_type;
|
|
159
|
+
} else {
|
|
160
|
+
issues.push({
|
|
161
|
+
message: "token_type must be a string",
|
|
162
|
+
path: ["token_type"]
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
} else {
|
|
166
|
+
issues.push({
|
|
167
|
+
message: "token_type is required",
|
|
168
|
+
path: ["token_type"]
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
if ("refresh_token" in response) {
|
|
172
|
+
if (typeof response.refresh_token === "string" || response.refresh_token === undefined) {
|
|
173
|
+
result.refresh_token = response.refresh_token;
|
|
174
|
+
} else {
|
|
175
|
+
issues.push({
|
|
176
|
+
message: "refresh_token must be a string",
|
|
177
|
+
path: ["refresh_token"]
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if ("scope" in response) {
|
|
182
|
+
if (typeof response.scope === "string" || response.scope === undefined) {
|
|
183
|
+
result.scope = response.scope;
|
|
184
|
+
} else {
|
|
185
|
+
issues.push({
|
|
186
|
+
message: "scope must be a string",
|
|
187
|
+
path: ["scope"]
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
if ("session_state" in response) {
|
|
192
|
+
if (typeof response.session_state === "string" || response.session_state === undefined) {
|
|
193
|
+
result.session_state = response.session_state;
|
|
194
|
+
} else {
|
|
195
|
+
issues.push({
|
|
196
|
+
message: "session_state must be a string",
|
|
197
|
+
path: ["session_state"]
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if ("expires" in response) {
|
|
202
|
+
if (typeof response.expires === "string" || response.expires === undefined) {
|
|
203
|
+
result.expires = response.expires;
|
|
204
|
+
} else {
|
|
205
|
+
issues.push({
|
|
206
|
+
message: "expires must be a string",
|
|
207
|
+
path: ["expires"]
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
if ("expires_in" in response) {
|
|
212
|
+
if (typeof response.expires_in === "number" || response.expires_in === undefined) {
|
|
213
|
+
result.expires_in = response.expires_in;
|
|
214
|
+
} else {
|
|
215
|
+
issues.push({
|
|
216
|
+
message: "expires_in must be a number",
|
|
217
|
+
path: ["expires_in"]
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if ("refresh_expires_in" in response) {
|
|
222
|
+
if (typeof response.refresh_expires_in === "number" || response.refresh_expires_in === undefined) {
|
|
223
|
+
result.refresh_expires_in = response.refresh_expires_in;
|
|
224
|
+
} else {
|
|
225
|
+
issues.push({
|
|
226
|
+
message: "refresh_expires_in must be a number",
|
|
227
|
+
path: ["refresh_expires_in"]
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
if (issues.length > 0) {
|
|
232
|
+
return { issues };
|
|
233
|
+
}
|
|
234
|
+
return { value: result };
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
function idTokenClaimsSchema(vendor) {
|
|
3
240
|
return {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
241
|
+
"~standard": {
|
|
242
|
+
version: 1,
|
|
243
|
+
vendor,
|
|
244
|
+
validate: (value) => {
|
|
245
|
+
if (typeof value !== "object" || value === null) {
|
|
246
|
+
return {
|
|
247
|
+
issues: [
|
|
248
|
+
{
|
|
249
|
+
message: "Expected an object"
|
|
250
|
+
}
|
|
251
|
+
]
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
const claims = value;
|
|
255
|
+
const issues = [];
|
|
256
|
+
const result = { ...claims };
|
|
257
|
+
const stringFields = ["iss", "aud", "sub", "sid", "name", "email", "preferred_username", "picture"];
|
|
258
|
+
for (const field of stringFields) {
|
|
259
|
+
if (field in claims && claims[field] !== undefined) {
|
|
260
|
+
if (typeof claims[field] !== "string") {
|
|
261
|
+
issues.push({
|
|
262
|
+
message: `${field} must be a string`,
|
|
263
|
+
path: [field]
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
const numberFields = ["exp", "iat"];
|
|
269
|
+
for (const field of numberFields) {
|
|
270
|
+
if (field in claims && claims[field] !== undefined) {
|
|
271
|
+
if (typeof claims[field] !== "number") {
|
|
272
|
+
issues.push({
|
|
273
|
+
message: `${field} must be a number`,
|
|
274
|
+
path: [field]
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
if (issues.length > 0) {
|
|
280
|
+
return { issues };
|
|
281
|
+
}
|
|
282
|
+
return { value: result };
|
|
283
|
+
}
|
|
284
|
+
}
|
|
7
285
|
};
|
|
8
286
|
}
|
|
9
287
|
|
|
@@ -31,10 +309,16 @@ function getES(es) {
|
|
|
31
309
|
|
|
32
310
|
// src/sso.ts
|
|
33
311
|
var jwksCache = new Map;
|
|
34
|
-
var _logoutTokenJtis = new Set;
|
|
35
312
|
function sso(config) {
|
|
36
313
|
const configWithDefaults = {
|
|
37
314
|
...config,
|
|
315
|
+
authority: must(config.authority, "Missing 'authority' from SSO Config"),
|
|
316
|
+
token_url: must(config.token_url, "Missing 'token_url' from SSO Config"),
|
|
317
|
+
authorization_url: must(config.authorization_url, "Missing 'authorization_url' from SSO Config"),
|
|
318
|
+
client_id: must(config.client_id, "Missing 'client_id' from SSO Config"),
|
|
319
|
+
redirect_uri: must(config.redirect_uri, "Missing 'redirect_uri' from SSO Config"),
|
|
320
|
+
scope: must(config.scope, "Missing 'scope' from SSO Config"),
|
|
321
|
+
response_type: config.response_type ?? "code",
|
|
38
322
|
cookies_secure: config.cookies_secure !== undefined ? config.cookies_secure : true,
|
|
39
323
|
cookies_same_site: config.cookies_same_site !== undefined ? config.cookies_same_site : "Strict",
|
|
40
324
|
cookies_prefix: config.cookies_prefix ?? `es.sso.${config.client_id}`,
|
|
@@ -103,6 +387,18 @@ function sso(config) {
|
|
|
103
387
|
} catch (error) {
|
|
104
388
|
console.warn("Failed to revoke token:", error);
|
|
105
389
|
}
|
|
390
|
+
if (config.session_store) {
|
|
391
|
+
try {
|
|
392
|
+
const user = await getUser(request);
|
|
393
|
+
if (user?.sso?.profile.sid) {
|
|
394
|
+
const sid = user.sso.profile.sid;
|
|
395
|
+
await config.session_store.delete(sid);
|
|
396
|
+
console.log(`Session ${sid} deleted from store`);
|
|
397
|
+
}
|
|
398
|
+
} catch (error) {
|
|
399
|
+
console.warn("Failed to delete session:", error);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
106
402
|
const clearHeaders = [
|
|
107
403
|
["Set-Cookie", clearCookie("access")],
|
|
108
404
|
["Set-Cookie", clearCookie("id")],
|
|
@@ -140,16 +436,65 @@ function sso(config) {
|
|
|
140
436
|
});
|
|
141
437
|
}
|
|
142
438
|
}
|
|
143
|
-
async function
|
|
439
|
+
async function logoutBackChannel(request) {
|
|
440
|
+
if (!configWithDefaults.session_store) {
|
|
441
|
+
return new Response("Back-Channel Logout requires session_store configuration", {
|
|
442
|
+
status: 400,
|
|
443
|
+
statusText: "Bad Request"
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
try {
|
|
447
|
+
const contentType = request.headers.get("content-type");
|
|
448
|
+
if (!contentType || !contentType.includes("application/x-www-form-urlencoded")) {
|
|
449
|
+
return new Response("Invalid Content-Type, expected application/x-www-form-urlencoded", {
|
|
450
|
+
status: 400
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
const body = await request.text();
|
|
454
|
+
const params = new URLSearchParams(body);
|
|
455
|
+
const logoutToken = params.get("logout_token");
|
|
456
|
+
if (!logoutToken) {
|
|
457
|
+
return new Response("Missing logout_token parameter", { status: 400 });
|
|
458
|
+
}
|
|
459
|
+
const claims = await parseJwt(logoutToken);
|
|
460
|
+
const sid = claims.sid;
|
|
461
|
+
if (!sid) {
|
|
462
|
+
console.warn("Back-Channel Logout: logout_token missing sid claim");
|
|
463
|
+
return new Response("Invalid logout_token: missing sid claim", { status: 400 });
|
|
464
|
+
}
|
|
465
|
+
await configWithDefaults.session_store.delete(sid);
|
|
466
|
+
console.log(`Back-Channel Logout: successfully deleted session ${sid}`);
|
|
467
|
+
return new Response("OK", { status: 200 });
|
|
468
|
+
} catch (error) {
|
|
469
|
+
console.error("Error during back-channel logout:", error);
|
|
470
|
+
return new Response("Internal Server Error", { status: 500 });
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
async function callbackHandler(request, validation) {
|
|
144
474
|
if (!configWithDefaults) {
|
|
145
475
|
console.error("SSO Manager not initialized");
|
|
146
476
|
return Promise.resolve(new Response("SSO Manager not initialized", { status: 503 }));
|
|
147
477
|
}
|
|
148
478
|
const url = new URL(request.url);
|
|
149
479
|
const params = new URLSearchParams(url.search);
|
|
480
|
+
const callbackParamsValidator = validation?.callbackParams ?? oidcCallbackSchema("builtin");
|
|
481
|
+
const paramsObject = Object.fromEntries(params.entries());
|
|
482
|
+
const paramsResult = await callbackParamsValidator["~standard"].validate(paramsObject);
|
|
483
|
+
if ("issues" in paramsResult) {
|
|
484
|
+
return new Response(JSON.stringify({
|
|
485
|
+
error: "validation_failed",
|
|
486
|
+
message: "OIDC callback parameters validation failed",
|
|
487
|
+
issues: paramsResult.issues?.map((i) => ({
|
|
488
|
+
path: i.path?.join("."),
|
|
489
|
+
message: i.message
|
|
490
|
+
}))
|
|
491
|
+
}), {
|
|
492
|
+
status: 400,
|
|
493
|
+
headers: { "Content-Type": "application/json" }
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
const { code: codeFromUrl, state: stateFromUrl } = paramsResult.value;
|
|
150
497
|
try {
|
|
151
|
-
const codeFromUrl = must(params.get("code"), 'OIDC "code" was not passed as a search param, ensure that the SSO login completed successfully');
|
|
152
|
-
const stateFromUrl = must(params.get("state"), 'OIDC "state" was not passed as a search param, ensure that the SSO login completed successfully');
|
|
153
498
|
const cookie = getCookie("state", request, true);
|
|
154
499
|
const { codeVerifier, state, landingUrl } = cookie ?? {};
|
|
155
500
|
must(codeVerifier, 'OIDC "codeVerifier" was not present in cookies, ensure that the SSO login was initiated correctly');
|
|
@@ -158,8 +503,27 @@ function sso(config) {
|
|
|
158
503
|
if (stateFromUrl !== state) {
|
|
159
504
|
throw new Error('SSO State Verifier failed, the "state" request parameter does not equal the "state" in the SSO cookie');
|
|
160
505
|
}
|
|
161
|
-
const tokenResponse = await exchangeCodeForToken(codeFromUrl, codeVerifier);
|
|
162
|
-
const user = await parseUser(tokenResponse);
|
|
506
|
+
const tokenResponse = await exchangeCodeForToken(codeFromUrl, codeVerifier, validation);
|
|
507
|
+
const user = await parseUser(tokenResponse, validation);
|
|
508
|
+
if (config.session_store) {
|
|
509
|
+
try {
|
|
510
|
+
const sid = user.sso.profile.sid;
|
|
511
|
+
const sub = user.id;
|
|
512
|
+
if (sid && sub) {
|
|
513
|
+
const session = {
|
|
514
|
+
sid,
|
|
515
|
+
sub,
|
|
516
|
+
createdAt: new Date,
|
|
517
|
+
lastActivityAt: new Date
|
|
518
|
+
};
|
|
519
|
+
await config.session_store.create(session);
|
|
520
|
+
} else {
|
|
521
|
+
console.warn("Session creation skipped: missing sid or sub in ID token claims");
|
|
522
|
+
}
|
|
523
|
+
} catch (error) {
|
|
524
|
+
console.warn("Failed to create session:", error);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
163
527
|
return new Response("Authentication successful, redirecting", {
|
|
164
528
|
status: 302,
|
|
165
529
|
headers: [
|
|
@@ -188,10 +552,10 @@ function sso(config) {
|
|
|
188
552
|
});
|
|
189
553
|
}
|
|
190
554
|
}
|
|
191
|
-
async function parseUser(token) {
|
|
555
|
+
async function parseUser(token, validation) {
|
|
192
556
|
if (!configWithDefaults)
|
|
193
557
|
throw new Error("SSO Manager not initialized");
|
|
194
|
-
const idToken = await parseJwt(token.id_token);
|
|
558
|
+
const idToken = await parseJwt(token.id_token, validation);
|
|
195
559
|
const expiresIn = Number(token.refresh_expires_in ?? token.expires_in ?? 3600);
|
|
196
560
|
const expires = token.expires ? new Date(token.expires) : new Date(Date.now() + expiresIn * 1000);
|
|
197
561
|
return {
|
|
@@ -223,7 +587,7 @@ function sso(config) {
|
|
|
223
587
|
}
|
|
224
588
|
};
|
|
225
589
|
}
|
|
226
|
-
async function exchangeCodeForToken(code, codeVerifier) {
|
|
590
|
+
async function exchangeCodeForToken(code, codeVerifier, validation) {
|
|
227
591
|
if (!configWithDefaults)
|
|
228
592
|
throw new Error("SSO Manager not initialized");
|
|
229
593
|
const tokenUrl = configWithDefaults.token_url;
|
|
@@ -247,10 +611,16 @@ function sso(config) {
|
|
|
247
611
|
console.error("Token exchange error:", data);
|
|
248
612
|
throw new Error(`Token exchange failed: ${data.error || response.statusText} - ${data.error_description || ""}`.trim());
|
|
249
613
|
}
|
|
250
|
-
|
|
614
|
+
const tokenResponseValidator = validation?.tokenResponse ?? tokenResponseSchema("builtin");
|
|
615
|
+
const tokenResult = await tokenResponseValidator["~standard"].validate(data);
|
|
616
|
+
if ("issues" in tokenResult) {
|
|
617
|
+
console.error("Token response validation failed:", tokenResult.issues);
|
|
618
|
+
throw new Error(`Token response validation failed: ${tokenResult.issues?.map((i) => i.message).join("; ")}`);
|
|
619
|
+
}
|
|
620
|
+
return tokenResult.value;
|
|
251
621
|
} catch (error) {
|
|
252
622
|
console.error("Error during token exchange:", error);
|
|
253
|
-
throw
|
|
623
|
+
throw error;
|
|
254
624
|
}
|
|
255
625
|
}
|
|
256
626
|
async function refreshToken(refreshToken2) {
|
|
@@ -282,12 +652,14 @@ function sso(config) {
|
|
|
282
652
|
try {
|
|
283
653
|
if (!configWithDefaults)
|
|
284
654
|
throw new Error("SSO Manager not initialized");
|
|
285
|
-
|
|
655
|
+
if (!configWithDefaults.revocation_endpoint) {
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
286
658
|
const body = new URLSearchParams;
|
|
287
659
|
body.append("token", token);
|
|
288
660
|
body.append("token_type_hint", "refresh_token");
|
|
289
661
|
body.append("client_id", configWithDefaults.client_id);
|
|
290
|
-
const response = await fetch(
|
|
662
|
+
const response = await fetch(configWithDefaults.revocation_endpoint, {
|
|
291
663
|
method: "POST",
|
|
292
664
|
headers: {
|
|
293
665
|
"Content-Type": "application/x-www-form-urlencoded"
|
|
@@ -340,7 +712,7 @@ function sso(config) {
|
|
|
340
712
|
}
|
|
341
713
|
throw lastError;
|
|
342
714
|
}
|
|
343
|
-
async function parseJwt(token) {
|
|
715
|
+
async function parseJwt(token, validation) {
|
|
344
716
|
try {
|
|
345
717
|
const parts = token.split(".");
|
|
346
718
|
if (parts.length !== 3)
|
|
@@ -354,7 +726,13 @@ function sso(config) {
|
|
|
354
726
|
const isValid = await crypto.subtle.verify("RSASSA-PKCS1-v1_5", publicKey, Uint8Array.from(atob(signature), (c) => c.charCodeAt(0)), data);
|
|
355
727
|
if (!isValid)
|
|
356
728
|
throw new Error("Invalid JWT signature");
|
|
357
|
-
|
|
729
|
+
const idTokenClaimsValidator = validation?.idTokenClaims ?? idTokenClaimsSchema("builtin");
|
|
730
|
+
const claimsResult = await idTokenClaimsValidator["~standard"].validate(payload);
|
|
731
|
+
if ("issues" in claimsResult) {
|
|
732
|
+
console.error("ID token claims validation failed:", claimsResult.issues);
|
|
733
|
+
throw new Error(`ID token claims validation failed: ${claimsResult.issues?.map((i) => i.message).join("; ")}`);
|
|
734
|
+
}
|
|
735
|
+
return claimsResult.value;
|
|
358
736
|
} catch (e) {
|
|
359
737
|
console.error("Error verifying JWT:", e);
|
|
360
738
|
throw e;
|
|
@@ -464,13 +842,13 @@ function sso(config) {
|
|
|
464
842
|
return JSON.parse(str);
|
|
465
843
|
}
|
|
466
844
|
async function handler(request, handlerConfig) {
|
|
467
|
-
const { loginUrl, userUrl, errorUrl, landingUrl, tokenUrl, refreshUrl, logoutUrl, jwksUrl } = handlerConfig ?? {};
|
|
845
|
+
const { loginUrl, userUrl, errorUrl, landingUrl, tokenUrl, refreshUrl, logoutUrl, logoutBackChannelUrl, jwksUrl, validation } = handlerConfig ?? {};
|
|
468
846
|
if (!loginUrl) {
|
|
469
847
|
console.error("loginUrl is required");
|
|
470
848
|
}
|
|
471
849
|
const path = new URL(request.url).pathname;
|
|
472
|
-
if (new URL(
|
|
473
|
-
return callbackHandler(request);
|
|
850
|
+
if (new URL(configWithDefaults.redirect_uri).pathname === path) {
|
|
851
|
+
return callbackHandler(request, validation);
|
|
474
852
|
}
|
|
475
853
|
if (loginUrl === path) {
|
|
476
854
|
return initiateLogin({
|
|
@@ -516,6 +894,9 @@ function sso(config) {
|
|
|
516
894
|
if (logoutUrl === path) {
|
|
517
895
|
return logout(request, { landingUrl: landingUrl || "/" });
|
|
518
896
|
}
|
|
897
|
+
if (logoutBackChannelUrl === path) {
|
|
898
|
+
return logoutBackChannel(request);
|
|
899
|
+
}
|
|
519
900
|
if (jwksUrl === path) {
|
|
520
901
|
const jwks = await fetchJwks();
|
|
521
902
|
return new Response(JSON.stringify(jwks), {
|
|
@@ -557,109 +938,6 @@ function vault(url) {
|
|
|
557
938
|
}
|
|
558
939
|
};
|
|
559
940
|
}
|
|
560
|
-
|
|
561
|
-
// src/oidc-schema.ts
|
|
562
|
-
function oidcCallbackSchema(vendor) {
|
|
563
|
-
return {
|
|
564
|
-
"~standard": {
|
|
565
|
-
version: 1,
|
|
566
|
-
vendor,
|
|
567
|
-
validate: (value) => {
|
|
568
|
-
if (typeof value !== "object" || value === null) {
|
|
569
|
-
return {
|
|
570
|
-
issues: [
|
|
571
|
-
{
|
|
572
|
-
message: "Expected an object"
|
|
573
|
-
}
|
|
574
|
-
]
|
|
575
|
-
};
|
|
576
|
-
}
|
|
577
|
-
const params = value;
|
|
578
|
-
const issues = [];
|
|
579
|
-
const result = {};
|
|
580
|
-
if ("code" in params) {
|
|
581
|
-
if (typeof params.code === "string") {
|
|
582
|
-
result.code = params.code;
|
|
583
|
-
} else {
|
|
584
|
-
issues.push({
|
|
585
|
-
message: "code must be a string",
|
|
586
|
-
path: ["code"]
|
|
587
|
-
});
|
|
588
|
-
}
|
|
589
|
-
} else if (!("error" in params)) {
|
|
590
|
-
issues.push({
|
|
591
|
-
message: "code is required",
|
|
592
|
-
path: ["code"]
|
|
593
|
-
});
|
|
594
|
-
}
|
|
595
|
-
if ("state" in params) {
|
|
596
|
-
if (typeof params.state === "string" || params.state === undefined) {
|
|
597
|
-
result.state = params.state;
|
|
598
|
-
} else {
|
|
599
|
-
issues.push({
|
|
600
|
-
message: "state must be a string",
|
|
601
|
-
path: ["state"]
|
|
602
|
-
});
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
if ("session_state" in params) {
|
|
606
|
-
if (typeof params.session_state === "string" || params.session_state === undefined) {
|
|
607
|
-
result.session_state = params.session_state;
|
|
608
|
-
} else {
|
|
609
|
-
issues.push({
|
|
610
|
-
message: "session_state must be a string",
|
|
611
|
-
path: ["session_state"]
|
|
612
|
-
});
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
if ("error" in params) {
|
|
616
|
-
if (typeof params.error === "string") {
|
|
617
|
-
result.error = params.error;
|
|
618
|
-
} else {
|
|
619
|
-
issues.push({
|
|
620
|
-
message: "error must be a string",
|
|
621
|
-
path: ["error"]
|
|
622
|
-
});
|
|
623
|
-
}
|
|
624
|
-
if ("error_description" in params) {
|
|
625
|
-
if (typeof params.error_description === "string" || params.error_description === undefined) {
|
|
626
|
-
result.error_description = params.error_description;
|
|
627
|
-
} else {
|
|
628
|
-
issues.push({
|
|
629
|
-
message: "error_description must be a string",
|
|
630
|
-
path: ["error_description"]
|
|
631
|
-
});
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
if ("error_uri" in params) {
|
|
635
|
-
if (typeof params.error_uri === "string" || params.error_uri === undefined) {
|
|
636
|
-
result.error_uri = params.error_uri;
|
|
637
|
-
} else {
|
|
638
|
-
issues.push({
|
|
639
|
-
message: "error_uri must be a string",
|
|
640
|
-
path: ["error_uri"]
|
|
641
|
-
});
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
if ("iss" in params) {
|
|
646
|
-
if (typeof params.iss === "string" || params.iss === undefined) {
|
|
647
|
-
result.iss = params.iss;
|
|
648
|
-
} else {
|
|
649
|
-
issues.push({
|
|
650
|
-
message: "iss must be a string",
|
|
651
|
-
path: ["iss"]
|
|
652
|
-
});
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
if (issues.length > 0) {
|
|
656
|
-
return { issues };
|
|
657
|
-
}
|
|
658
|
-
return { value: result };
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
};
|
|
662
|
-
}
|
|
663
941
|
// src/server.ts
|
|
664
942
|
function getSSO(config) {
|
|
665
943
|
const es = getES(config?.es);
|
|
@@ -703,39 +981,63 @@ async function handler(request, config) {
|
|
|
703
981
|
throw unavailable();
|
|
704
982
|
return sso2.handler(request, config);
|
|
705
983
|
}
|
|
984
|
+
// src/session-store.ts
|
|
985
|
+
class InMemorySessionStore {
|
|
986
|
+
sessions = new Map;
|
|
987
|
+
async create(session) {
|
|
988
|
+
if (this.sessions.has(session.sid)) {
|
|
989
|
+
throw new Error(`Session with sid ${session.sid} already exists`);
|
|
990
|
+
}
|
|
991
|
+
this.sessions.set(session.sid, session);
|
|
992
|
+
}
|
|
993
|
+
async get(sid) {
|
|
994
|
+
return this.sessions.get(sid) ?? null;
|
|
995
|
+
}
|
|
996
|
+
async update(sid, data) {
|
|
997
|
+
const session = this.sessions.get(sid);
|
|
998
|
+
if (!session) {
|
|
999
|
+
throw new Error(`Session with sid ${sid} not found`);
|
|
1000
|
+
}
|
|
1001
|
+
const updated = { ...session, ...data };
|
|
1002
|
+
this.sessions.set(sid, updated);
|
|
1003
|
+
}
|
|
1004
|
+
async delete(sid) {
|
|
1005
|
+
this.sessions.delete(sid);
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
706
1008
|
// src/ui/sign-in-loading.tsx
|
|
707
|
-
import {
|
|
1009
|
+
import { jsxDEV, Fragment } from "react/jsx-dev-runtime";
|
|
708
1010
|
function SignInLoading({ complete = false, children }) {
|
|
709
1011
|
const { isLoading } = useUser();
|
|
710
1012
|
if (isLoading && !complete)
|
|
711
|
-
return /* @__PURE__ */
|
|
1013
|
+
return /* @__PURE__ */ jsxDEV(Fragment, {
|
|
712
1014
|
children
|
|
713
|
-
});
|
|
1015
|
+
}, undefined, false, undefined, this);
|
|
714
1016
|
return null;
|
|
715
1017
|
}
|
|
716
1018
|
// src/ui/signed-in.tsx
|
|
717
|
-
import {
|
|
1019
|
+
import { jsxDEV as jsxDEV2, Fragment as Fragment2 } from "react/jsx-dev-runtime";
|
|
718
1020
|
function SignedIn({ children }) {
|
|
719
1021
|
const { user } = useUser();
|
|
720
1022
|
if (user)
|
|
721
|
-
return /* @__PURE__ */
|
|
1023
|
+
return /* @__PURE__ */ jsxDEV2(Fragment2, {
|
|
722
1024
|
children
|
|
723
|
-
});
|
|
1025
|
+
}, undefined, false, undefined, this);
|
|
724
1026
|
return null;
|
|
725
1027
|
}
|
|
726
1028
|
// src/ui/signed-out.tsx
|
|
727
|
-
import {
|
|
1029
|
+
import { jsxDEV as jsxDEV3, Fragment as Fragment3 } from "react/jsx-dev-runtime";
|
|
728
1030
|
function SignedOut({ children }) {
|
|
729
1031
|
const { user, isLoading } = useUser();
|
|
730
1032
|
if (user || isLoading)
|
|
731
1033
|
return null;
|
|
732
|
-
return /* @__PURE__ */
|
|
1034
|
+
return /* @__PURE__ */ jsxDEV3(Fragment3, {
|
|
733
1035
|
children
|
|
734
|
-
});
|
|
1036
|
+
}, undefined, false, undefined, this);
|
|
735
1037
|
}
|
|
736
1038
|
// src/ui/sso-provider.tsx
|
|
737
1039
|
import { createContext, useCallback, useContext, useEffect, useState } from "react";
|
|
738
|
-
import {
|
|
1040
|
+
import { jsxDEV as jsxDEV4 } from "react/jsx-dev-runtime";
|
|
739
1041
|
var CTX = createContext(undefined);
|
|
740
1042
|
var generateStorageKey = (tenantId) => {
|
|
741
1043
|
return `es-sso-user-${tenantId.replace(/[^a-zA-Z0-9]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "")}`;
|
|
@@ -875,10 +1177,10 @@ function SSOProvider({
|
|
|
875
1177
|
tokenUrl,
|
|
876
1178
|
refreshUrl
|
|
877
1179
|
};
|
|
878
|
-
return /* @__PURE__ */
|
|
1180
|
+
return /* @__PURE__ */ jsxDEV4(CTX.Provider, {
|
|
879
1181
|
value: contextValue,
|
|
880
1182
|
children
|
|
881
|
-
});
|
|
1183
|
+
}, undefined, false, undefined, this);
|
|
882
1184
|
}
|
|
883
1185
|
function useUser() {
|
|
884
1186
|
const context = useContext(CTX);
|
|
@@ -1001,16 +1303,17 @@ async function logout(logoutUrl) {
|
|
|
1001
1303
|
}
|
|
1002
1304
|
|
|
1003
1305
|
// src/index.ts
|
|
1004
|
-
async function enterpriseStandard(
|
|
1306
|
+
async function enterpriseStandard(appKey, initConfig) {
|
|
1005
1307
|
let vaultUrl;
|
|
1006
1308
|
let vaultToken;
|
|
1007
1309
|
let secrets;
|
|
1008
1310
|
const ioniteUrl = initConfig?.ioniteUrl ?? "https://ionite.com";
|
|
1009
|
-
if (
|
|
1311
|
+
if (appKey?.startsWith("IONITE_PUBLIC_DEMO_")) {
|
|
1010
1312
|
vaultUrl = "https://vault-ionite.ionite.dev/v1/secret/data";
|
|
1313
|
+
const port = appKey.slice("IONITE_PUBLIC_DEMO_".length);
|
|
1011
1314
|
secrets = {
|
|
1012
1315
|
sso: {
|
|
1013
|
-
path:
|
|
1316
|
+
path: `public/IONITE_PUBLIC_DEMO_SSO_${port}`,
|
|
1014
1317
|
token: "hvs.VGhD2hmXDH9PmZjTacZx0G5K"
|
|
1015
1318
|
}
|
|
1016
1319
|
};
|
|
@@ -1023,13 +1326,15 @@ async function enterpriseStandard(appId, appKey, initConfig) {
|
|
|
1023
1326
|
throw new Error("TODO tell them how to connect to ionite");
|
|
1024
1327
|
}
|
|
1025
1328
|
const defaultInstance2 = getDefaultInstance();
|
|
1026
|
-
const vaultClient =
|
|
1329
|
+
const vaultClient = vault(vaultUrl);
|
|
1027
1330
|
const result = {
|
|
1028
|
-
appId,
|
|
1029
1331
|
ioniteUrl,
|
|
1030
1332
|
defaultInstance: initConfig?.defaultInstance || initConfig?.defaultInstance !== false && !defaultInstance2,
|
|
1031
1333
|
vault: vaultClient,
|
|
1032
|
-
sso: secrets.sso ? sso(
|
|
1334
|
+
sso: secrets.sso ? sso({
|
|
1335
|
+
...await vaultClient.getSecret(secrets.sso.path, secrets.sso.token),
|
|
1336
|
+
...initConfig
|
|
1337
|
+
}) : undefined,
|
|
1033
1338
|
iam: secrets.iam ? await iam(await vaultClient.getSecret(secrets.iam.path, secrets.iam.token)) : undefined
|
|
1034
1339
|
};
|
|
1035
1340
|
if (result.defaultInstance) {
|
|
@@ -1043,9 +1348,11 @@ async function enterpriseStandard(appId, appKey, initConfig) {
|
|
|
1043
1348
|
export {
|
|
1044
1349
|
useUser,
|
|
1045
1350
|
useToken,
|
|
1351
|
+
tokenResponseSchema,
|
|
1046
1352
|
oidcCallbackSchema,
|
|
1047
1353
|
logout,
|
|
1048
1354
|
initiateLogin,
|
|
1355
|
+
idTokenClaimsSchema,
|
|
1049
1356
|
handler,
|
|
1050
1357
|
getUser,
|
|
1051
1358
|
getRequiredUser,
|
|
@@ -1054,5 +1361,6 @@ export {
|
|
|
1054
1361
|
SignedOut,
|
|
1055
1362
|
SignedIn,
|
|
1056
1363
|
SignInLoading,
|
|
1057
|
-
SSOProvider
|
|
1364
|
+
SSOProvider,
|
|
1365
|
+
InMemorySessionStore
|
|
1058
1366
|
};
|