@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 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,12 @@ 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`;
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 this.retryWithBackoff(async () => {
242
- if (!this.config)
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 this.getPublicKey(header.kid);
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 this.fetchJwks();
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", 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)]
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 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);
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 this.refreshToken(refresh_token);
345
+ tokenResponse = await refreshToken(refresh_token);
350
346
  }
351
347
  return tokenResponse;
352
348
  }
353
- createCookie(name, value, expires) {
354
- 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}`;
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=${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};`;
370
372
  }
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};`;
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(`${this.config.cookiePrefix}.${name}=`));
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
- async function vault(url, token) {
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
- const secret = await resp.json();
397
- 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
+ }
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(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,32 +581,38 @@ 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);
528
586
  }
529
- async function getRequiredUser(request, es) {
530
- const sso = getSSO(es);
531
- if (!sso)
587
+ async function getRequiredUser(request, config) {
588
+ const sso2 = getSSO(config);
589
+ if (!sso2)
532
590
  throw unavailable();
533
- return sso.getRequiredUser(request);
591
+ return sso2.getRequiredUser(request);
534
592
  }
535
- async function initiateLogin(landingPage, errorPage, es) {
536
- const sso = getSSO(es);
537
- if (!sso)
593
+ async function initiateLogin(config) {
594
+ const sso2 = getSSO(config);
595
+ if (!sso2)
538
596
  throw unavailable();
539
- return sso.initiateLogin(landingPage, errorPage);
597
+ return sso2.initiateLogin(config);
540
598
  }
541
- async function callback(request, es) {
542
- const sso = getSSO(es);
543
- if (!sso)
599
+ async function callback(request, config) {
600
+ const sso2 = getSSO(config);
601
+ if (!sso2)
544
602
  throw unavailable();
545
- return sso.callbackHandler(request);
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 SSOContext = createContext(undefined);
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(SSOContext.Provider, {
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(SSOContext);
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.qIM9R0hmdnVCQmdvWdDGPSZ4";
729
- 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" };
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 ? new SSOManager(await vaultClient.getSecret(paths.sso)) : undefined,
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 { 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;
@@ -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.1",
4
4
  "description": "Enterprise Standard React Components",
5
5
  "private": false,
6
6
  "main": "dist/index.js",