@enterprisestandard/react 0.0.1 → 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 CHANGED
@@ -1,12 +1,12 @@
1
- import { SSOManager } from './sso';
2
- import { type Vault } from './vault';
3
1
  import { type IAM } from './iam';
2
+ import { type SSO } from './sso';
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?: SSOManager;
9
+ sso?: SSO;
10
10
  iam?: IAM;
11
11
  };
12
12
  type ESConfig = {
@@ -18,4 +18,7 @@ export declare function enterpriseStandard(appId: string, appKey?: string, initC
18
18
  export type * from './enterprise-user';
19
19
  export { oidcCallbackSchema } from './oidc-schema';
20
20
  export * from './server';
21
- export { useUser } from './useUser';
21
+ export { SignInLoading } from './ui/sign-in-loading';
22
+ export { SignedIn } from './ui/signed-in';
23
+ export { SignedOut } from './ui/signed-out';
24
+ export * from './ui/sso-provider';
package/dist/index.js CHANGED
@@ -1,3 +1,12 @@
1
+ // src/iam.ts
2
+ async function iam(config) {
3
+ return {
4
+ url: config.url,
5
+ userEndpoint: config.userEndpoint,
6
+ groupEndpoint: config.groupEndpoint
7
+ };
8
+ }
9
+
1
10
  // src/utils.ts
2
11
  var defaultInstance;
3
12
  function must(value, message = "Assertion failed. Required value is null or undefined.") {
@@ -22,35 +31,31 @@ function getES(es) {
22
31
 
23
32
  // src/sso.ts
24
33
  var jwksCache = new Map;
25
-
26
- class SSOManager {
27
- config;
28
- constructor(config) {
29
- this.config = {
30
- ...config,
31
- secure: config.secure === undefined || config.secure === true ? true : false,
32
- sameSite: config.sameSite === undefined || config.sameSite === "Strict" ? "Strict" : "Lax",
33
- cookiePrefix: config.cookiePrefix ?? `es.sso.${config.client_id}`,
34
- cookiePath: config.cookiePath ?? "/"
35
- };
36
- }
37
- async getUser(request) {
38
- 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) {
39
44
  console.error("SSO Manager not initialized");
40
45
  return;
41
46
  }
42
47
  try {
43
- const token = await this.getJwt(request);
48
+ const token = await getTokenFromCookies(request);
44
49
  if (!token)
45
50
  return;
46
- return await this.parseUser(token);
51
+ return await parseUser(token);
47
52
  } catch (error) {
48
53
  console.error("Error parsing user from cookies:", error);
49
54
  return;
50
55
  }
51
56
  }
52
- async getRequiredUser(request) {
53
- const user = await this.getUser(request);
57
+ async function getRequiredUser(request) {
58
+ const user = await getUser(request);
54
59
  if (user)
55
60
  return user;
56
61
  throw new Response("Unauthorized", {
@@ -58,38 +63,38 @@ class SSOManager {
58
63
  statusText: "Unauthorized"
59
64
  });
60
65
  }
61
- async initiateLogin(landingPage, errorPage) {
62
- if (!this.config) {
66
+ async function initiateLogin({ landingUrl, errorUrl }) {
67
+ if (!configWithDefaults) {
63
68
  console.error("SSO Manager not initialized");
64
69
  return Promise.resolve(new Response("SSO Manager not initialized", { status: 503 }));
65
70
  }
66
- const state = this.generateRandomString();
67
- const codeVerifier = this.generateRandomString(64);
68
- const url = new URL(this.config.authorization_url);
69
- url.searchParams.append("client_id", this.config.client_id);
70
- url.searchParams.append("redirect_uri", this.config.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);
71
76
  url.searchParams.append("response_type", "code");
72
- url.searchParams.append("scope", this.config.scope);
77
+ url.searchParams.append("scope", configWithDefaults.scope);
73
78
  url.searchParams.append("state", state);
74
- const codeChallenge = await this.pkceChallengeFromVerifier(codeVerifier);
79
+ const codeChallenge = await pkceChallengeFromVerifier(codeVerifier);
75
80
  url.searchParams.append("code_challenge", codeChallenge);
76
81
  url.searchParams.append("code_challenge_method", "S256");
77
82
  const val = {
78
83
  state,
79
84
  codeVerifier,
80
- landingPage,
81
- errorPage
85
+ landingUrl,
86
+ errorUrl
82
87
  };
83
88
  return new Response("Redirecting to SSO Provider", {
84
89
  status: 302,
85
90
  headers: {
86
91
  Location: url.toString(),
87
- "Set-Cookie": this.createCookie("state", val, 86400)
92
+ "Set-Cookie": createCookie("state", val, 86400)
88
93
  }
89
94
  });
90
95
  }
91
- async callbackHandler(request) {
92
- if (!this.config) {
96
+ async function callbackHandler(request) {
97
+ if (!configWithDefaults) {
93
98
  console.error("SSO Manager not initialized");
94
99
  return Promise.resolve(new Response("SSO Manager not initialized", { status: 503 }));
95
100
  }
@@ -98,39 +103,37 @@ class SSOManager {
98
103
  try {
99
104
  const codeFromUrl = must(params.get("code"), 'OIDC "code" was not passed as a search param, ensure that the SSO login completed successfully');
100
105
  const stateFromUrl = must(params.get("state"), 'OIDC "state" was not passed as a search param, ensure that the SSO login completed successfully');
101
- const cookie = this.getCookie("state", request, true);
102
- const { codeVerifier, state, landingPage } = cookie ?? {};
106
+ const cookie = getCookie("state", request, true);
107
+ const { codeVerifier, state, landingUrl } = cookie ?? {};
103
108
  must(codeVerifier, 'OIDC "codeVerifier" was not present in cookies, ensure that the SSO login was initiated correctly');
104
109
  must(state, 'OIDC "stateVerifier" was not present in cookies, ensure that the SSO login was initiated correctly');
105
- must(landingPage, 'OIDC "landingPage" was not present in cookies');
110
+ must(landingUrl, 'OIDC "landingUrl" was not present in cookies');
106
111
  if (stateFromUrl !== state) {
107
112
  throw new Error('SSO State Verifier failed, the "state" request parameter does not equal the "state" in the SSO cookie');
108
113
  }
109
- const tokenResponse = await this.exchangeCodeForToken(codeFromUrl, codeVerifier);
110
- const user = await this.parseUser(tokenResponse);
114
+ const tokenResponse = await exchangeCodeForToken(codeFromUrl, codeVerifier);
115
+ const user = await parseUser(tokenResponse);
111
116
  return new Response("Authentication successful, redirecting", {
112
117
  status: 302,
113
118
  headers: [
114
- ["Location", landingPage],
115
- ["Set-Cookie", this.clearCookie("state")],
116
- ...this.createJwtCookies(tokenResponse, user.sso.expires)
119
+ ["Location", landingUrl],
120
+ ["Set-Cookie", clearCookie("state")],
121
+ ...createJwtCookies(tokenResponse, user.sso.expires)
117
122
  ]
118
123
  });
119
124
  } catch (error) {
120
125
  console.error("Error during sign-in callback:", error);
121
126
  try {
122
- const cookie = this.getCookie("state", request, true);
123
- const { errorPage } = cookie ?? {};
124
- if (errorPage) {
125
- return new Response("Redirecting to error page", {
127
+ const cookie = getCookie("state", request, true);
128
+ const { errorUrl } = cookie ?? {};
129
+ if (errorUrl) {
130
+ return new Response("Redirecting to error url", {
126
131
  status: 302,
127
- headers: [
128
- ["Location", errorPage]
129
- ]
132
+ headers: [["Location", errorUrl]]
130
133
  });
131
134
  }
132
- } catch (err) {
133
- console.warn("Error parsing the errorPage from the OIDC cookie");
135
+ } catch (_err) {
136
+ console.warn("Error parsing the errorUrl from the OIDC cookie");
134
137
  }
135
138
  console.warn("No error page was found in the cookies. The user will be shown a default error page.");
136
139
  return new Response("An error occurred during authentication, please return to the application homepage and try again.", {
@@ -138,30 +141,32 @@ class SSOManager {
138
141
  });
139
142
  }
140
143
  }
141
- async parseUser(token) {
142
- if (!this.config)
144
+ async function parseUser(token) {
145
+ if (!configWithDefaults)
143
146
  throw new Error("SSO Manager not initialized");
144
- const idToken = await this.parseJwt(token.id_token);
147
+ const idToken = await parseJwt(token.id_token);
145
148
  const expiresIn = Number(token.refresh_expires_in ?? token.expires_in ?? 3600);
146
149
  const expires = token.expires ? new Date(token.expires) : new Date(Date.now() + expiresIn * 1000);
147
150
  return {
148
151
  userName: idToken.preferred_username || "",
149
152
  name: idToken.name || "",
150
153
  email: idToken.email || "",
151
- emails: [{
152
- value: idToken.email || "",
153
- primary: true
154
- }],
154
+ emails: [
155
+ {
156
+ value: idToken.email || "",
157
+ primary: true
158
+ }
159
+ ],
155
160
  avatarUrl: idToken.picture,
156
161
  sso: {
157
162
  profile: {
158
163
  ...idToken,
159
- iss: idToken.iss || this.config.authority,
160
- aud: idToken.aud || this.config.client_id
164
+ iss: idToken.iss || configWithDefaults.authority,
165
+ aud: idToken.aud || configWithDefaults.client_id
161
166
  },
162
167
  tenant: {
163
- id: idToken.idp || idToken.iss || this.config.authority,
164
- name: idToken.iss || this.config.authority
168
+ id: idToken.idp || idToken.iss || configWithDefaults.authority,
169
+ name: idToken.iss || configWithDefaults.authority
165
170
  },
166
171
  scope: token.scope,
167
172
  tokenType: token.token_type,
@@ -170,15 +175,15 @@ class SSOManager {
170
175
  }
171
176
  };
172
177
  }
173
- async exchangeCodeForToken(code, codeVerifier) {
174
- if (!this.config)
178
+ async function exchangeCodeForToken(code, codeVerifier) {
179
+ if (!configWithDefaults)
175
180
  throw new Error("SSO Manager not initialized");
176
- const tokenUrl = this.config.token_url;
181
+ const tokenUrl = configWithDefaults.token_url;
177
182
  const body = new URLSearchParams;
178
183
  body.append("grant_type", "authorization_code");
179
184
  body.append("code", code);
180
- body.append("redirect_uri", this.config.redirect_uri);
181
- body.append("client_id", this.config.client_id);
185
+ body.append("redirect_uri", configWithDefaults.redirect_uri);
186
+ body.append("client_id", configWithDefaults.client_id);
182
187
  body.append("code_verifier", codeVerifier);
183
188
  try {
184
189
  const response = await fetch(tokenUrl, {
@@ -200,15 +205,15 @@ class SSOManager {
200
205
  throw new Error("Error during token exchange");
201
206
  }
202
207
  }
203
- async refreshToken(refreshToken) {
204
- return this.retryWithBackoff(async () => {
205
- if (!this.config)
208
+ async function refreshToken(refreshToken2) {
209
+ return retryWithBackoff(async () => {
210
+ if (!configWithDefaults)
206
211
  throw new Error("SSO Manager not initialized");
207
- const tokenUrl = this.config.token_url;
212
+ const tokenUrl = configWithDefaults.token_url;
208
213
  const body = new URLSearchParams;
209
214
  body.append("grant_type", "refresh_token");
210
- body.append("refresh_token", refreshToken);
211
- body.append("client_id", this.config.client_id);
215
+ body.append("refresh_token", refreshToken2);
216
+ body.append("client_id", configWithDefaults.client_id);
212
217
  const response = await fetch(tokenUrl, {
213
218
  method: "POST",
214
219
  headers: {
@@ -225,12 +230,12 @@ class SSOManager {
225
230
  return data;
226
231
  });
227
232
  }
228
- async fetchJwks() {
229
- const url = this.config.jwks_uri || `${this.config.authority}/protocol/openid-connect/certs`;
233
+ async function fetchJwks() {
234
+ const url = configWithDefaults.jwks_uri || `${configWithDefaults.authority}/protocol/openid-connect/certs`;
230
235
  if (jwksCache.has(url))
231
236
  return jwksCache.get(url);
232
- return this.retryWithBackoff(async () => {
233
- if (!this.config)
237
+ return retryWithBackoff(async () => {
238
+ if (!configWithDefaults)
234
239
  throw new Error("SSO Manager not initialized");
235
240
  const response = await fetch(url);
236
241
  if (!response.ok)
@@ -240,7 +245,7 @@ class SSOManager {
240
245
  return jwks;
241
246
  });
242
247
  }
243
- async retryWithBackoff(operation, maxRetries = 3, baseDelay = 1000, maxDelay = 30000) {
248
+ async function retryWithBackoff(operation, maxRetries = 3, baseDelay = 1000, maxDelay = 30000) {
244
249
  let lastError = new Error("Placeholder Error");
245
250
  for (let attempt = 0;attempt <= maxRetries; attempt++) {
246
251
  try {
@@ -261,7 +266,7 @@ class SSOManager {
261
266
  }
262
267
  throw lastError;
263
268
  }
264
- async parseJwt(token) {
269
+ async function parseJwt(token) {
265
270
  try {
266
271
  const parts = token.split(".");
267
272
  if (parts.length !== 3)
@@ -269,7 +274,7 @@ class SSOManager {
269
274
  const header = JSON.parse(atob(parts[0].replace(/-/g, "+").replace(/_/g, "/")));
270
275
  const payload = JSON.parse(atob(parts[1].replace(/-/g, "+").replace(/_/g, "/")));
271
276
  const signature = parts[2].replace(/-/g, "+").replace(/_/g, "/");
272
- const publicKey = await this.getPublicKey(header.kid);
277
+ const publicKey = await getPublicKey(header.kid);
273
278
  const encoder = new TextEncoder;
274
279
  const data = encoder.encode(`${parts[0]}.${parts[1]}`);
275
280
  const isValid = await crypto.subtle.verify("RSASSA-PKCS1-v1_5", publicKey, Uint8Array.from(atob(signature), (c) => c.charCodeAt(0)), data);
@@ -281,12 +286,12 @@ class SSOManager {
281
286
  throw e;
282
287
  }
283
288
  }
284
- generateRandomString(length = 32) {
289
+ function generateRandomString(length = 32) {
285
290
  const array = new Uint8Array(length);
286
291
  crypto.getRandomValues(array);
287
292
  return Array.from(array, (byte) => byte.toString(16).padStart(2, "0")).join("").substring(0, length);
288
293
  }
289
- async pkceChallengeFromVerifier(verifier) {
294
+ async function pkceChallengeFromVerifier(verifier) {
290
295
  const encoder = new TextEncoder;
291
296
  const data = encoder.encode(verifier);
292
297
  const hashBuffer = await crypto.subtle.digest("SHA-256", data);
@@ -294,8 +299,8 @@ class SSOManager {
294
299
  const hashBase64 = btoa(String.fromCharCode(...hashArray)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
295
300
  return hashBase64;
296
301
  }
297
- async getPublicKey(kid) {
298
- const jwks = await this.fetchJwks();
302
+ async function getPublicKey(kid) {
303
+ const jwks = await fetchJwks();
299
304
  const key = jwks.keys.find((k) => k.kid === kid);
300
305
  if (!key)
301
306
  throw new Error("Public key not found");
@@ -306,7 +311,7 @@ class SSOManager {
306
311
  }, { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" }, false, ["verify"]);
307
312
  return publicKey;
308
313
  }
309
- createJwtCookies(token, expires) {
314
+ function createJwtCookies(token, expires) {
310
315
  const control = {
311
316
  expires_in: token.expires_in,
312
317
  refresh_expires_in: token.refresh_expires_in,
@@ -316,17 +321,17 @@ class SSOManager {
316
321
  expires: expires.toISOString()
317
322
  };
318
323
  return [
319
- ["Set-Cookie", this.createCookie("access", token.access_token, expires)],
320
- ["Set-Cookie", this.createCookie("id", token.id_token, expires)],
321
- ["Set-Cookie", this.createCookie("refresh", token.refresh_token ?? "", expires)],
322
- ["Set-Cookie", this.createCookie("control", control, expires)]
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)]
323
328
  ];
324
329
  }
325
- async getJwt(req) {
326
- const access_token = this.getCookie("access", req);
327
- const id_token = this.getCookie("id", req);
328
- const refresh_token = this.getCookie("refresh", req);
329
- const control = this.getCookie("control", req, true);
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);
330
335
  if (!access_token || !id_token || !refresh_token || !control) {
331
336
  return;
332
337
  }
@@ -337,12 +342,18 @@ class SSOManager {
337
342
  ...control
338
343
  };
339
344
  if (control.expires && refresh_token && Date.now() > new Date(control.expires).getTime()) {
340
- tokenResponse = await this.refreshToken(refresh_token);
345
+ tokenResponse = await refreshToken(refresh_token);
341
346
  }
342
347
  return tokenResponse;
343
348
  }
344
- createCookie(name, value, expires) {
345
- name = `${this.config.cookiePrefix}.${name}`;
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}`;
346
357
  if (typeof value !== "string") {
347
358
  value = btoa(JSON.stringify(value));
348
359
  }
@@ -357,16 +368,16 @@ class SSOManager {
357
368
  if (value.length > 4000) {
358
369
  throw new Error(`Error setting cookie: ${name}. Cookie length is: ${value.length}`);
359
370
  }
360
- return `${name}=${value}; ${exp}; Path=${this.config.cookiePath}; HttpOnly;${this.config.secure ? " Secure;" : ""} SameSite=${this.config.sameSite};`;
371
+ return `${name}=${value}; ${exp}; Path=${configWithDefaults.cookiePath}; HttpOnly;${configWithDefaults.secure ? " Secure;" : ""} SameSite=${configWithDefaults.sameSite};`;
361
372
  }
362
- clearCookie(name) {
363
- return `${this.config.cookiePrefix}.${name}=; Max-Age=0; Path=${this.config.cookiePath}; HttpOnly;${this.config.secure ? " Secure;" : ""} SameSite=${this.config.sameSite};`;
373
+ function clearCookie(name) {
374
+ return `${configWithDefaults.cookiePrefix}.${name}=; Max-Age=0; Path=${configWithDefaults.cookiePath}; HttpOnly;${configWithDefaults.secure ? " Secure;" : ""} SameSite=${configWithDefaults.sameSite};`;
364
375
  }
365
- getCookie(name, req, parse = false) {
376
+ function getCookie(name, req, parse = false) {
366
377
  const header = req.headers.get("cookie");
367
378
  if (!header)
368
379
  return null;
369
- const cookie = header.split(";").find((row) => row.trim().startsWith(`${this.config.cookiePrefix}.${name}=`));
380
+ const cookie = header.split(";").find((row) => row.trim().startsWith(`${configWithDefaults.cookiePrefix}.${name}=`));
370
381
  if (!cookie)
371
382
  return null;
372
383
  const val = cookie.split("=")[1].trim();
@@ -375,17 +386,73 @@ class SSOManager {
375
386
  const str = atob(val);
376
387
  return JSON.parse(str);
377
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
+ };
378
441
  }
379
442
 
380
443
  // src/vault.ts
381
- async function vault(url, token) {
444
+ function vault(url, token) {
382
445
  async function getFullSecret(path) {
383
446
  const resp = await fetch(`${url}/${path}`, { headers: { "X-Vault-Token": token } });
384
447
  if (resp.status !== 200) {
385
448
  throw new Error(`Vault returned invalid status, ${resp.status}: '${resp.statusText}' from URL: ${url}`);
386
449
  }
387
- const secret = await resp.json();
388
- return secret.data;
450
+ try {
451
+ const secret = await resp.json();
452
+ return secret.data;
453
+ } catch (cause) {
454
+ throw new Error("Error retrieving secret", { cause });
455
+ }
389
456
  }
390
457
  return {
391
458
  url,
@@ -396,15 +463,6 @@ async function vault(url, token) {
396
463
  };
397
464
  }
398
465
 
399
- // src/iam.ts
400
- async function iam(config) {
401
- return {
402
- url: config.url,
403
- userEndpoint: config.userEndpoint,
404
- groupEndpoint: config.groupEndpoint
405
- };
406
- }
407
-
408
466
  // src/oidc-schema.ts
409
467
  function oidcCallbackSchema(vendor) {
410
468
  return {
@@ -508,8 +566,8 @@ function oidcCallbackSchema(vendor) {
508
566
  };
509
567
  }
510
568
  // src/server.ts
511
- function getSSO(es) {
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,54 +581,275 @@ function unavailable() {
523
581
  headers: { "Content-Type": "application/json" }
524
582
  });
525
583
  }
526
- async function getUser(request, es) {
527
- return getSSO(es)?.getUser(request);
584
+ async function getUser(request, config) {
585
+ return getSSO(config)?.getUser(request);
586
+ }
587
+ async function getRequiredUser(request, config) {
588
+ const sso2 = getSSO(config);
589
+ if (!sso2)
590
+ throw unavailable();
591
+ return sso2.getRequiredUser(request);
528
592
  }
529
- async function getRequiredUser(request, es) {
530
- const sso = getSSO(es);
531
- if (!sso)
593
+ async function initiateLogin(config) {
594
+ const sso2 = getSSO(config);
595
+ if (!sso2)
532
596
  throw unavailable();
533
- return sso.getRequiredUser(request);
597
+ return sso2.initiateLogin(config);
534
598
  }
535
- async function initiateLogin(landingPage, errorPage, es) {
536
- const sso = getSSO(es);
537
- if (!sso)
599
+ async function callback(request, config) {
600
+ const sso2 = getSSO(config);
601
+ if (!sso2)
538
602
  throw unavailable();
539
- return sso.initiateLogin(landingPage, errorPage);
603
+ return sso2.callbackHandler(request);
540
604
  }
541
- async function callback(request, es) {
542
- const sso = getSSO(es);
543
- if (!sso)
605
+ async function handler(request, config) {
606
+ const sso2 = getSSO(config);
607
+ if (!sso2)
544
608
  throw unavailable();
545
- return sso.callbackHandler(request);
609
+ return sso2.handler(request, config);
610
+ }
611
+ // src/ui/sign-in-loading.tsx
612
+ import { jsxDEV, Fragment } from "react/jsx-dev-runtime";
613
+ function SignInLoading({ complete = false, children }) {
614
+ const { isLoading } = useUser();
615
+ if (isLoading && !complete)
616
+ return /* @__PURE__ */ jsxDEV(Fragment, {
617
+ children
618
+ }, undefined, false, undefined, this);
619
+ return /* @__PURE__ */ jsxDEV(Fragment, {}, undefined, false, undefined, this);
620
+ }
621
+ // src/ui/signed-in.tsx
622
+ import { jsxDEV as jsxDEV2, Fragment as Fragment2 } from "react/jsx-dev-runtime";
623
+ function SignedIn({ children }) {
624
+ const { user } = useUser();
625
+ if (user)
626
+ return /* @__PURE__ */ jsxDEV2(Fragment2, {
627
+ children
628
+ }, undefined, false, undefined, this);
629
+ return /* @__PURE__ */ jsxDEV2(Fragment2, {}, undefined, false, undefined, this);
630
+ }
631
+ // src/ui/signed-out.tsx
632
+ import { jsxDEV as jsxDEV3, Fragment as Fragment3 } from "react/jsx-dev-runtime";
633
+ function SignedOut({ children }) {
634
+ const { user } = useUser();
635
+ if (user)
636
+ return /* @__PURE__ */ jsxDEV3(Fragment3, {}, undefined, false, undefined, this);
637
+ return /* @__PURE__ */ jsxDEV3(Fragment3, {
638
+ children
639
+ }, undefined, false, undefined, this);
546
640
  }
547
- // src/useUser.ts
548
- import { useState, useEffect } from "react";
549
- function useUser(apiUrl) {
550
- const [isLoading, setIsLoading] = useState(true);
551
- const [user, setUser] = useState(undefined);
552
- const [status, setStatus] = useState(undefined);
553
- const resolvedApiUrl = apiUrl || "/api/user";
641
+ // src/ui/sso-provider.tsx
642
+ import { createContext, useCallback, useContext, useEffect, useState } from "react";
643
+ import { jsxDEV as jsxDEV4 } from "react/jsx-dev-runtime";
644
+ var CTX = createContext(undefined);
645
+ var generateStorageKey = (tenantId) => {
646
+ return `es-sso-user-${tenantId.replace(/[^a-zA-Z0-9]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "")}`;
647
+ };
648
+ function SSOProvider({
649
+ tenantId,
650
+ storage = "memory",
651
+ storageKey,
652
+ userUrl,
653
+ tokenUrl,
654
+ refreshUrl,
655
+ disableListener = false,
656
+ children
657
+ }) {
658
+ const [user, setUserState] = useState(null);
659
+ const [isLoading, setIsLoading] = useState(!!userUrl);
660
+ const actualStorageKey = storageKey || (tenantId ? generateStorageKey(tenantId) : "es-sso-user");
661
+ const isValidUser = useCallback((user2) => {
662
+ if (!user2 || !tenantId)
663
+ return true;
664
+ return user2.sso?.tenant?.id === tenantId;
665
+ }, [tenantId]);
666
+ const loadUserFromStorage = useCallback(() => {
667
+ if (storage === "memory" || typeof window === "undefined")
668
+ return null;
669
+ try {
670
+ const storageObject = storage === "local" ? localStorage : sessionStorage;
671
+ const userData = storageObject.getItem(actualStorageKey);
672
+ if (!userData)
673
+ return null;
674
+ const parsedUser = JSON.parse(userData);
675
+ if (parsedUser.sso?.expires) {
676
+ parsedUser.sso.expires = new Date(parsedUser.sso.expires);
677
+ }
678
+ return isValidUser(parsedUser) ? parsedUser : null;
679
+ } catch (error) {
680
+ console.error("Error loading user from storage:", error);
681
+ return null;
682
+ }
683
+ }, [storage, actualStorageKey, isValidUser]);
684
+ const saveUserToStorage = useCallback((user2) => {
685
+ if (storage === "memory" || typeof window === "undefined")
686
+ return;
687
+ try {
688
+ const storageObject = storage === "local" ? localStorage : sessionStorage;
689
+ if (user2 === null) {
690
+ storageObject.removeItem(actualStorageKey);
691
+ } else {
692
+ storageObject.setItem(actualStorageKey, JSON.stringify(user2));
693
+ }
694
+ } catch (error) {
695
+ console.error("Error saving user to storage:", error);
696
+ }
697
+ }, [storage, actualStorageKey]);
698
+ const setUser = useCallback((newUser) => {
699
+ if (newUser && !isValidUser(newUser))
700
+ return;
701
+ setUserState(newUser);
702
+ saveUserToStorage(newUser);
703
+ }, [isValidUser, saveUserToStorage]);
704
+ const fetchUserFromUrl = useCallback(async () => {
705
+ if (!userUrl)
706
+ return;
707
+ setIsLoading(true);
708
+ try {
709
+ const response = await fetch(userUrl);
710
+ if (!response.ok) {
711
+ throw new Error(`Failed to fetch user: ${response.status} ${response.statusText}`);
712
+ }
713
+ const userData = await response.json();
714
+ if (userData.sso?.expires && typeof userData.sso.expires === "string") {
715
+ userData.sso.expires = new Date(userData.sso.expires);
716
+ }
717
+ if (isValidUser(userData)) {
718
+ setUserState(userData);
719
+ saveUserToStorage(userData);
720
+ }
721
+ } catch (error) {
722
+ console.error("Error fetching user from URL:", error);
723
+ } finally {
724
+ setIsLoading(false);
725
+ }
726
+ }, [userUrl, isValidUser, saveUserToStorage]);
554
727
  useEffect(() => {
555
- const fetchUser = async () => {
556
- try {
557
- const response = await fetch(resolvedApiUrl);
558
- setStatus(response.status);
559
- if (response.ok) {
560
- const userData = await response.json();
561
- setUser(userData);
562
- } else {
563
- setUser(undefined);
728
+ const storedUser = loadUserFromStorage();
729
+ if (storedUser) {
730
+ setUserState(storedUser);
731
+ setIsLoading(false);
732
+ } else if (userUrl) {
733
+ fetchUserFromUrl();
734
+ } else {
735
+ setIsLoading(false);
736
+ }
737
+ }, [loadUserFromStorage, userUrl, fetchUserFromUrl]);
738
+ useEffect(() => {
739
+ if (disableListener || storage === "memory")
740
+ return;
741
+ const handleStorageChange = (event) => {
742
+ if (event.key !== actualStorageKey)
743
+ return;
744
+ if (event.newValue === null) {
745
+ setUserState(null);
746
+ } else {
747
+ try {
748
+ const parsedUser = JSON.parse(event.newValue);
749
+ if (parsedUser.sso?.expires) {
750
+ parsedUser.sso.expires = new Date(parsedUser.sso.expires);
751
+ }
752
+ if (isValidUser(parsedUser)) {
753
+ setUserState(parsedUser);
754
+ }
755
+ } catch (error) {
756
+ console.error("Error parsing user from storage event:", error);
564
757
  }
565
- } catch (error) {
566
- console.error("Error fetching user:", error);
567
- setUser(undefined);
568
758
  }
759
+ };
760
+ window.addEventListener("storage", handleStorageChange);
761
+ return () => {
762
+ window.removeEventListener("storage", handleStorageChange);
763
+ };
764
+ }, [disableListener, storage, actualStorageKey, isValidUser]);
765
+ const contextValue = {
766
+ user,
767
+ setUser,
768
+ isLoading,
769
+ tokenUrl,
770
+ refreshUrl
771
+ };
772
+ return /* @__PURE__ */ jsxDEV4(CTX.Provider, {
773
+ value: contextValue,
774
+ children
775
+ }, undefined, false, undefined, this);
776
+ }
777
+ function useUser() {
778
+ const context = useContext(CTX);
779
+ if (context === undefined) {
780
+ throw new Error("useUser must be used within a SSOProvider");
781
+ }
782
+ return context;
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 {
569
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
+ }
570
842
  };
571
- fetchUser();
572
- }, [resolvedApiUrl]);
573
- return { isLoading, user, status };
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
+ };
574
853
  }
575
854
 
576
855
  // src/index.ts
@@ -580,9 +859,9 @@ async function enterpriseStandard(appId, appKey, initConfig) {
580
859
  let paths;
581
860
  const ioniteUrl = initConfig?.ioniteUrl ?? "https://ionite.com";
582
861
  if (appId === "IONITE_PUBLIC_DEMO") {
583
- vaultUrl = "https://vault.ionite.dev/v1/secret/data";
584
- vaultToken = "hvs.qIM9R0hmdnVCQmdvWdDGPSZ4";
585
- paths = { sso: "ionite/DEV01K2JRP1DWXFCZN9FRDT37J1Y0" };
862
+ vaultUrl = "https://vault-ionite.ionite.dev/v1/secret/data";
863
+ vaultToken = "hvs.NuiBSLuFk5Ju4JDOUwTOlSlP";
864
+ paths = { sso: "ionite/IONITE_PUBLIC_DEMO" };
586
865
  } else if (appKey) {
587
866
  if (!vaultUrl || !vaultToken) {
588
867
  throw new Error("TODO something is wrong with the ionite config, handle this error");
@@ -598,7 +877,7 @@ async function enterpriseStandard(appId, appKey, initConfig) {
598
877
  ioniteUrl,
599
878
  defaultInstance: initConfig?.defaultInstance || initConfig?.defaultInstance !== false && !defaultInstance2,
600
879
  vault: vaultClient,
601
- sso: paths.sso ? new SSOManager(await vaultClient.getSecret(paths.sso)) : undefined,
880
+ sso: paths.sso ? sso(await vaultClient.getSecret(paths.sso)) : undefined,
602
881
  iam: paths.iam ? await iam(await vaultClient.getSecret(paths.iam)) : undefined
603
882
  };
604
883
  if (result.defaultInstance) {
@@ -611,10 +890,16 @@ async function enterpriseStandard(appId, appKey, initConfig) {
611
890
  }
612
891
  export {
613
892
  useUser,
893
+ useToken,
614
894
  oidcCallbackSchema,
615
895
  initiateLogin,
896
+ handler,
616
897
  getUser,
617
898
  getRequiredUser,
618
899
  enterpriseStandard,
619
- callback
900
+ callback,
901
+ SignedOut,
902
+ SignedIn,
903
+ SignInLoading,
904
+ SSOProvider
620
905
  };
package/dist/server.d.ts CHANGED
@@ -1,5 +1,6 @@
1
- import { EnterpriseStandard, EnterpriseUser } from ".";
2
- export declare function getUser(request: Request, es?: EnterpriseStandard): Promise<EnterpriseUser | undefined>;
3
- export declare function getRequiredUser(request: Request, es?: EnterpriseStandard): Promise<EnterpriseUser>;
4
- export declare function initiateLogin(landingPage: string, errorPage?: string, es?: EnterpriseStandard): Promise<Response>;
5
- export declare function callback(request: Request, es?: EnterpriseStandard): Promise<Response>;
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 SSOConfigWithDefaults = SSOConfig & {
19
- secure: boolean;
20
- sameSite: string;
21
- cookiePrefix: string;
22
- cookiePath: string;
18
+ export type ESConfig = {
19
+ es?: EnterpriseStandard;
23
20
  };
24
- export declare class SSOManager {
25
- config: SSOConfigWithDefaults;
26
- constructor(config: SSOConfig);
27
- getUser(request: Request): Promise<EnterpriseUser | undefined>;
28
- getRequiredUser(request: Request): Promise<EnterpriseUser>;
29
- initiateLogin(landingPage: string, errorPage?: string): Promise<Response>;
30
- callbackHandler(request: Request): Promise<Response>;
31
- private parseUser;
32
- private exchangeCodeForToken;
33
- private refreshToken;
34
- private fetchJwks;
35
- private retryWithBackoff;
36
- private parseJwt;
37
- private generateRandomString;
38
- private pkceChallengeFromVerifier;
39
- private getPublicKey;
40
- private createJwtCookies;
41
- private getJwt;
42
- private createCookie;
43
- private clearCookie;
44
- private getCookie;
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;
@@ -0,0 +1,4 @@
1
+ import type { PropsWithChildren } from 'react';
2
+ export declare function SignInLoading({ complete, children }: {
3
+ complete?: boolean;
4
+ } & PropsWithChildren): import("react/jsx-runtime").JSX.Element;
@@ -1,6 +1,2 @@
1
- import { PropsWithChildren } from "react";
2
- import { EnterpriseStandard } from "..";
3
- export type Props = {
4
- es?: EnterpriseStandard;
5
- } & PropsWithChildren;
6
- export declare function SignedIn({ es, children }: Props): import("react/jsx-runtime").JSX.Element;
1
+ import type { PropsWithChildren } from 'react';
2
+ export declare function SignedIn({ children }: PropsWithChildren): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,2 @@
1
+ import type { PropsWithChildren } from 'react';
2
+ export declare function SignedOut({ children }: PropsWithChildren): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,30 @@
1
+ import { type ReactNode } from 'react';
2
+ import type { EnterpriseUser } from '../enterprise-user';
3
+ type StorageType = 'local' | 'session' | 'memory';
4
+ interface SSOProviderProps {
5
+ tenantId?: string;
6
+ storage?: StorageType;
7
+ storageKey?: string;
8
+ userUrl?: string;
9
+ tokenUrl?: string;
10
+ refreshUrl?: string;
11
+ disableListener?: boolean;
12
+ children: ReactNode;
13
+ }
14
+ interface SSOContext {
15
+ user: EnterpriseUser | null;
16
+ setUser: (user: EnterpriseUser | null) => void;
17
+ isLoading: boolean;
18
+ tokenUrl?: string;
19
+ refreshUrl?: string;
20
+ }
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;
30
+ export {};
package/dist/utils.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type EnterpriseStandard } from ".";
1
+ import type { EnterpriseStandard } from '.';
2
2
  export declare function must<T>(value: T | undefined | null, message?: string): T;
3
3
  export declare function setDefaultInstance(es: EnterpriseStandard): void;
4
4
  export declare function getDefaultInstance(): EnterpriseStandard | undefined;
package/dist/vault.d.ts CHANGED
@@ -4,7 +4,6 @@ type Secret<T> = {
4
4
  };
5
5
  type MetaData = {
6
6
  created_time: string;
7
- custom_metadata: any;
8
7
  deletion_time: string;
9
8
  destroyed: boolean;
10
9
  version: number;
@@ -14,5 +13,5 @@ export type Vault = {
14
13
  getFullSecret: <T>(path: string) => Promise<Secret<T>>;
15
14
  getSecret: <T>(path: string) => Promise<T>;
16
15
  };
17
- export declare function vault(url: string, token: string): Promise<Vault>;
16
+ export declare function vault(url: string, token: string): Vault;
18
17
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@enterprisestandard/react",
3
- "version": "0.0.1",
3
+ "version": "0.0.3-beta.1",
4
4
  "description": "Enterprise Standard React Components",
5
5
  "private": false,
6
6
  "main": "dist/index.js",
package/dist/useUser.d.ts DELETED
@@ -1,8 +0,0 @@
1
- import { EnterpriseUser } from '.';
2
- type UseUser = {
3
- isLoading: boolean;
4
- user?: EnterpriseUser;
5
- status?: number;
6
- };
7
- export declare function useUser(apiUrl?: string): UseUser;
8
- export {};