@enterprisestandard/react 0.0.2 → 0.0.3-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2 -2
- package/dist/index.js +264 -127
- package/dist/server.d.ts +6 -5
- package/dist/sso.d.ts +24 -29
- package/dist/ui/sign-in-loading.d.ts +3 -1
- package/dist/ui/sso-provider.d.ts +14 -3
- package/dist/vault.d.ts +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { type IAM } from './iam';
|
|
2
|
-
import {
|
|
2
|
+
import { type SSO } from './sso';
|
|
3
3
|
import { type Vault } from './vault';
|
|
4
4
|
export type EnterpriseStandard = {
|
|
5
5
|
ioniteUrl: string;
|
|
6
6
|
appId: string;
|
|
7
7
|
defaultInstance: boolean;
|
|
8
8
|
vault: Vault;
|
|
9
|
-
sso?:
|
|
9
|
+
sso?: SSO;
|
|
10
10
|
iam?: IAM;
|
|
11
11
|
};
|
|
12
12
|
type ESConfig = {
|
package/dist/index.js
CHANGED
|
@@ -31,35 +31,31 @@ function getES(es) {
|
|
|
31
31
|
|
|
32
32
|
// src/sso.ts
|
|
33
33
|
var jwksCache = new Map;
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
async getUser(request) {
|
|
47
|
-
if (!this.config) {
|
|
34
|
+
function sso(config) {
|
|
35
|
+
const configWithDefaults = {
|
|
36
|
+
...config,
|
|
37
|
+
secure: config.secure !== undefined ? config.secure : false,
|
|
38
|
+
sameSite: config.sameSite !== undefined ? config.sameSite : "Lax",
|
|
39
|
+
cookiePrefix: config.cookiePrefix ?? `es.sso.${config.client_id}`,
|
|
40
|
+
cookiePath: config.cookiePath ?? "/"
|
|
41
|
+
};
|
|
42
|
+
async function getUser(request) {
|
|
43
|
+
if (!configWithDefaults) {
|
|
48
44
|
console.error("SSO Manager not initialized");
|
|
49
45
|
return;
|
|
50
46
|
}
|
|
51
47
|
try {
|
|
52
|
-
const token = await
|
|
48
|
+
const token = await getTokenFromCookies(request);
|
|
53
49
|
if (!token)
|
|
54
50
|
return;
|
|
55
|
-
return await
|
|
51
|
+
return await parseUser(token);
|
|
56
52
|
} catch (error) {
|
|
57
53
|
console.error("Error parsing user from cookies:", error);
|
|
58
54
|
return;
|
|
59
55
|
}
|
|
60
56
|
}
|
|
61
|
-
async getRequiredUser(request) {
|
|
62
|
-
const user = await
|
|
57
|
+
async function getRequiredUser(request) {
|
|
58
|
+
const user = await getUser(request);
|
|
63
59
|
if (user)
|
|
64
60
|
return user;
|
|
65
61
|
throw new Response("Unauthorized", {
|
|
@@ -67,38 +63,38 @@ class SSOManager {
|
|
|
67
63
|
statusText: "Unauthorized"
|
|
68
64
|
});
|
|
69
65
|
}
|
|
70
|
-
async initiateLogin(
|
|
71
|
-
if (!
|
|
66
|
+
async function initiateLogin({ landingUrl, errorUrl }) {
|
|
67
|
+
if (!configWithDefaults) {
|
|
72
68
|
console.error("SSO Manager not initialized");
|
|
73
69
|
return Promise.resolve(new Response("SSO Manager not initialized", { status: 503 }));
|
|
74
70
|
}
|
|
75
|
-
const state =
|
|
76
|
-
const codeVerifier =
|
|
77
|
-
const url = new URL(
|
|
78
|
-
url.searchParams.append("client_id",
|
|
79
|
-
url.searchParams.append("redirect_uri",
|
|
71
|
+
const state = generateRandomString();
|
|
72
|
+
const codeVerifier = generateRandomString(64);
|
|
73
|
+
const url = new URL(configWithDefaults.authorization_url);
|
|
74
|
+
url.searchParams.append("client_id", configWithDefaults.client_id);
|
|
75
|
+
url.searchParams.append("redirect_uri", configWithDefaults.redirect_uri);
|
|
80
76
|
url.searchParams.append("response_type", "code");
|
|
81
|
-
url.searchParams.append("scope",
|
|
77
|
+
url.searchParams.append("scope", configWithDefaults.scope);
|
|
82
78
|
url.searchParams.append("state", state);
|
|
83
|
-
const codeChallenge = await
|
|
79
|
+
const codeChallenge = await pkceChallengeFromVerifier(codeVerifier);
|
|
84
80
|
url.searchParams.append("code_challenge", codeChallenge);
|
|
85
81
|
url.searchParams.append("code_challenge_method", "S256");
|
|
86
82
|
const val = {
|
|
87
83
|
state,
|
|
88
84
|
codeVerifier,
|
|
89
|
-
|
|
90
|
-
|
|
85
|
+
landingUrl,
|
|
86
|
+
errorUrl
|
|
91
87
|
};
|
|
92
88
|
return new Response("Redirecting to SSO Provider", {
|
|
93
89
|
status: 302,
|
|
94
90
|
headers: {
|
|
95
91
|
Location: url.toString(),
|
|
96
|
-
"Set-Cookie":
|
|
92
|
+
"Set-Cookie": createCookie("state", val, 86400)
|
|
97
93
|
}
|
|
98
94
|
});
|
|
99
95
|
}
|
|
100
|
-
async callbackHandler(request) {
|
|
101
|
-
if (!
|
|
96
|
+
async function callbackHandler(request) {
|
|
97
|
+
if (!configWithDefaults) {
|
|
102
98
|
console.error("SSO Manager not initialized");
|
|
103
99
|
return Promise.resolve(new Response("SSO Manager not initialized", { status: 503 }));
|
|
104
100
|
}
|
|
@@ -107,37 +103,37 @@ class SSOManager {
|
|
|
107
103
|
try {
|
|
108
104
|
const codeFromUrl = must(params.get("code"), 'OIDC "code" was not passed as a search param, ensure that the SSO login completed successfully');
|
|
109
105
|
const stateFromUrl = must(params.get("state"), 'OIDC "state" was not passed as a search param, ensure that the SSO login completed successfully');
|
|
110
|
-
const cookie =
|
|
111
|
-
const { codeVerifier, state,
|
|
106
|
+
const cookie = getCookie("state", request, true);
|
|
107
|
+
const { codeVerifier, state, landingUrl } = cookie ?? {};
|
|
112
108
|
must(codeVerifier, 'OIDC "codeVerifier" was not present in cookies, ensure that the SSO login was initiated correctly');
|
|
113
109
|
must(state, 'OIDC "stateVerifier" was not present in cookies, ensure that the SSO login was initiated correctly');
|
|
114
|
-
must(
|
|
110
|
+
must(landingUrl, 'OIDC "landingUrl" was not present in cookies');
|
|
115
111
|
if (stateFromUrl !== state) {
|
|
116
112
|
throw new Error('SSO State Verifier failed, the "state" request parameter does not equal the "state" in the SSO cookie');
|
|
117
113
|
}
|
|
118
|
-
const tokenResponse = await
|
|
119
|
-
const user = await
|
|
114
|
+
const tokenResponse = await exchangeCodeForToken(codeFromUrl, codeVerifier);
|
|
115
|
+
const user = await parseUser(tokenResponse);
|
|
120
116
|
return new Response("Authentication successful, redirecting", {
|
|
121
117
|
status: 302,
|
|
122
118
|
headers: [
|
|
123
|
-
["Location",
|
|
124
|
-
["Set-Cookie",
|
|
125
|
-
...
|
|
119
|
+
["Location", landingUrl],
|
|
120
|
+
["Set-Cookie", clearCookie("state")],
|
|
121
|
+
...createJwtCookies(tokenResponse, user.sso.expires)
|
|
126
122
|
]
|
|
127
123
|
});
|
|
128
124
|
} catch (error) {
|
|
129
125
|
console.error("Error during sign-in callback:", error);
|
|
130
126
|
try {
|
|
131
|
-
const cookie =
|
|
132
|
-
const {
|
|
133
|
-
if (
|
|
134
|
-
return new Response("Redirecting to error
|
|
127
|
+
const cookie = getCookie("state", request, true);
|
|
128
|
+
const { errorUrl } = cookie ?? {};
|
|
129
|
+
if (errorUrl) {
|
|
130
|
+
return new Response("Redirecting to error url", {
|
|
135
131
|
status: 302,
|
|
136
|
-
headers: [["Location",
|
|
132
|
+
headers: [["Location", errorUrl]]
|
|
137
133
|
});
|
|
138
134
|
}
|
|
139
135
|
} catch (_err) {
|
|
140
|
-
console.warn("Error parsing the
|
|
136
|
+
console.warn("Error parsing the errorUrl from the OIDC cookie");
|
|
141
137
|
}
|
|
142
138
|
console.warn("No error page was found in the cookies. The user will be shown a default error page.");
|
|
143
139
|
return new Response("An error occurred during authentication, please return to the application homepage and try again.", {
|
|
@@ -145,10 +141,10 @@ class SSOManager {
|
|
|
145
141
|
});
|
|
146
142
|
}
|
|
147
143
|
}
|
|
148
|
-
async parseUser(token) {
|
|
149
|
-
if (!
|
|
144
|
+
async function parseUser(token) {
|
|
145
|
+
if (!configWithDefaults)
|
|
150
146
|
throw new Error("SSO Manager not initialized");
|
|
151
|
-
const idToken = await
|
|
147
|
+
const idToken = await parseJwt(token.id_token);
|
|
152
148
|
const expiresIn = Number(token.refresh_expires_in ?? token.expires_in ?? 3600);
|
|
153
149
|
const expires = token.expires ? new Date(token.expires) : new Date(Date.now() + expiresIn * 1000);
|
|
154
150
|
return {
|
|
@@ -165,12 +161,12 @@ class SSOManager {
|
|
|
165
161
|
sso: {
|
|
166
162
|
profile: {
|
|
167
163
|
...idToken,
|
|
168
|
-
iss: idToken.iss ||
|
|
169
|
-
aud: idToken.aud ||
|
|
164
|
+
iss: idToken.iss || configWithDefaults.authority,
|
|
165
|
+
aud: idToken.aud || configWithDefaults.client_id
|
|
170
166
|
},
|
|
171
167
|
tenant: {
|
|
172
|
-
id: idToken.idp || idToken.iss ||
|
|
173
|
-
name: idToken.iss ||
|
|
168
|
+
id: idToken.idp || idToken.iss || configWithDefaults.authority,
|
|
169
|
+
name: idToken.iss || configWithDefaults.authority
|
|
174
170
|
},
|
|
175
171
|
scope: token.scope,
|
|
176
172
|
tokenType: token.token_type,
|
|
@@ -179,15 +175,15 @@ class SSOManager {
|
|
|
179
175
|
}
|
|
180
176
|
};
|
|
181
177
|
}
|
|
182
|
-
async exchangeCodeForToken(code, codeVerifier) {
|
|
183
|
-
if (!
|
|
178
|
+
async function exchangeCodeForToken(code, codeVerifier) {
|
|
179
|
+
if (!configWithDefaults)
|
|
184
180
|
throw new Error("SSO Manager not initialized");
|
|
185
|
-
const tokenUrl =
|
|
181
|
+
const tokenUrl = configWithDefaults.token_url;
|
|
186
182
|
const body = new URLSearchParams;
|
|
187
183
|
body.append("grant_type", "authorization_code");
|
|
188
184
|
body.append("code", code);
|
|
189
|
-
body.append("redirect_uri",
|
|
190
|
-
body.append("client_id",
|
|
185
|
+
body.append("redirect_uri", configWithDefaults.redirect_uri);
|
|
186
|
+
body.append("client_id", configWithDefaults.client_id);
|
|
191
187
|
body.append("code_verifier", codeVerifier);
|
|
192
188
|
try {
|
|
193
189
|
const response = await fetch(tokenUrl, {
|
|
@@ -209,15 +205,15 @@ class SSOManager {
|
|
|
209
205
|
throw new Error("Error during token exchange");
|
|
210
206
|
}
|
|
211
207
|
}
|
|
212
|
-
async refreshToken(
|
|
213
|
-
return
|
|
214
|
-
if (!
|
|
208
|
+
async function refreshToken(refreshToken2) {
|
|
209
|
+
return retryWithBackoff(async () => {
|
|
210
|
+
if (!configWithDefaults)
|
|
215
211
|
throw new Error("SSO Manager not initialized");
|
|
216
|
-
const tokenUrl =
|
|
212
|
+
const tokenUrl = configWithDefaults.token_url;
|
|
217
213
|
const body = new URLSearchParams;
|
|
218
214
|
body.append("grant_type", "refresh_token");
|
|
219
|
-
body.append("refresh_token",
|
|
220
|
-
body.append("client_id",
|
|
215
|
+
body.append("refresh_token", refreshToken2);
|
|
216
|
+
body.append("client_id", configWithDefaults.client_id);
|
|
221
217
|
const response = await fetch(tokenUrl, {
|
|
222
218
|
method: "POST",
|
|
223
219
|
headers: {
|
|
@@ -234,12 +230,12 @@ class SSOManager {
|
|
|
234
230
|
return data;
|
|
235
231
|
});
|
|
236
232
|
}
|
|
237
|
-
async fetchJwks() {
|
|
238
|
-
const url =
|
|
233
|
+
async function fetchJwks() {
|
|
234
|
+
const url = configWithDefaults.jwks_uri || `${configWithDefaults.authority}/protocol/openid-connect/certs`;
|
|
239
235
|
if (jwksCache.has(url))
|
|
240
236
|
return jwksCache.get(url);
|
|
241
|
-
return
|
|
242
|
-
if (!
|
|
237
|
+
return retryWithBackoff(async () => {
|
|
238
|
+
if (!configWithDefaults)
|
|
243
239
|
throw new Error("SSO Manager not initialized");
|
|
244
240
|
const response = await fetch(url);
|
|
245
241
|
if (!response.ok)
|
|
@@ -249,7 +245,7 @@ class SSOManager {
|
|
|
249
245
|
return jwks;
|
|
250
246
|
});
|
|
251
247
|
}
|
|
252
|
-
async retryWithBackoff(operation, maxRetries = 3, baseDelay = 1000, maxDelay = 30000) {
|
|
248
|
+
async function retryWithBackoff(operation, maxRetries = 3, baseDelay = 1000, maxDelay = 30000) {
|
|
253
249
|
let lastError = new Error("Placeholder Error");
|
|
254
250
|
for (let attempt = 0;attempt <= maxRetries; attempt++) {
|
|
255
251
|
try {
|
|
@@ -270,7 +266,7 @@ class SSOManager {
|
|
|
270
266
|
}
|
|
271
267
|
throw lastError;
|
|
272
268
|
}
|
|
273
|
-
async parseJwt(token) {
|
|
269
|
+
async function parseJwt(token) {
|
|
274
270
|
try {
|
|
275
271
|
const parts = token.split(".");
|
|
276
272
|
if (parts.length !== 3)
|
|
@@ -278,7 +274,7 @@ class SSOManager {
|
|
|
278
274
|
const header = JSON.parse(atob(parts[0].replace(/-/g, "+").replace(/_/g, "/")));
|
|
279
275
|
const payload = JSON.parse(atob(parts[1].replace(/-/g, "+").replace(/_/g, "/")));
|
|
280
276
|
const signature = parts[2].replace(/-/g, "+").replace(/_/g, "/");
|
|
281
|
-
const publicKey = await
|
|
277
|
+
const publicKey = await getPublicKey(header.kid);
|
|
282
278
|
const encoder = new TextEncoder;
|
|
283
279
|
const data = encoder.encode(`${parts[0]}.${parts[1]}`);
|
|
284
280
|
const isValid = await crypto.subtle.verify("RSASSA-PKCS1-v1_5", publicKey, Uint8Array.from(atob(signature), (c) => c.charCodeAt(0)), data);
|
|
@@ -290,12 +286,12 @@ class SSOManager {
|
|
|
290
286
|
throw e;
|
|
291
287
|
}
|
|
292
288
|
}
|
|
293
|
-
generateRandomString(length = 32) {
|
|
289
|
+
function generateRandomString(length = 32) {
|
|
294
290
|
const array = new Uint8Array(length);
|
|
295
291
|
crypto.getRandomValues(array);
|
|
296
292
|
return Array.from(array, (byte) => byte.toString(16).padStart(2, "0")).join("").substring(0, length);
|
|
297
293
|
}
|
|
298
|
-
async pkceChallengeFromVerifier(verifier) {
|
|
294
|
+
async function pkceChallengeFromVerifier(verifier) {
|
|
299
295
|
const encoder = new TextEncoder;
|
|
300
296
|
const data = encoder.encode(verifier);
|
|
301
297
|
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
@@ -303,8 +299,8 @@ class SSOManager {
|
|
|
303
299
|
const hashBase64 = btoa(String.fromCharCode(...hashArray)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
304
300
|
return hashBase64;
|
|
305
301
|
}
|
|
306
|
-
async getPublicKey(kid) {
|
|
307
|
-
const jwks = await
|
|
302
|
+
async function getPublicKey(kid) {
|
|
303
|
+
const jwks = await fetchJwks();
|
|
308
304
|
const key = jwks.keys.find((k) => k.kid === kid);
|
|
309
305
|
if (!key)
|
|
310
306
|
throw new Error("Public key not found");
|
|
@@ -315,7 +311,7 @@ class SSOManager {
|
|
|
315
311
|
}, { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" }, false, ["verify"]);
|
|
316
312
|
return publicKey;
|
|
317
313
|
}
|
|
318
|
-
createJwtCookies(token, expires) {
|
|
314
|
+
function createJwtCookies(token, expires) {
|
|
319
315
|
const control = {
|
|
320
316
|
expires_in: token.expires_in,
|
|
321
317
|
refresh_expires_in: token.refresh_expires_in,
|
|
@@ -325,17 +321,17 @@ class SSOManager {
|
|
|
325
321
|
expires: expires.toISOString()
|
|
326
322
|
};
|
|
327
323
|
return [
|
|
328
|
-
["Set-Cookie",
|
|
329
|
-
["Set-Cookie",
|
|
330
|
-
["Set-Cookie",
|
|
331
|
-
["Set-Cookie",
|
|
324
|
+
["Set-Cookie", createCookie("access", token.access_token, expires)],
|
|
325
|
+
["Set-Cookie", createCookie("id", token.id_token, expires)],
|
|
326
|
+
["Set-Cookie", createCookie("refresh", token.refresh_token ?? "", expires)],
|
|
327
|
+
["Set-Cookie", createCookie("control", control, expires)]
|
|
332
328
|
];
|
|
333
329
|
}
|
|
334
|
-
async
|
|
335
|
-
const access_token =
|
|
336
|
-
const id_token =
|
|
337
|
-
const refresh_token =
|
|
338
|
-
const control =
|
|
330
|
+
async function getTokenFromCookies(req) {
|
|
331
|
+
const access_token = getCookie("access", req);
|
|
332
|
+
const id_token = getCookie("id", req);
|
|
333
|
+
const refresh_token = getCookie("refresh", req);
|
|
334
|
+
const control = getCookie("control", req, true);
|
|
339
335
|
if (!access_token || !id_token || !refresh_token || !control) {
|
|
340
336
|
return;
|
|
341
337
|
}
|
|
@@ -346,12 +342,18 @@ class SSOManager {
|
|
|
346
342
|
...control
|
|
347
343
|
};
|
|
348
344
|
if (control.expires && refresh_token && Date.now() > new Date(control.expires).getTime()) {
|
|
349
|
-
tokenResponse = await
|
|
345
|
+
tokenResponse = await refreshToken(refresh_token);
|
|
350
346
|
}
|
|
351
347
|
return tokenResponse;
|
|
352
348
|
}
|
|
353
|
-
|
|
354
|
-
|
|
349
|
+
async function getJwt(request) {
|
|
350
|
+
const tokenResponse = await getTokenFromCookies(request);
|
|
351
|
+
if (!tokenResponse)
|
|
352
|
+
return;
|
|
353
|
+
return tokenResponse.access_token;
|
|
354
|
+
}
|
|
355
|
+
function createCookie(name, value, expires) {
|
|
356
|
+
name = `${configWithDefaults.cookiePrefix}.${name}`;
|
|
355
357
|
if (typeof value !== "string") {
|
|
356
358
|
value = btoa(JSON.stringify(value));
|
|
357
359
|
}
|
|
@@ -366,16 +368,16 @@ class SSOManager {
|
|
|
366
368
|
if (value.length > 4000) {
|
|
367
369
|
throw new Error(`Error setting cookie: ${name}. Cookie length is: ${value.length}`);
|
|
368
370
|
}
|
|
369
|
-
return `${name}=${value}; ${exp}; Path=${
|
|
371
|
+
return `${name}=${value}; ${exp}; Path=${configWithDefaults.cookiePath}; HttpOnly;${configWithDefaults.secure ? " Secure;" : ""} SameSite=${configWithDefaults.sameSite};`;
|
|
370
372
|
}
|
|
371
|
-
clearCookie(name) {
|
|
372
|
-
return `${
|
|
373
|
+
function clearCookie(name) {
|
|
374
|
+
return `${configWithDefaults.cookiePrefix}.${name}=; Max-Age=0; Path=${configWithDefaults.cookiePath}; HttpOnly;${configWithDefaults.secure ? " Secure;" : ""} SameSite=${configWithDefaults.sameSite};`;
|
|
373
375
|
}
|
|
374
|
-
getCookie(name, req, parse = false) {
|
|
376
|
+
function getCookie(name, req, parse = false) {
|
|
375
377
|
const header = req.headers.get("cookie");
|
|
376
378
|
if (!header)
|
|
377
379
|
return null;
|
|
378
|
-
const cookie = header.split(";").find((row) => row.trim().startsWith(`${
|
|
380
|
+
const cookie = header.split(";").find((row) => row.trim().startsWith(`${configWithDefaults.cookiePrefix}.${name}=`));
|
|
379
381
|
if (!cookie)
|
|
380
382
|
return null;
|
|
381
383
|
const val = cookie.split("=")[1].trim();
|
|
@@ -384,17 +386,73 @@ class SSOManager {
|
|
|
384
386
|
const str = atob(val);
|
|
385
387
|
return JSON.parse(str);
|
|
386
388
|
}
|
|
389
|
+
async function handler(request, handlerConfig) {
|
|
390
|
+
let { loginUrl, userUrl, errorUrl, landingUrl, tokenUrl, refreshUrl } = handlerConfig ?? {};
|
|
391
|
+
if (!loginUrl)
|
|
392
|
+
loginUrl = "*";
|
|
393
|
+
const path = new URL(request.url).pathname;
|
|
394
|
+
if (new URL(config.redirect_uri).pathname === path) {
|
|
395
|
+
return callbackHandler(request);
|
|
396
|
+
}
|
|
397
|
+
if (userUrl === path) {
|
|
398
|
+
const user = await getUser(request);
|
|
399
|
+
if (!user) {
|
|
400
|
+
return new Response("User not logged in", { status: 401 });
|
|
401
|
+
}
|
|
402
|
+
return new Response(JSON.stringify(user), {
|
|
403
|
+
headers: [["Content-Type", "application/json"]]
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
if (tokenUrl === path) {
|
|
407
|
+
const tokenResponse = await getTokenFromCookies(request);
|
|
408
|
+
if (!tokenResponse) {
|
|
409
|
+
return new Response("User not logged in", { status: 401 });
|
|
410
|
+
}
|
|
411
|
+
return new Response(JSON.stringify({
|
|
412
|
+
token: tokenResponse.access_token,
|
|
413
|
+
expires: tokenResponse.expires
|
|
414
|
+
}), {
|
|
415
|
+
headers: [["Content-Type", "application/json"]]
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
if (refreshUrl === path) {
|
|
419
|
+
const tokenResponse = await getTokenFromCookies(request);
|
|
420
|
+
if (!tokenResponse) {
|
|
421
|
+
return new Response("User not logged in", { status: 401 });
|
|
422
|
+
}
|
|
423
|
+
return new Response("Refresh Complete", { status: 200 });
|
|
424
|
+
}
|
|
425
|
+
if (loginUrl === "*" || loginUrl === path) {
|
|
426
|
+
return initiateLogin({
|
|
427
|
+
landingUrl: landingUrl || "/",
|
|
428
|
+
errorUrl
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
return new Response("Not Found", { status: 404 });
|
|
432
|
+
}
|
|
433
|
+
return {
|
|
434
|
+
getUser,
|
|
435
|
+
getRequiredUser,
|
|
436
|
+
getJwt,
|
|
437
|
+
initiateLogin,
|
|
438
|
+
callbackHandler,
|
|
439
|
+
handler
|
|
440
|
+
};
|
|
387
441
|
}
|
|
388
442
|
|
|
389
443
|
// src/vault.ts
|
|
390
|
-
|
|
444
|
+
function vault(url, token) {
|
|
391
445
|
async function getFullSecret(path) {
|
|
392
446
|
const resp = await fetch(`${url}/${path}`, { headers: { "X-Vault-Token": token } });
|
|
393
447
|
if (resp.status !== 200) {
|
|
394
448
|
throw new Error(`Vault returned invalid status, ${resp.status}: '${resp.statusText}' from URL: ${url}`);
|
|
395
449
|
}
|
|
396
|
-
|
|
397
|
-
|
|
450
|
+
try {
|
|
451
|
+
const secret = await resp.json();
|
|
452
|
+
return secret.data;
|
|
453
|
+
} catch (cause) {
|
|
454
|
+
throw new Error("Error retrieving secret", { cause });
|
|
455
|
+
}
|
|
398
456
|
}
|
|
399
457
|
return {
|
|
400
458
|
url,
|
|
@@ -508,8 +566,8 @@ function oidcCallbackSchema(vendor) {
|
|
|
508
566
|
};
|
|
509
567
|
}
|
|
510
568
|
// src/server.ts
|
|
511
|
-
function getSSO(
|
|
512
|
-
es = getES(es);
|
|
569
|
+
function getSSO(config) {
|
|
570
|
+
const es = getES(config?.es);
|
|
513
571
|
if (!es.sso) {
|
|
514
572
|
console.error("TODO tell them how to connect SSO");
|
|
515
573
|
return;
|
|
@@ -523,32 +581,38 @@ function unavailable() {
|
|
|
523
581
|
headers: { "Content-Type": "application/json" }
|
|
524
582
|
});
|
|
525
583
|
}
|
|
526
|
-
async function getUser(request,
|
|
527
|
-
return getSSO(
|
|
584
|
+
async function getUser(request, config) {
|
|
585
|
+
return getSSO(config)?.getUser(request);
|
|
528
586
|
}
|
|
529
|
-
async function getRequiredUser(request,
|
|
530
|
-
const
|
|
531
|
-
if (!
|
|
587
|
+
async function getRequiredUser(request, config) {
|
|
588
|
+
const sso2 = getSSO(config);
|
|
589
|
+
if (!sso2)
|
|
532
590
|
throw unavailable();
|
|
533
|
-
return
|
|
591
|
+
return sso2.getRequiredUser(request);
|
|
534
592
|
}
|
|
535
|
-
async function initiateLogin(
|
|
536
|
-
const
|
|
537
|
-
if (!
|
|
593
|
+
async function initiateLogin(config) {
|
|
594
|
+
const sso2 = getSSO(config);
|
|
595
|
+
if (!sso2)
|
|
538
596
|
throw unavailable();
|
|
539
|
-
return
|
|
597
|
+
return sso2.initiateLogin(config);
|
|
540
598
|
}
|
|
541
|
-
async function callback(request,
|
|
542
|
-
const
|
|
543
|
-
if (!
|
|
599
|
+
async function callback(request, config) {
|
|
600
|
+
const sso2 = getSSO(config);
|
|
601
|
+
if (!sso2)
|
|
544
602
|
throw unavailable();
|
|
545
|
-
return
|
|
603
|
+
return sso2.callbackHandler(request);
|
|
604
|
+
}
|
|
605
|
+
async function handler(request, config) {
|
|
606
|
+
const sso2 = getSSO(config);
|
|
607
|
+
if (!sso2)
|
|
608
|
+
throw unavailable();
|
|
609
|
+
return sso2.handler(request, config);
|
|
546
610
|
}
|
|
547
611
|
// src/ui/sign-in-loading.tsx
|
|
548
612
|
import { jsxDEV, Fragment } from "react/jsx-dev-runtime";
|
|
549
|
-
function SignInLoading({ children }) {
|
|
613
|
+
function SignInLoading({ complete = false, children }) {
|
|
550
614
|
const { isLoading } = useUser();
|
|
551
|
-
if (isLoading)
|
|
615
|
+
if (isLoading && !complete)
|
|
552
616
|
return /* @__PURE__ */ jsxDEV(Fragment, {
|
|
553
617
|
children
|
|
554
618
|
}, undefined, false, undefined, this);
|
|
@@ -577,7 +641,7 @@ function SignedOut({ children }) {
|
|
|
577
641
|
// src/ui/sso-provider.tsx
|
|
578
642
|
import { createContext, useCallback, useContext, useEffect, useState } from "react";
|
|
579
643
|
import { jsxDEV as jsxDEV4 } from "react/jsx-dev-runtime";
|
|
580
|
-
var
|
|
644
|
+
var CTX = createContext(undefined);
|
|
581
645
|
var generateStorageKey = (tenantId) => {
|
|
582
646
|
return `es-sso-user-${tenantId.replace(/[^a-zA-Z0-9]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "")}`;
|
|
583
647
|
};
|
|
@@ -586,6 +650,8 @@ function SSOProvider({
|
|
|
586
650
|
storage = "memory",
|
|
587
651
|
storageKey,
|
|
588
652
|
userUrl,
|
|
653
|
+
tokenUrl,
|
|
654
|
+
refreshUrl,
|
|
589
655
|
disableListener = false,
|
|
590
656
|
children
|
|
591
657
|
}) {
|
|
@@ -598,7 +664,7 @@ function SSOProvider({
|
|
|
598
664
|
return user2.sso?.tenant?.id === tenantId;
|
|
599
665
|
}, [tenantId]);
|
|
600
666
|
const loadUserFromStorage = useCallback(() => {
|
|
601
|
-
if (storage === "memory")
|
|
667
|
+
if (storage === "memory" || typeof window === "undefined")
|
|
602
668
|
return null;
|
|
603
669
|
try {
|
|
604
670
|
const storageObject = storage === "local" ? localStorage : sessionStorage;
|
|
@@ -616,7 +682,7 @@ function SSOProvider({
|
|
|
616
682
|
}
|
|
617
683
|
}, [storage, actualStorageKey, isValidUser]);
|
|
618
684
|
const saveUserToStorage = useCallback((user2) => {
|
|
619
|
-
if (storage === "memory")
|
|
685
|
+
if (storage === "memory" || typeof window === "undefined")
|
|
620
686
|
return;
|
|
621
687
|
try {
|
|
622
688
|
const storageObject = storage === "local" ? localStorage : sessionStorage;
|
|
@@ -670,12 +736,9 @@ function SSOProvider({
|
|
|
670
736
|
}
|
|
671
737
|
}, [loadUserFromStorage, userUrl, fetchUserFromUrl]);
|
|
672
738
|
useEffect(() => {
|
|
673
|
-
console.log(disableListener, storage, actualStorageKey);
|
|
674
739
|
if (disableListener || storage === "memory")
|
|
675
740
|
return;
|
|
676
|
-
console.log("set up listener");
|
|
677
741
|
const handleStorageChange = (event) => {
|
|
678
|
-
console.log("lsitened", actualStorageKey);
|
|
679
742
|
if (event.key !== actualStorageKey)
|
|
680
743
|
return;
|
|
681
744
|
if (event.newValue === null) {
|
|
@@ -702,20 +765,92 @@ function SSOProvider({
|
|
|
702
765
|
const contextValue = {
|
|
703
766
|
user,
|
|
704
767
|
setUser,
|
|
705
|
-
isLoading
|
|
768
|
+
isLoading,
|
|
769
|
+
tokenUrl,
|
|
770
|
+
refreshUrl
|
|
706
771
|
};
|
|
707
|
-
return /* @__PURE__ */ jsxDEV4(
|
|
772
|
+
return /* @__PURE__ */ jsxDEV4(CTX.Provider, {
|
|
708
773
|
value: contextValue,
|
|
709
774
|
children
|
|
710
775
|
}, undefined, false, undefined, this);
|
|
711
776
|
}
|
|
712
777
|
function useUser() {
|
|
713
|
-
const context = useContext(
|
|
778
|
+
const context = useContext(CTX);
|
|
714
779
|
if (context === undefined) {
|
|
715
780
|
throw new Error("useUser must be used within a SSOProvider");
|
|
716
781
|
}
|
|
717
782
|
return context;
|
|
718
783
|
}
|
|
784
|
+
function useToken() {
|
|
785
|
+
const context = useContext(CTX);
|
|
786
|
+
if (context === undefined) {
|
|
787
|
+
throw new Error("useToken must be used within a SSOProvider");
|
|
788
|
+
}
|
|
789
|
+
const { tokenUrl, refreshUrl } = context;
|
|
790
|
+
if (!tokenUrl || !refreshUrl) {
|
|
791
|
+
throw new Error('useToken requires that a "tokenUrl" and "refreshUrl" be set in the SSOProvider');
|
|
792
|
+
}
|
|
793
|
+
const [token, setToken] = useState(null);
|
|
794
|
+
const [expires, setExpires] = useState(null);
|
|
795
|
+
const [isLoading, setIsLoading] = useState(!!tokenUrl);
|
|
796
|
+
const [error, setError] = useState(null);
|
|
797
|
+
const fetchJwt = useCallback(async (url) => {
|
|
798
|
+
setIsLoading(true);
|
|
799
|
+
setError(null);
|
|
800
|
+
try {
|
|
801
|
+
const response = await fetch(url);
|
|
802
|
+
if (!response.ok) {
|
|
803
|
+
throw new Error(`Failed to fetch JWT: ${response.status} ${response.statusText}`);
|
|
804
|
+
}
|
|
805
|
+
const data = await response.json();
|
|
806
|
+
setToken(data.token);
|
|
807
|
+
setExpires(new Date(data.expires));
|
|
808
|
+
} catch (err) {
|
|
809
|
+
const error2 = err instanceof Error ? err : new Error(String(err));
|
|
810
|
+
setError(error2);
|
|
811
|
+
setToken(null);
|
|
812
|
+
setExpires(null);
|
|
813
|
+
console.error("Error fetching JWT:", error2);
|
|
814
|
+
} finally {
|
|
815
|
+
setIsLoading(false);
|
|
816
|
+
}
|
|
817
|
+
}, []);
|
|
818
|
+
const refresh = useCallback(async () => {
|
|
819
|
+
const url = refreshUrl || tokenUrl;
|
|
820
|
+
if (!url) {
|
|
821
|
+
console.warn("No tokenUrl or refreshUrl provided");
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
await fetchJwt(url);
|
|
825
|
+
}, [refreshUrl, tokenUrl, fetchJwt]);
|
|
826
|
+
useEffect(() => {
|
|
827
|
+
if (!tokenUrl) {
|
|
828
|
+
setIsLoading(false);
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
fetchJwt(tokenUrl);
|
|
832
|
+
}, [tokenUrl, fetchJwt]);
|
|
833
|
+
useEffect(() => {
|
|
834
|
+
if (!expires || !refreshUrl)
|
|
835
|
+
return;
|
|
836
|
+
const checkExpiration = () => {
|
|
837
|
+
const now = new Date;
|
|
838
|
+
const timeUntilExpiry = expires.getTime() - now.getTime();
|
|
839
|
+
if (timeUntilExpiry <= 60000 && timeUntilExpiry > 0) {
|
|
840
|
+
refresh();
|
|
841
|
+
}
|
|
842
|
+
};
|
|
843
|
+
checkExpiration();
|
|
844
|
+
const interval = setInterval(checkExpiration, 30000);
|
|
845
|
+
return () => clearInterval(interval);
|
|
846
|
+
}, [expires, refreshUrl, refresh]);
|
|
847
|
+
return {
|
|
848
|
+
token,
|
|
849
|
+
isLoading,
|
|
850
|
+
error,
|
|
851
|
+
refresh
|
|
852
|
+
};
|
|
853
|
+
}
|
|
719
854
|
|
|
720
855
|
// src/index.ts
|
|
721
856
|
async function enterpriseStandard(appId, appKey, initConfig) {
|
|
@@ -724,9 +859,9 @@ async function enterpriseStandard(appId, appKey, initConfig) {
|
|
|
724
859
|
let paths;
|
|
725
860
|
const ioniteUrl = initConfig?.ioniteUrl ?? "https://ionite.com";
|
|
726
861
|
if (appId === "IONITE_PUBLIC_DEMO") {
|
|
727
|
-
vaultUrl = "https://vault.ionite.dev/v1/secret/data";
|
|
728
|
-
vaultToken = "hvs.
|
|
729
|
-
paths = { sso: "ionite/
|
|
862
|
+
vaultUrl = "https://vault-ionite.ionite.dev/v1/secret/data";
|
|
863
|
+
vaultToken = "hvs.NuiBSLuFk5Ju4JDOUwTOlSlP";
|
|
864
|
+
paths = { sso: "ionite/IONITE_PUBLIC_DEMO" };
|
|
730
865
|
} else if (appKey) {
|
|
731
866
|
if (!vaultUrl || !vaultToken) {
|
|
732
867
|
throw new Error("TODO something is wrong with the ionite config, handle this error");
|
|
@@ -742,7 +877,7 @@ async function enterpriseStandard(appId, appKey, initConfig) {
|
|
|
742
877
|
ioniteUrl,
|
|
743
878
|
defaultInstance: initConfig?.defaultInstance || initConfig?.defaultInstance !== false && !defaultInstance2,
|
|
744
879
|
vault: vaultClient,
|
|
745
|
-
sso: paths.sso ?
|
|
880
|
+
sso: paths.sso ? sso(await vaultClient.getSecret(paths.sso)) : undefined,
|
|
746
881
|
iam: paths.iam ? await iam(await vaultClient.getSecret(paths.iam)) : undefined
|
|
747
882
|
};
|
|
748
883
|
if (result.defaultInstance) {
|
|
@@ -755,8 +890,10 @@ async function enterpriseStandard(appId, appKey, initConfig) {
|
|
|
755
890
|
}
|
|
756
891
|
export {
|
|
757
892
|
useUser,
|
|
893
|
+
useToken,
|
|
758
894
|
oidcCallbackSchema,
|
|
759
895
|
initiateLogin,
|
|
896
|
+
handler,
|
|
760
897
|
getUser,
|
|
761
898
|
getRequiredUser,
|
|
762
899
|
enterpriseStandard,
|
package/dist/server.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export declare function getUser(request: Request,
|
|
3
|
-
export declare function getRequiredUser(request: Request,
|
|
4
|
-
export declare function initiateLogin(
|
|
5
|
-
export declare function callback(request: Request,
|
|
1
|
+
import type { ESConfig, LoginConfig, SSOHandlerConfig } from './sso';
|
|
2
|
+
export declare function getUser(request: Request, config?: ESConfig): Promise<import("./enterprise-user").EnterpriseUser | undefined>;
|
|
3
|
+
export declare function getRequiredUser(request: Request, config?: ESConfig): Promise<import("./enterprise-user").EnterpriseUser>;
|
|
4
|
+
export declare function initiateLogin(config: LoginConfig): Promise<Response>;
|
|
5
|
+
export declare function callback(request: Request, config?: ESConfig): Promise<Response>;
|
|
6
|
+
export declare function handler(request: Request, config?: SSOHandlerConfig): Promise<Response>;
|
package/dist/sso.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { EnterpriseUser } from '.';
|
|
1
|
+
import type { EnterpriseStandard, EnterpriseUser } from '.';
|
|
2
2
|
export type SSOConfig = {
|
|
3
3
|
authority: string;
|
|
4
4
|
token_url: string;
|
|
@@ -15,32 +15,27 @@ export type SSOConfig = {
|
|
|
15
15
|
sameSite?: 'Strict' | 'Lax';
|
|
16
16
|
secure?: boolean;
|
|
17
17
|
};
|
|
18
|
-
type
|
|
19
|
-
|
|
20
|
-
sameSite: string;
|
|
21
|
-
cookiePrefix: string;
|
|
22
|
-
cookiePath: string;
|
|
18
|
+
export type ESConfig = {
|
|
19
|
+
es?: EnterpriseStandard;
|
|
23
20
|
};
|
|
24
|
-
export
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
export {};
|
|
21
|
+
export type LoginConfig = {
|
|
22
|
+
landingUrl: string;
|
|
23
|
+
errorUrl?: string;
|
|
24
|
+
} & ESConfig;
|
|
25
|
+
export type SSOHandlerConfig = {
|
|
26
|
+
loginUrl?: string;
|
|
27
|
+
userUrl?: string;
|
|
28
|
+
errorUrl?: string;
|
|
29
|
+
landingUrl?: string;
|
|
30
|
+
tokenUrl?: string;
|
|
31
|
+
refreshUrl?: string;
|
|
32
|
+
} & ESConfig;
|
|
33
|
+
export type SSO = {
|
|
34
|
+
getUser: (request: Request) => Promise<EnterpriseUser | undefined>;
|
|
35
|
+
getRequiredUser: (request: Request) => Promise<EnterpriseUser>;
|
|
36
|
+
getJwt: (request: Request) => Promise<string | undefined>;
|
|
37
|
+
initiateLogin: (config: LoginConfig) => Promise<Response>;
|
|
38
|
+
callbackHandler: (request: Request) => Promise<Response>;
|
|
39
|
+
handler: (request: Request, handlerConfig?: SSOHandlerConfig) => Promise<Response>;
|
|
40
|
+
};
|
|
41
|
+
export declare function sso(config: SSOConfig): SSO;
|
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
import type { PropsWithChildren } from 'react';
|
|
2
|
-
export declare function SignInLoading({ children }:
|
|
2
|
+
export declare function SignInLoading({ complete, children }: {
|
|
3
|
+
complete?: boolean;
|
|
4
|
+
} & PropsWithChildren): import("react/jsx-runtime").JSX.Element;
|
|
@@ -6,14 +6,25 @@ interface SSOProviderProps {
|
|
|
6
6
|
storage?: StorageType;
|
|
7
7
|
storageKey?: string;
|
|
8
8
|
userUrl?: string;
|
|
9
|
+
tokenUrl?: string;
|
|
10
|
+
refreshUrl?: string;
|
|
9
11
|
disableListener?: boolean;
|
|
10
12
|
children: ReactNode;
|
|
11
13
|
}
|
|
12
|
-
interface
|
|
14
|
+
interface SSOContext {
|
|
13
15
|
user: EnterpriseUser | null;
|
|
14
16
|
setUser: (user: EnterpriseUser | null) => void;
|
|
15
17
|
isLoading: boolean;
|
|
18
|
+
tokenUrl?: string;
|
|
19
|
+
refreshUrl?: string;
|
|
16
20
|
}
|
|
17
|
-
export declare function SSOProvider({ tenantId, storage, storageKey, userUrl, disableListener, children, }: SSOProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
18
|
-
export declare function useUser():
|
|
21
|
+
export declare function SSOProvider({ tenantId, storage, storageKey, userUrl, tokenUrl, refreshUrl, disableListener, children, }: SSOProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
22
|
+
export declare function useUser(): SSOContext;
|
|
23
|
+
interface UseTokenReturn {
|
|
24
|
+
token: string | null;
|
|
25
|
+
isLoading: boolean;
|
|
26
|
+
error: Error | null;
|
|
27
|
+
refresh: () => Promise<void>;
|
|
28
|
+
}
|
|
29
|
+
export declare function useToken(): UseTokenReturn;
|
|
19
30
|
export {};
|
package/dist/vault.d.ts
CHANGED
|
@@ -13,5 +13,5 @@ export type Vault = {
|
|
|
13
13
|
getFullSecret: <T>(path: string) => Promise<Secret<T>>;
|
|
14
14
|
getSecret: <T>(path: string) => Promise<T>;
|
|
15
15
|
};
|
|
16
|
-
export declare function vault(url: string, token: string):
|
|
16
|
+
export declare function vault(url: string, token: string): Vault;
|
|
17
17
|
export {};
|