@enterprisestandard/react 0.0.2 → 0.0.3-beta.2

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
1
  import { type IAM } from './iam';
2
- import { SSOManager } from './sso';
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?: SSOManager;
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
- class SSOManager {
36
- config;
37
- constructor(config) {
38
- this.config = {
39
- ...config,
40
- secure: !!(config.secure === undefined || config.secure === true),
41
- sameSite: config.sameSite === undefined || config.sameSite === "Strict" ? "Strict" : "Lax",
42
- cookiePrefix: config.cookiePrefix ?? `es.sso.${config.client_id}`,
43
- cookiePath: config.cookiePath ?? "/"
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 this.getJwt(request);
48
+ const token = await getTokenFromCookies(request);
53
49
  if (!token)
54
50
  return;
55
- return await this.parseUser(token);
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 this.getUser(request);
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(landingPage, errorPage) {
71
- if (!this.config) {
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 = this.generateRandomString();
76
- const codeVerifier = this.generateRandomString(64);
77
- const url = new URL(this.config.authorization_url);
78
- url.searchParams.append("client_id", this.config.client_id);
79
- 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);
80
76
  url.searchParams.append("response_type", "code");
81
- url.searchParams.append("scope", this.config.scope);
77
+ url.searchParams.append("scope", configWithDefaults.scope);
82
78
  url.searchParams.append("state", state);
83
- const codeChallenge = await this.pkceChallengeFromVerifier(codeVerifier);
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
- landingPage,
90
- errorPage
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": this.createCookie("state", val, 86400)
92
+ "Set-Cookie": createCookie("state", val, 86400)
97
93
  }
98
94
  });
99
95
  }
100
- async callbackHandler(request) {
101
- if (!this.config) {
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 = this.getCookie("state", request, true);
111
- const { codeVerifier, state, landingPage } = cookie ?? {};
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(landingPage, 'OIDC "landingPage" was not present in cookies');
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 this.exchangeCodeForToken(codeFromUrl, codeVerifier);
119
- const user = await this.parseUser(tokenResponse);
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", landingPage],
124
- ["Set-Cookie", this.clearCookie("state")],
125
- ...this.createJwtCookies(tokenResponse, user.sso.expires)
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 = this.getCookie("state", request, true);
132
- const { errorPage } = cookie ?? {};
133
- if (errorPage) {
134
- 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", {
135
131
  status: 302,
136
- headers: [["Location", errorPage]]
132
+ headers: [["Location", errorUrl]]
137
133
  });
138
134
  }
139
135
  } catch (_err) {
140
- console.warn("Error parsing the errorPage from the OIDC cookie");
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 (!this.config)
144
+ async function parseUser(token) {
145
+ if (!configWithDefaults)
150
146
  throw new Error("SSO Manager not initialized");
151
- const idToken = await this.parseJwt(token.id_token);
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 || this.config.authority,
169
- aud: idToken.aud || this.config.client_id
164
+ iss: idToken.iss || configWithDefaults.authority,
165
+ aud: idToken.aud || configWithDefaults.client_id
170
166
  },
171
167
  tenant: {
172
- id: idToken.idp || idToken.iss || this.config.authority,
173
- name: idToken.iss || this.config.authority
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 (!this.config)
178
+ async function exchangeCodeForToken(code, codeVerifier) {
179
+ if (!configWithDefaults)
184
180
  throw new Error("SSO Manager not initialized");
185
- const tokenUrl = this.config.token_url;
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", this.config.redirect_uri);
190
- body.append("client_id", this.config.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(refreshToken) {
213
- return this.retryWithBackoff(async () => {
214
- if (!this.config)
208
+ async function refreshToken(refreshToken2) {
209
+ return retryWithBackoff(async () => {
210
+ if (!configWithDefaults)
215
211
  throw new Error("SSO Manager not initialized");
216
- const tokenUrl = this.config.token_url;
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", refreshToken);
220
- body.append("client_id", this.config.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,13 @@ class SSOManager {
234
230
  return data;
235
231
  });
236
232
  }
237
- async fetchJwks() {
238
- const url = this.config.jwks_uri || `${this.config.authority}/protocol/openid-connect/certs`;
239
- if (jwksCache.has(url))
240
- return jwksCache.get(url);
241
- return this.retryWithBackoff(async () => {
242
- if (!this.config)
233
+ async function fetchJwks() {
234
+ const url = configWithDefaults.jwks_uri || `${configWithDefaults.authority}/protocol/openid-connect/certs`;
235
+ const cached = jwksCache.get(url);
236
+ if (cached)
237
+ return cached;
238
+ return retryWithBackoff(async () => {
239
+ if (!configWithDefaults)
243
240
  throw new Error("SSO Manager not initialized");
244
241
  const response = await fetch(url);
245
242
  if (!response.ok)
@@ -249,7 +246,7 @@ class SSOManager {
249
246
  return jwks;
250
247
  });
251
248
  }
252
- async retryWithBackoff(operation, maxRetries = 3, baseDelay = 1000, maxDelay = 30000) {
249
+ async function retryWithBackoff(operation, maxRetries = 3, baseDelay = 1000, maxDelay = 30000) {
253
250
  let lastError = new Error("Placeholder Error");
254
251
  for (let attempt = 0;attempt <= maxRetries; attempt++) {
255
252
  try {
@@ -270,7 +267,7 @@ class SSOManager {
270
267
  }
271
268
  throw lastError;
272
269
  }
273
- async parseJwt(token) {
270
+ async function parseJwt(token) {
274
271
  try {
275
272
  const parts = token.split(".");
276
273
  if (parts.length !== 3)
@@ -278,7 +275,7 @@ class SSOManager {
278
275
  const header = JSON.parse(atob(parts[0].replace(/-/g, "+").replace(/_/g, "/")));
279
276
  const payload = JSON.parse(atob(parts[1].replace(/-/g, "+").replace(/_/g, "/")));
280
277
  const signature = parts[2].replace(/-/g, "+").replace(/_/g, "/");
281
- const publicKey = await this.getPublicKey(header.kid);
278
+ const publicKey = await getPublicKey(header.kid);
282
279
  const encoder = new TextEncoder;
283
280
  const data = encoder.encode(`${parts[0]}.${parts[1]}`);
284
281
  const isValid = await crypto.subtle.verify("RSASSA-PKCS1-v1_5", publicKey, Uint8Array.from(atob(signature), (c) => c.charCodeAt(0)), data);
@@ -290,12 +287,12 @@ class SSOManager {
290
287
  throw e;
291
288
  }
292
289
  }
293
- generateRandomString(length = 32) {
290
+ function generateRandomString(length = 32) {
294
291
  const array = new Uint8Array(length);
295
292
  crypto.getRandomValues(array);
296
293
  return Array.from(array, (byte) => byte.toString(16).padStart(2, "0")).join("").substring(0, length);
297
294
  }
298
- async pkceChallengeFromVerifier(verifier) {
295
+ async function pkceChallengeFromVerifier(verifier) {
299
296
  const encoder = new TextEncoder;
300
297
  const data = encoder.encode(verifier);
301
298
  const hashBuffer = await crypto.subtle.digest("SHA-256", data);
@@ -303,8 +300,8 @@ class SSOManager {
303
300
  const hashBase64 = btoa(String.fromCharCode(...hashArray)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
304
301
  return hashBase64;
305
302
  }
306
- async getPublicKey(kid) {
307
- const jwks = await this.fetchJwks();
303
+ async function getPublicKey(kid) {
304
+ const jwks = await fetchJwks();
308
305
  const key = jwks.keys.find((k) => k.kid === kid);
309
306
  if (!key)
310
307
  throw new Error("Public key not found");
@@ -315,7 +312,7 @@ class SSOManager {
315
312
  }, { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" }, false, ["verify"]);
316
313
  return publicKey;
317
314
  }
318
- createJwtCookies(token, expires) {
315
+ function createJwtCookies(token, expires) {
319
316
  const control = {
320
317
  expires_in: token.expires_in,
321
318
  refresh_expires_in: token.refresh_expires_in,
@@ -325,17 +322,17 @@ class SSOManager {
325
322
  expires: expires.toISOString()
326
323
  };
327
324
  return [
328
- ["Set-Cookie", this.createCookie("access", token.access_token, expires)],
329
- ["Set-Cookie", this.createCookie("id", token.id_token, expires)],
330
- ["Set-Cookie", this.createCookie("refresh", token.refresh_token ?? "", expires)],
331
- ["Set-Cookie", this.createCookie("control", control, expires)]
325
+ ["Set-Cookie", createCookie("access", token.access_token, expires)],
326
+ ["Set-Cookie", createCookie("id", token.id_token, expires)],
327
+ ["Set-Cookie", createCookie("refresh", token.refresh_token ?? "", expires)],
328
+ ["Set-Cookie", createCookie("control", control, expires)]
332
329
  ];
333
330
  }
334
- async getJwt(req) {
335
- const access_token = this.getCookie("access", req);
336
- const id_token = this.getCookie("id", req);
337
- const refresh_token = this.getCookie("refresh", req);
338
- const control = this.getCookie("control", req, true);
331
+ async function getTokenFromCookies(req) {
332
+ const access_token = getCookie("access", req);
333
+ const id_token = getCookie("id", req);
334
+ const refresh_token = getCookie("refresh", req);
335
+ const control = getCookie("control", req, true);
339
336
  if (!access_token || !id_token || !refresh_token || !control) {
340
337
  return;
341
338
  }
@@ -346,12 +343,18 @@ class SSOManager {
346
343
  ...control
347
344
  };
348
345
  if (control.expires && refresh_token && Date.now() > new Date(control.expires).getTime()) {
349
- tokenResponse = await this.refreshToken(refresh_token);
346
+ tokenResponse = await refreshToken(refresh_token);
350
347
  }
351
348
  return tokenResponse;
352
349
  }
353
- createCookie(name, value, expires) {
354
- name = `${this.config.cookiePrefix}.${name}`;
350
+ async function getJwt(request) {
351
+ const tokenResponse = await getTokenFromCookies(request);
352
+ if (!tokenResponse)
353
+ return;
354
+ return tokenResponse.access_token;
355
+ }
356
+ function createCookie(name, value, expires) {
357
+ name = `${configWithDefaults.cookiePrefix}.${name}`;
355
358
  if (typeof value !== "string") {
356
359
  value = btoa(JSON.stringify(value));
357
360
  }
@@ -366,16 +369,16 @@ class SSOManager {
366
369
  if (value.length > 4000) {
367
370
  throw new Error(`Error setting cookie: ${name}. Cookie length is: ${value.length}`);
368
371
  }
369
- return `${name}=${value}; ${exp}; Path=${this.config.cookiePath}; HttpOnly;${this.config.secure ? " Secure;" : ""} SameSite=${this.config.sameSite};`;
372
+ return `${name}=${value}; ${exp}; Path=${configWithDefaults.cookiePath}; HttpOnly;${configWithDefaults.secure ? " Secure;" : ""} SameSite=${configWithDefaults.sameSite};`;
370
373
  }
371
- clearCookie(name) {
372
- return `${this.config.cookiePrefix}.${name}=; Max-Age=0; Path=${this.config.cookiePath}; HttpOnly;${this.config.secure ? " Secure;" : ""} SameSite=${this.config.sameSite};`;
374
+ function clearCookie(name) {
375
+ return `${configWithDefaults.cookiePrefix}.${name}=; Max-Age=0; Path=${configWithDefaults.cookiePath}; HttpOnly;${configWithDefaults.secure ? " Secure;" : ""} SameSite=${configWithDefaults.sameSite};`;
373
376
  }
374
- getCookie(name, req, parse = false) {
377
+ function getCookie(name, req, parse = false) {
375
378
  const header = req.headers.get("cookie");
376
379
  if (!header)
377
380
  return null;
378
- const cookie = header.split(";").find((row) => row.trim().startsWith(`${this.config.cookiePrefix}.${name}=`));
381
+ const cookie = header.split(";").find((row) => row.trim().startsWith(`${configWithDefaults.cookiePrefix}.${name}=`));
379
382
  if (!cookie)
380
383
  return null;
381
384
  const val = cookie.split("=")[1].trim();
@@ -384,17 +387,73 @@ class SSOManager {
384
387
  const str = atob(val);
385
388
  return JSON.parse(str);
386
389
  }
390
+ async function handler(request, handlerConfig) {
391
+ let { loginUrl, userUrl, errorUrl, landingUrl, tokenUrl, refreshUrl } = handlerConfig ?? {};
392
+ if (!loginUrl)
393
+ loginUrl = "*";
394
+ const path = new URL(request.url).pathname;
395
+ if (new URL(config.redirect_uri).pathname === path) {
396
+ return callbackHandler(request);
397
+ }
398
+ if (userUrl === path) {
399
+ const user = await getUser(request);
400
+ if (!user) {
401
+ return new Response("User not logged in", { status: 401 });
402
+ }
403
+ return new Response(JSON.stringify(user), {
404
+ headers: [["Content-Type", "application/json"]]
405
+ });
406
+ }
407
+ if (tokenUrl === path) {
408
+ const tokenResponse = await getTokenFromCookies(request);
409
+ if (!tokenResponse) {
410
+ return new Response("User not logged in", { status: 401 });
411
+ }
412
+ return new Response(JSON.stringify({
413
+ token: tokenResponse.access_token,
414
+ expires: tokenResponse.expires
415
+ }), {
416
+ headers: [["Content-Type", "application/json"]]
417
+ });
418
+ }
419
+ if (refreshUrl === path) {
420
+ const tokenResponse = await getTokenFromCookies(request);
421
+ if (!tokenResponse) {
422
+ return new Response("User not logged in", { status: 401 });
423
+ }
424
+ return new Response("Refresh Complete", { status: 200 });
425
+ }
426
+ if (loginUrl === "*" || loginUrl === path) {
427
+ return initiateLogin({
428
+ landingUrl: landingUrl || "/",
429
+ errorUrl
430
+ });
431
+ }
432
+ return new Response("Not Found", { status: 404 });
433
+ }
434
+ return {
435
+ getUser,
436
+ getRequiredUser,
437
+ getJwt,
438
+ initiateLogin,
439
+ callbackHandler,
440
+ handler
441
+ };
387
442
  }
388
443
 
389
444
  // src/vault.ts
390
- async function vault(url, token) {
445
+ function vault(url, token) {
391
446
  async function getFullSecret(path) {
392
447
  const resp = await fetch(`${url}/${path}`, { headers: { "X-Vault-Token": token } });
393
448
  if (resp.status !== 200) {
394
449
  throw new Error(`Vault returned invalid status, ${resp.status}: '${resp.statusText}' from URL: ${url}`);
395
450
  }
396
- const secret = await resp.json();
397
- return secret.data;
451
+ try {
452
+ const secret = await resp.json();
453
+ return secret.data;
454
+ } catch (cause) {
455
+ throw new Error("Error retrieving secret", { cause });
456
+ }
398
457
  }
399
458
  return {
400
459
  url,
@@ -508,8 +567,8 @@ function oidcCallbackSchema(vendor) {
508
567
  };
509
568
  }
510
569
  // src/server.ts
511
- function getSSO(es) {
512
- es = getES(es);
570
+ function getSSO(config) {
571
+ const es = getES(config?.es);
513
572
  if (!es.sso) {
514
573
  console.error("TODO tell them how to connect SSO");
515
574
  return;
@@ -523,36 +582,42 @@ function unavailable() {
523
582
  headers: { "Content-Type": "application/json" }
524
583
  });
525
584
  }
526
- async function getUser(request, es) {
527
- return getSSO(es)?.getUser(request);
585
+ async function getUser(request, config) {
586
+ return getSSO(config)?.getUser(request);
587
+ }
588
+ async function getRequiredUser(request, config) {
589
+ const sso2 = getSSO(config);
590
+ if (!sso2)
591
+ throw unavailable();
592
+ return sso2.getRequiredUser(request);
528
593
  }
529
- async function getRequiredUser(request, es) {
530
- const sso = getSSO(es);
531
- if (!sso)
594
+ async function initiateLogin(config) {
595
+ const sso2 = getSSO(config);
596
+ if (!sso2)
532
597
  throw unavailable();
533
- return sso.getRequiredUser(request);
598
+ return sso2.initiateLogin(config);
534
599
  }
535
- async function initiateLogin(landingPage, errorPage, es) {
536
- const sso = getSSO(es);
537
- if (!sso)
600
+ async function callback(request, config) {
601
+ const sso2 = getSSO(config);
602
+ if (!sso2)
538
603
  throw unavailable();
539
- return sso.initiateLogin(landingPage, errorPage);
604
+ return sso2.callbackHandler(request);
540
605
  }
541
- async function callback(request, es) {
542
- const sso = getSSO(es);
543
- if (!sso)
606
+ async function handler(request, config) {
607
+ const sso2 = getSSO(config);
608
+ if (!sso2)
544
609
  throw unavailable();
545
- return sso.callbackHandler(request);
610
+ return sso2.handler(request, config);
546
611
  }
547
612
  // src/ui/sign-in-loading.tsx
548
613
  import { jsxDEV, Fragment } from "react/jsx-dev-runtime";
549
- function SignInLoading({ children }) {
614
+ function SignInLoading({ complete = false, children }) {
550
615
  const { isLoading } = useUser();
551
- if (isLoading)
616
+ if (isLoading && !complete)
552
617
  return /* @__PURE__ */ jsxDEV(Fragment, {
553
618
  children
554
619
  }, undefined, false, undefined, this);
555
- return /* @__PURE__ */ jsxDEV(Fragment, {}, undefined, false, undefined, this);
620
+ return null;
556
621
  }
557
622
  // src/ui/signed-in.tsx
558
623
  import { jsxDEV as jsxDEV2, Fragment as Fragment2 } from "react/jsx-dev-runtime";
@@ -562,14 +627,14 @@ function SignedIn({ children }) {
562
627
  return /* @__PURE__ */ jsxDEV2(Fragment2, {
563
628
  children
564
629
  }, undefined, false, undefined, this);
565
- return /* @__PURE__ */ jsxDEV2(Fragment2, {}, undefined, false, undefined, this);
630
+ return null;
566
631
  }
567
632
  // src/ui/signed-out.tsx
568
633
  import { jsxDEV as jsxDEV3, Fragment as Fragment3 } from "react/jsx-dev-runtime";
569
634
  function SignedOut({ children }) {
570
635
  const { user } = useUser();
571
636
  if (user)
572
- return /* @__PURE__ */ jsxDEV3(Fragment3, {}, undefined, false, undefined, this);
637
+ return null;
573
638
  return /* @__PURE__ */ jsxDEV3(Fragment3, {
574
639
  children
575
640
  }, undefined, false, undefined, this);
@@ -577,7 +642,7 @@ function SignedOut({ children }) {
577
642
  // src/ui/sso-provider.tsx
578
643
  import { createContext, useCallback, useContext, useEffect, useState } from "react";
579
644
  import { jsxDEV as jsxDEV4 } from "react/jsx-dev-runtime";
580
- var SSOContext = createContext(undefined);
645
+ var CTX = createContext(undefined);
581
646
  var generateStorageKey = (tenantId) => {
582
647
  return `es-sso-user-${tenantId.replace(/[^a-zA-Z0-9]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "")}`;
583
648
  };
@@ -586,6 +651,8 @@ function SSOProvider({
586
651
  storage = "memory",
587
652
  storageKey,
588
653
  userUrl,
654
+ tokenUrl,
655
+ refreshUrl,
589
656
  disableListener = false,
590
657
  children
591
658
  }) {
@@ -598,7 +665,7 @@ function SSOProvider({
598
665
  return user2.sso?.tenant?.id === tenantId;
599
666
  }, [tenantId]);
600
667
  const loadUserFromStorage = useCallback(() => {
601
- if (storage === "memory")
668
+ if (storage === "memory" || typeof window === "undefined")
602
669
  return null;
603
670
  try {
604
671
  const storageObject = storage === "local" ? localStorage : sessionStorage;
@@ -616,7 +683,7 @@ function SSOProvider({
616
683
  }
617
684
  }, [storage, actualStorageKey, isValidUser]);
618
685
  const saveUserToStorage = useCallback((user2) => {
619
- if (storage === "memory")
686
+ if (storage === "memory" || typeof window === "undefined")
620
687
  return;
621
688
  try {
622
689
  const storageObject = storage === "local" ? localStorage : sessionStorage;
@@ -641,6 +708,12 @@ function SSOProvider({
641
708
  setIsLoading(true);
642
709
  try {
643
710
  const response = await fetch(userUrl);
711
+ if (response.status === 401) {
712
+ setUserState(null);
713
+ saveUserToStorage(null);
714
+ setIsLoading(false);
715
+ return;
716
+ }
644
717
  if (!response.ok) {
645
718
  throw new Error(`Failed to fetch user: ${response.status} ${response.statusText}`);
646
719
  }
@@ -662,20 +735,17 @@ function SSOProvider({
662
735
  const storedUser = loadUserFromStorage();
663
736
  if (storedUser) {
664
737
  setUserState(storedUser);
665
- setIsLoading(false);
666
- } else if (userUrl) {
738
+ }
739
+ if (userUrl) {
667
740
  fetchUserFromUrl();
668
741
  } else {
669
742
  setIsLoading(false);
670
743
  }
671
744
  }, [loadUserFromStorage, userUrl, fetchUserFromUrl]);
672
745
  useEffect(() => {
673
- console.log(disableListener, storage, actualStorageKey);
674
746
  if (disableListener || storage === "memory")
675
747
  return;
676
- console.log("set up listener");
677
748
  const handleStorageChange = (event) => {
678
- console.log("lsitened", actualStorageKey);
679
749
  if (event.key !== actualStorageKey)
680
750
  return;
681
751
  if (event.newValue === null) {
@@ -702,20 +772,99 @@ function SSOProvider({
702
772
  const contextValue = {
703
773
  user,
704
774
  setUser,
705
- isLoading
775
+ isLoading,
776
+ tokenUrl,
777
+ refreshUrl
706
778
  };
707
- return /* @__PURE__ */ jsxDEV4(SSOContext.Provider, {
779
+ return /* @__PURE__ */ jsxDEV4(CTX.Provider, {
708
780
  value: contextValue,
709
781
  children
710
782
  }, undefined, false, undefined, this);
711
783
  }
712
784
  function useUser() {
713
- const context = useContext(SSOContext);
785
+ const context = useContext(CTX);
714
786
  if (context === undefined) {
715
787
  throw new Error("useUser must be used within a SSOProvider");
716
788
  }
717
789
  return context;
718
790
  }
791
+ function useToken() {
792
+ const context = useContext(CTX);
793
+ if (context === undefined) {
794
+ throw new Error("useToken must be used within a SSOProvider");
795
+ }
796
+ const { tokenUrl, refreshUrl } = context;
797
+ if (!tokenUrl || !refreshUrl) {
798
+ throw new Error('useToken requires that a "tokenUrl" and "refreshUrl" be set in the SSOProvider');
799
+ }
800
+ const [token, setToken] = useState(null);
801
+ const [expires, setExpires] = useState(null);
802
+ const [isLoading, setIsLoading] = useState(!!tokenUrl);
803
+ const [error, setError] = useState(null);
804
+ const fetchJwt = useCallback(async (url) => {
805
+ setIsLoading(true);
806
+ setError(null);
807
+ try {
808
+ const response = await fetch(url);
809
+ if (response.status === 401) {
810
+ context.setUser(null);
811
+ setToken(null);
812
+ setExpires(null);
813
+ setIsLoading(false);
814
+ return;
815
+ }
816
+ if (!response.ok) {
817
+ throw new Error(`Failed to fetch JWT: ${response.status} ${response.statusText}`);
818
+ }
819
+ const data = await response.json();
820
+ setToken(data.token);
821
+ setExpires(new Date(data.expires));
822
+ } catch (err) {
823
+ const error2 = err instanceof Error ? err : new Error(String(err));
824
+ setError(error2);
825
+ setToken(null);
826
+ setExpires(null);
827
+ console.error("Error fetching JWT:", error2);
828
+ } finally {
829
+ setIsLoading(false);
830
+ }
831
+ }, [context]);
832
+ const refresh = useCallback(async () => {
833
+ const url = refreshUrl || tokenUrl;
834
+ if (!url) {
835
+ console.warn("No tokenUrl or refreshUrl provided");
836
+ return;
837
+ }
838
+ await fetchJwt(url);
839
+ }, [refreshUrl, tokenUrl, fetchJwt]);
840
+ useEffect(() => {
841
+ if (!tokenUrl) {
842
+ setIsLoading(false);
843
+ return;
844
+ }
845
+ fetchJwt(tokenUrl);
846
+ }, [tokenUrl, fetchJwt]);
847
+ useEffect(() => {
848
+ if (!expires || !refreshUrl)
849
+ return;
850
+ const checkExpiration = () => {
851
+ const now = new Date;
852
+ const timeUntilExpiry = expires.getTime() - now.getTime();
853
+ if (timeUntilExpiry <= 60000 && timeUntilExpiry > 0) {
854
+ refresh();
855
+ }
856
+ };
857
+ checkExpiration();
858
+ const interval = setInterval(checkExpiration, 30000);
859
+ return () => clearInterval(interval);
860
+ }, [expires, refreshUrl, refresh]);
861
+ return {
862
+ token,
863
+ isLoading,
864
+ error,
865
+ refresh
866
+ };
867
+ }
719
868
 
720
869
  // src/index.ts
721
870
  async function enterpriseStandard(appId, appKey, initConfig) {
@@ -724,9 +873,9 @@ async function enterpriseStandard(appId, appKey, initConfig) {
724
873
  let paths;
725
874
  const ioniteUrl = initConfig?.ioniteUrl ?? "https://ionite.com";
726
875
  if (appId === "IONITE_PUBLIC_DEMO") {
727
- vaultUrl = "https://vault.ionite.dev/v1/secret/data";
728
- vaultToken = "hvs.qIM9R0hmdnVCQmdvWdDGPSZ4";
729
- paths = { sso: "ionite/DEV01K2JRP1DWXFCZN9FRDT37J1Y0" };
876
+ vaultUrl = "https://vault-ionite.ionite.dev/v1/secret/data";
877
+ vaultToken = "hvs.NuiBSLuFk5Ju4JDOUwTOlSlP";
878
+ paths = { sso: "ionite/IONITE_PUBLIC_DEMO" };
730
879
  } else if (appKey) {
731
880
  if (!vaultUrl || !vaultToken) {
732
881
  throw new Error("TODO something is wrong with the ionite config, handle this error");
@@ -742,7 +891,7 @@ async function enterpriseStandard(appId, appKey, initConfig) {
742
891
  ioniteUrl,
743
892
  defaultInstance: initConfig?.defaultInstance || initConfig?.defaultInstance !== false && !defaultInstance2,
744
893
  vault: vaultClient,
745
- sso: paths.sso ? new SSOManager(await vaultClient.getSecret(paths.sso)) : undefined,
894
+ sso: paths.sso ? sso(await vaultClient.getSecret(paths.sso)) : undefined,
746
895
  iam: paths.iam ? await iam(await vaultClient.getSecret(paths.iam)) : undefined
747
896
  };
748
897
  if (result.defaultInstance) {
@@ -755,8 +904,10 @@ async function enterpriseStandard(appId, appKey, initConfig) {
755
904
  }
756
905
  export {
757
906
  useUser,
907
+ useToken,
758
908
  oidcCallbackSchema,
759
909
  initiateLogin,
910
+ handler,
760
911
  getUser,
761
912
  getRequiredUser,
762
913
  enterpriseStandard,
package/dist/server.d.ts CHANGED
@@ -1,5 +1,6 @@
1
- import type { EnterpriseStandard } from '.';
2
- export declare function getUser(request: Request, es?: EnterpriseStandard): Promise<import("./enterprise-user").EnterpriseUser | undefined>;
3
- export declare function getRequiredUser(request: Request, es?: EnterpriseStandard): Promise<import("./enterprise-user").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;
@@ -1,2 +1,4 @@
1
1
  import type { PropsWithChildren } from 'react';
2
- export declare function SignInLoading({ children }: PropsWithChildren): import("react/jsx-runtime").JSX.Element;
2
+ export declare function SignInLoading({ complete, children }: {
3
+ complete?: boolean;
4
+ } & PropsWithChildren): import("react/jsx-runtime").JSX.Element | null;
@@ -1,2 +1,2 @@
1
1
  import type { PropsWithChildren } from 'react';
2
- export declare function SignedIn({ children }: PropsWithChildren): import("react/jsx-runtime").JSX.Element;
2
+ export declare function SignedIn({ children }: PropsWithChildren): import("react/jsx-runtime").JSX.Element | null;
@@ -1,2 +1,2 @@
1
1
  import type { PropsWithChildren } from 'react';
2
- export declare function SignedOut({ children }: PropsWithChildren): import("react/jsx-runtime").JSX.Element;
2
+ export declare function SignedOut({ children }: PropsWithChildren): import("react/jsx-runtime").JSX.Element | null;
@@ -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 SSOContextValue {
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(): SSOContextValue;
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): Promise<Vault>;
16
+ export declare function vault(url: string, token: string): Vault;
17
17
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@enterprisestandard/react",
3
- "version": "0.0.2",
3
+ "version": "0.0.3-beta.2",
4
4
  "description": "Enterprise Standard React Components",
5
5
  "private": false,
6
6
  "main": "dist/index.js",