@byggnode/auth-client 0.0.1 → 0.1.0

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/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # `@byggnode/auth-client`
2
2
 
3
- Thin Angular client for the Byggnode central IdP (`https://auth.handverksradet.no`).
3
+ Thin Angular client for the Byggnode central IdP (`https://auth.byggnode.no`;
4
+ `https://auth.handverksradet.no` remains a supported alias).
4
5
  Use this from product apps (HR, …). Domain logic stays in those apps.
5
6
 
6
7
  ## Install
@@ -30,7 +31,7 @@ import { provideAuthClient, SessionStore } from "@byggnode/auth-client";
30
31
  export const appConfig = {
31
32
  providers: [
32
33
  provideAuthClient({
33
- authOrigin: "https://auth.handverksradet.no",
34
+ authOrigin: "https://auth.byggnode.no",
34
35
  appSlug: "handverksradet",
35
36
  returnUrl: "https://hr.example/callback",
36
37
  }),
@@ -41,24 +42,31 @@ export const appConfig = {
41
42
  ```ts
42
43
  const session = inject(SessionStore);
43
44
 
44
- // Hosted login on the IdP (app + return_url query)
45
- session.redirectToLogin();
45
+ // Auth /authorize with PKCE: straight back with a code when the user is
46
+ // signed in on Auth (SSO across app domains), else the hosted login page
47
+ await session.redirectToLogin();
48
+ // Check silently; the callback gets ?error=login_required when not signed in
49
+ await session.redirectToLogin({ prompt: "none" });
46
50
 
47
- // Or send a magic link from your own form
51
+ // Or send a magic link from your own form. The mail also has a 6-digit
52
+ // code: let the user type it here and stay in the app
48
53
  await session.requestMagicLink(email);
54
+ const res = await session.loginWithCode(email, code); // 400 invalid_code on a wrong code
49
55
 
50
56
  // Invite a user (Auth sends mail and applies role on accept)
51
57
  await session.createInvite({ email, role: "cms" });
52
58
 
53
- // /callback?code=… → POST /oauth/token
54
- await session.handleCallback(code);
59
+ // /callback?code=… → POST /oauth/token (sends the PKCE verifier if any;
60
+ // throws the callback error, e.g. "login_required")
61
+ await session.handleCallback(location.search);
55
62
 
56
63
  if (session.can("articles:publish")) {
57
64
  // …
58
65
  }
59
66
 
60
67
  const me = await session.request("/me");
61
- await session.logout();
68
+ await session.logout(); // this app only
69
+ await session.logoutEverywhere(); // Auth GET /logout: every app on every domain
62
70
  ```
63
71
 
64
72
  `SessionStore.request` sends `Authorization: Bearer …`, refreshes when the
@@ -67,4 +75,10 @@ behavior as the auth console. Session tokens are stored in **localStorage** by
67
75
  default (survives new tabs); only a known-invalid refresh (`refresh_expired` /
68
76
  `invalid_refresh`) clears the session — network blips and 5xx do not.
69
77
 
78
+ Tabs (and apps) on the same origin share the stored session. Auth rotates the
79
+ refresh token on every use, so the client refreshes under a Web Lock
80
+ (`navigator.locks`), adopts a newer token another tab stored instead of
81
+ refreshing with a dead one, and follows `storage` events so every tab sees
82
+ logins, refreshes and logouts from the others.
83
+
70
84
  Grant-dialog UI is a stub (`GrantDialog`) and is not required for v1.
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, inject, signal, computed, Injectable, makeEnvironmentProviders, input, output, Component } from '@angular/core';
2
+ import { InjectionToken, inject, signal, computed, DestroyRef, Injectable, makeEnvironmentProviders, input, output, Component } from '@angular/core';
3
3
  import { Router } from '@angular/router';
4
4
 
5
5
  const ACCESS_REFRESH_SKEW_MS = 60_000;
@@ -53,6 +53,7 @@ function resolveAuthClientConfig(config) {
53
53
  returnUrl,
54
54
  storageKey: config.storageKey?.trim() || "auth.session",
55
55
  loginPath: config.loginPath?.trim() || "/login",
56
+ authorizePath: config.authorizePath?.trim() || "/authorize",
56
57
  refreshSkewMs: config.refreshSkewMs ?? ACCESS_REFRESH_SKEW_MS,
57
58
  expiredPath: config.expiredPath?.trim() || "/login?expired=1",
58
59
  };
@@ -172,6 +173,19 @@ function isSessionPayload(value) {
172
173
  Array.isArray(rec.permissions));
173
174
  }
174
175
 
176
+ function base64url(bytes) {
177
+ let binary = "";
178
+ for (const byte of bytes)
179
+ binary += String.fromCharCode(byte);
180
+ return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
181
+ }
182
+ /** RFC 7636 S256 pair: 43-char verifier, base64url(sha256(verifier)) challenge. */
183
+ async function createPkcePair() {
184
+ const verifier = base64url(crypto.getRandomValues(new Uint8Array(32)));
185
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
186
+ return { verifier, challenge: base64url(new Uint8Array(digest)) };
187
+ }
188
+
175
189
  function joinAuthUrl(origin, path) {
176
190
  const normalizedPath = path.startsWith("/") ? path : `/${path}`;
177
191
  if (!origin)
@@ -212,6 +226,37 @@ function readCallbackCode(searchOrUrl) {
212
226
  }
213
227
  return raw;
214
228
  }
229
+ /** `error` on the callback URL, e.g. `login_required` after `prompt=none`. */
230
+ function readCallbackError(searchOrUrl) {
231
+ const raw = searchOrUrl.trim();
232
+ if (!raw.includes("="))
233
+ return null;
234
+ try {
235
+ const url = raw.includes("://") ? new URL(raw) : new URL(raw, "http://auth.local");
236
+ return (url.searchParams.get("error") ??
237
+ new URLSearchParams(raw.startsWith("?") ? raw.slice(1) : raw).get("error"));
238
+ }
239
+ catch {
240
+ return null;
241
+ }
242
+ }
243
+ function buildAuthorizeUrl(opts) {
244
+ const url = new URL(joinAuthUrl(opts.authOrigin, opts.authorizePath ?? "/authorize"), "http://auth.local");
245
+ url.searchParams.set("app", opts.appSlug);
246
+ url.searchParams.set("return_url", opts.returnUrl);
247
+ url.searchParams.set("code_challenge", opts.codeChallenge);
248
+ url.searchParams.set("code_challenge_method", "S256");
249
+ if (opts.prompt)
250
+ url.searchParams.set("prompt", opts.prompt);
251
+ return opts.authOrigin ? url.toString() : `${url.pathname}${url.search}`;
252
+ }
253
+ /** Auth `GET /logout`: ends SSO and every app session it issued. */
254
+ function buildLogoutUrl(opts) {
255
+ const url = new URL(joinAuthUrl(opts.authOrigin, "/logout"), "http://auth.local");
256
+ url.searchParams.set("app", opts.appSlug);
257
+ url.searchParams.set("return_url", opts.returnUrl);
258
+ return opts.authOrigin ? url.toString() : `${url.pathname}${url.search}`;
259
+ }
215
260
  function buildLoginUrl(opts) {
216
261
  const path = joinAuthUrl(opts.authOrigin, opts.loginPath ?? "/login");
217
262
  const url = path.includes("://")
@@ -241,14 +286,45 @@ function isInvalidRefreshResponse(status, body) {
241
286
  class AuthClient {
242
287
  config;
243
288
  storage;
289
+ pkceStorage;
244
290
  fetchFn;
291
+ locks;
292
+ listeners = new Set();
245
293
  current;
246
294
  refreshInFlight = null;
247
295
  constructor(config, options = {}) {
248
296
  this.config = resolveAuthClientConfig(config);
249
297
  this.storage = options.storage ?? defaultSessionStorage();
298
+ this.pkceStorage = options.pkceStorage ?? defaultPkceStorage();
250
299
  this.fetchFn = options.fetch ?? fetch;
300
+ this.locks =
301
+ options.locks === undefined ? defaultLocks() : options.locks;
302
+ this.current = this.loadFromStorage();
303
+ if (typeof window !== "undefined") {
304
+ window.addEventListener("storage", (event) => {
305
+ if (event.key === this.config.storageKey || event.key === null) {
306
+ this.reloadFromStorage();
307
+ }
308
+ });
309
+ }
310
+ }
311
+ /** Called whenever the session changes, including from another tab. */
312
+ onChange(listener) {
313
+ this.listeners.add(listener);
314
+ return () => this.listeners.delete(listener);
315
+ }
316
+ /** Adopt what another tab (or app on this origin) stored. */
317
+ reloadFromStorage() {
251
318
  this.current = this.loadFromStorage();
319
+ this.emit();
320
+ return this.current;
321
+ }
322
+ emit() {
323
+ for (const listener of this.listeners)
324
+ listener(this.current);
325
+ }
326
+ get pkceKey() {
327
+ return `${this.config.storageKey}.pkce`;
252
328
  }
253
329
  getSession() {
254
330
  return this.current;
@@ -275,18 +351,50 @@ class AuthClient {
275
351
  const next = withExpiresAt(session);
276
352
  this.current = next;
277
353
  this.storage.setItem(this.config.storageKey, JSON.stringify(next));
354
+ this.emit();
278
355
  return next;
279
356
  }
280
357
  clearSession() {
281
358
  this.current = null;
282
359
  this.storage.removeItem(this.config.storageKey);
360
+ this.emit();
283
361
  }
362
+ /** Hosted login page without SSO (`app` + `return_url`). */
284
363
  loginUrl() {
285
364
  return buildLoginUrl(this.config);
286
365
  }
287
- /** Redirect the browser to the IdP login page (`app` + `return_url`). */
288
- redirectToLogin(assign = defaultAssign) {
289
- assign(this.loginUrl());
366
+ /**
367
+ * Auth `GET /authorize` with a fresh PKCE pair. Signed in on Auth already:
368
+ * straight back with a code. Otherwise the hosted login page.
369
+ */
370
+ async authorizeUrl(prompt) {
371
+ const { verifier, challenge } = await createPkcePair();
372
+ this.pkceStorage.setItem(this.pkceKey, verifier);
373
+ return buildAuthorizeUrl({
374
+ authOrigin: this.config.authOrigin,
375
+ appSlug: this.config.appSlug,
376
+ returnUrl: this.config.returnUrl,
377
+ authorizePath: this.config.authorizePath,
378
+ codeChallenge: challenge,
379
+ prompt,
380
+ });
381
+ }
382
+ /** Send the browser through Auth `/authorize` (SSO across app domains). */
383
+ async redirectToLogin(options = {}) {
384
+ const { prompt, assign = defaultAssign } = typeof options === "function" ? { assign: options } : options;
385
+ assign(await this.authorizeUrl(prompt));
386
+ }
387
+ /**
388
+ * Log out of every app: ends this app session, then Auth `GET /logout`
389
+ * ends SSO and every app session it issued. `returnUrl` must be allowlisted.
390
+ */
391
+ async logoutEverywhere(returnUrl = `${new URL(this.config.returnUrl).origin}/`, assign = defaultAssign) {
392
+ await this.logout();
393
+ assign(buildLogoutUrl({
394
+ authOrigin: this.config.authOrigin,
395
+ appSlug: this.config.appSlug,
396
+ returnUrl,
397
+ }));
290
398
  }
291
399
  async requestMagicLink(email, intent = "login") {
292
400
  return authJson(this.fetchFn, this.url("/magic-link"), {
@@ -299,6 +407,21 @@ class AuthClient {
299
407
  }),
300
408
  });
301
409
  }
410
+ /**
411
+ * The 6-digit code from the mail `requestMagicLink()` sent, typed in the
412
+ * app's own form. Logs in without leaving the app (no SSO cookie).
413
+ */
414
+ async loginWithCode(email, code) {
415
+ const res = await authJson(this.fetchFn, this.url("/magic-link/code"), {
416
+ method: "POST",
417
+ body: JSON.stringify({ email, app: this.config.appSlug, code }),
418
+ });
419
+ if (res.status !== 200 || !res.body.code) {
420
+ const body = { error: res.body.error ?? "invalid_code" };
421
+ return { status: res.status, body: body };
422
+ }
423
+ return this.exchangeCode(res.body.code);
424
+ }
302
425
  /** Authenticated `POST /invites`. Auth owns mail + accept. */
303
426
  async createInvite(input) {
304
427
  return this.request("/invites", {
@@ -312,13 +435,14 @@ class AuthClient {
312
435
  }),
313
436
  });
314
437
  }
315
- async exchangeCode(code) {
438
+ async exchangeCode(code, codeVerifier) {
316
439
  const res = await authJson(this.fetchFn, this.url("/oauth/token"), {
317
440
  method: "POST",
318
441
  body: JSON.stringify({
319
442
  grant_type: "authorization_code",
320
443
  code,
321
444
  app: this.config.appSlug,
445
+ ...(codeVerifier ? { code_verifier: codeVerifier } : {}),
322
446
  }),
323
447
  });
324
448
  if (res.status === 200 && isSessionPayload(res.body)) {
@@ -326,12 +450,23 @@ class AuthClient {
326
450
  }
327
451
  return res;
328
452
  }
453
+ /**
454
+ * `/callback?code=…` → tokens. Sends the PKCE verifier from
455
+ * `redirectToLogin()` when there is one (codes from a magic link have none).
456
+ * Throws the callback `error` (e.g. `login_required`) or `missing_code`.
457
+ */
329
458
  async handleCallback(codeOrSearch) {
459
+ const error = readCallbackError(codeOrSearch);
460
+ const verifier = this.pkceStorage.getItem(this.pkceKey) ?? undefined;
461
+ this.pkceStorage.removeItem(this.pkceKey);
462
+ if (error) {
463
+ throw new Error(error);
464
+ }
330
465
  const code = readCallbackCode(codeOrSearch);
331
466
  if (!code) {
332
467
  throw new Error("missing_code");
333
468
  }
334
- const res = await this.exchangeCode(code);
469
+ const res = await this.exchangeCode(code, verifier);
335
470
  if (res.status !== 200 || !isSessionPayload(res.body)) {
336
471
  throw new Error(res.body.error ?? "exchange_failed");
337
472
  }
@@ -410,12 +545,41 @@ class AuthClient {
410
545
  async refreshSessionDetailed() {
411
546
  if (this.refreshInFlight)
412
547
  return this.refreshInFlight;
413
- this.refreshInFlight = this.doRefresh().finally(() => {
548
+ const run = () => this.doRefresh();
549
+ this.refreshInFlight = (this.locks
550
+ ? this.locks.request(`${this.config.storageKey}:refresh`, run)
551
+ : run()).finally(() => {
414
552
  this.refreshInFlight = null;
415
553
  });
416
554
  return this.refreshInFlight;
417
555
  }
556
+ /**
557
+ * Auth rotates the refresh token on every use and the old one dies at once,
558
+ * so every tab on this origin must use the newest stored token: adopt what
559
+ * another tab stored before refreshing, and after an `invalid_refresh`.
560
+ */
418
561
  async doRefresh() {
562
+ const stored = this.loadFromStorage();
563
+ if (stored && stored.refresh_token !== this.current?.refresh_token) {
564
+ this.current = stored;
565
+ this.emit();
566
+ const expiresAt = accessTokenExpiresAtMs(stored);
567
+ if (!accessTokenNeedsRefresh(expiresAt, Date.now(), this.config.refreshSkewMs)) {
568
+ return "refreshed";
569
+ }
570
+ }
571
+ const outcome = await this.postRefresh();
572
+ if (outcome !== "invalid")
573
+ return outcome;
574
+ const latest = this.loadFromStorage();
575
+ if (latest && latest.refresh_token !== this.current?.refresh_token) {
576
+ this.current = latest;
577
+ this.emit();
578
+ return "refreshed";
579
+ }
580
+ return "invalid";
581
+ }
582
+ async postRefresh() {
419
583
  const refreshToken = this.current?.refresh_token;
420
584
  if (!refreshToken)
421
585
  return "invalid";
@@ -454,6 +618,21 @@ class AuthClient {
454
618
  function createAuthClient(config, options) {
455
619
  return new AuthClient(config, options);
456
620
  }
621
+ function defaultPkceStorage() {
622
+ try {
623
+ if (typeof sessionStorage !== "undefined")
624
+ return sessionStorage;
625
+ }
626
+ catch {
627
+ // private mode / non-browser
628
+ }
629
+ return createMemoryStorage();
630
+ }
631
+ function defaultLocks() {
632
+ const locks = globalThis
633
+ .navigator?.locks;
634
+ return locks && typeof locks.request === "function" ? locks : null;
635
+ }
457
636
  function defaultAssign(url) {
458
637
  if (typeof location !== "undefined") {
459
638
  location.assign(url);
@@ -480,6 +659,10 @@ class SessionStore {
480
659
  ...(ngDevMode ? [{ debugName: "roles" }] : /* istanbul ignore next */ []));
481
660
  permissions = computed(() => this.session()?.permissions ?? [], /* @ts-ignore */
482
661
  ...(ngDevMode ? [{ debugName: "permissions" }] : /* istanbul ignore next */ []));
662
+ constructor() {
663
+ const stop = this.client.onChange((session) => this.session.set(session));
664
+ inject(DestroyRef).onDestroy(stop);
665
+ }
483
666
  can(permission) {
484
667
  return this.permissions().includes(permission);
485
668
  }
@@ -501,12 +684,23 @@ class SessionStore {
501
684
  loginUrl() {
502
685
  return this.client.loginUrl();
503
686
  }
504
- redirectToLogin() {
505
- this.client.redirectToLogin();
687
+ /** Auth `/authorize`: SSO if signed in on Auth, else the hosted login page. */
688
+ redirectToLogin(options = {}) {
689
+ return this.client.redirectToLogin(options);
690
+ }
691
+ /** Ends SSO and every app session it issued, then returns to `returnUrl`. */
692
+ logoutEverywhere(returnUrl) {
693
+ return this.client.logoutEverywhere(returnUrl);
506
694
  }
507
695
  async requestMagicLink(email, intent = "login") {
508
696
  return this.client.requestMagicLink(email, intent);
509
697
  }
698
+ /** The 6-digit code from that mail, typed in the app's own form. */
699
+ async loginWithCode(email, code) {
700
+ const result = await this.client.loginWithCode(email, code);
701
+ this.session.set(this.client.getSession());
702
+ return result;
703
+ }
510
704
  async createInvite(input) {
511
705
  const result = await this.client.createInvite(input);
512
706
  this.session.set(this.client.getSession());
@@ -540,7 +734,7 @@ class SessionStore {
540
734
  }
541
735
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: SessionStore, decorators: [{
542
736
  type: Injectable
543
- }] });
737
+ }], ctorParameters: () => [] });
544
738
 
545
739
  function provideAuthClient(config) {
546
740
  const resolved = resolveAuthClientConfig(config);
@@ -597,5 +791,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
597
791
  * Generated bundle index. Do not edit.
598
792
  */
599
793
 
600
- export { ACCESS_REFRESH_SKEW_MS, AUTH_CLIENT_CONFIG, AuthClient, GrantDialog, SessionStore, accessTokenExpiresAtMs, accessTokenNeedsRefresh, authJson, buildLoginUrl, createAuthClient, createMemoryStorage, defaultSessionStorage, isHttpUrl, isSessionPayload, isUnauthorized, joinAuthUrl, jsonHeaders, jwtExpMs, parseJsonResponse, provideAuthClient, readCallbackCode, readErrorCode, readStoredJson, requireAuthSession, resolveAuthClientConfig, resolveReturnUrl, withExpiresAt };
794
+ export { ACCESS_REFRESH_SKEW_MS, AUTH_CLIENT_CONFIG, AuthClient, GrantDialog, SessionStore, accessTokenExpiresAtMs, accessTokenNeedsRefresh, authJson, buildAuthorizeUrl, buildLoginUrl, buildLogoutUrl, createAuthClient, createMemoryStorage, createPkcePair, defaultSessionStorage, isHttpUrl, isSessionPayload, isUnauthorized, joinAuthUrl, jsonHeaders, jwtExpMs, parseJsonResponse, provideAuthClient, readCallbackCode, readCallbackError, readErrorCode, readStoredJson, requireAuthSession, resolveAuthClientConfig, resolveReturnUrl, withExpiresAt };
601
795
  //# sourceMappingURL=byggnode-auth-client.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"byggnode-auth-client.mjs","sources":["../../src/lib/session-expiry.ts","../../src/lib/config.ts","../../src/lib/http.ts","../../src/lib/storage.ts","../../src/lib/types.ts","../../src/lib/urls.ts","../../src/lib/client.ts","../../src/lib/tokens.ts","../../src/lib/session.ts","../../src/lib/provide.ts","../../src/lib/guards.ts","../../src/lib/grant-dialog.ts","../../src/byggnode-auth-client.ts"],"sourcesContent":["export const ACCESS_REFRESH_SKEW_MS = 60_000;\n\nexport function jwtExpMs(accessToken: string): number | null {\n const parts = accessToken.split(\".\");\n if (parts.length < 2 || !parts[1]) return null;\n try {\n const raw = parts[1].replaceAll(\"-\", \"+\").replaceAll(\"_\", \"/\");\n const pad = raw.length % 4 === 0 ? \"\" : \"=\".repeat(4 - (raw.length % 4));\n const json = atob(raw + pad);\n const exp = (JSON.parse(json) as { exp?: unknown }).exp;\n return typeof exp === \"number\" && Number.isFinite(exp) ? exp * 1000 : null;\n } catch {\n return null;\n }\n}\n\nexport function accessTokenExpiresAtMs(session: {\n access_token?: string;\n expires_at?: number;\n}): number | null {\n if (typeof session.expires_at === \"number\" && Number.isFinite(session.expires_at)) {\n return session.expires_at;\n }\n return jwtExpMs(session.access_token ?? \"\");\n}\n\nexport function accessTokenNeedsRefresh(\n expiresAtMs: number | null,\n now = Date.now(),\n skewMs = ACCESS_REFRESH_SKEW_MS,\n): boolean {\n if (expiresAtMs == null) return false;\n return expiresAtMs - now <= skewMs;\n}\n\nexport function withExpiresAt<\n T extends { access_token: string; expires_in?: number; expires_at?: number },\n>(session: T, now = Date.now()): T & { expires_at?: number } {\n const fromJwt = jwtExpMs(session.access_token);\n const fromTtl =\n typeof session.expires_in === \"number\" && session.expires_in > 0\n ? now + session.expires_in * 1000\n : null;\n const expiresAt = session.expires_at ?? fromJwt ?? fromTtl ?? undefined;\n return expiresAt == null ? session : { ...session, expires_at: expiresAt };\n}\n","import { ACCESS_REFRESH_SKEW_MS } from \"./session-expiry\";\n\nexport type AuthClientConfig = {\n /** IdP origin, e.g. `https://auth.handverksradet.no`. Empty string = same origin. */\n authOrigin: string;\n /** Registered app slug sent on magic-link / login redirect / token exchange. */\n appSlug: string;\n /** Allowed callback URL; verify redirects here with `?code=`. */\n returnUrl: string;\n /** Browser storage key (localStorage by default). Default matches the auth console (`auth.session`). */\n storageKey?: string;\n /** Path on the IdP for the hosted login page. Default `/login`. */\n loginPath?: string;\n /** Refresh this many ms before access-token expiry. Default 60s. */\n refreshSkewMs?: number;\n /** Router path SessionStore uses after a failed refresh. Default `/login?expired=1`. */\n expiredPath?: string;\n};\n\nexport type ResolvedAuthClientConfig = {\n authOrigin: string;\n appSlug: string;\n returnUrl: string;\n storageKey: string;\n loginPath: string;\n refreshSkewMs: number;\n expiredPath: string;\n};\n\nexport function resolveAuthClientConfig(\n config: AuthClientConfig,\n): ResolvedAuthClientConfig {\n const appSlug = config.appSlug.trim();\n const returnUrl = config.returnUrl.trim();\n if (!appSlug) {\n throw new Error(\"provideAuthClient: appSlug is required\");\n }\n if (!returnUrl) {\n throw new Error(\"provideAuthClient: returnUrl is required\");\n }\n return {\n authOrigin: config.authOrigin.replace(/\\/$/, \"\"),\n appSlug,\n returnUrl,\n storageKey: config.storageKey?.trim() || \"auth.session\",\n loginPath: config.loginPath?.trim() || \"/login\",\n refreshSkewMs: config.refreshSkewMs ?? ACCESS_REFRESH_SKEW_MS,\n expiredPath: config.expiredPath?.trim() || \"/login?expired=1\",\n };\n}\n","import type { AuthApiResult } from \"./types\";\n\nexport function readErrorCode(body: unknown): string {\n if (!body || typeof body !== \"object\") return \"\";\n const error = (body as { error?: unknown }).error;\n return typeof error === \"string\" ? error : \"\";\n}\n\nexport function isUnauthorized(status: number, body: unknown): boolean {\n if (status === 401) return true;\n return readErrorCode(body) === \"unauthorized\";\n}\n\nexport function jsonHeaders(\n init: RequestInit & { token?: string } = {},\n): Headers {\n const headers = new Headers(init.headers);\n if (init.body && !headers.has(\"content-type\")) {\n headers.set(\"content-type\", \"application/json\");\n }\n if (init.token) headers.set(\"authorization\", `Bearer ${init.token}`);\n return headers;\n}\n\nexport async function parseJsonResponse<T>(\n res: Response,\n): Promise<AuthApiResult<T>> {\n const body = (await res.json().catch(() => ({}))) as T;\n return { status: res.status, body };\n}\n\nexport type AuthFetchFn = (\n input: string,\n init?: RequestInit,\n) => Promise<Response>;\n\nexport async function authJson<T>(\n fetchFn: AuthFetchFn,\n url: string,\n init: RequestInit & { token?: string } = {},\n): Promise<AuthApiResult<T>> {\n const { token, ...rest } = init;\n const res = await fetchFn(url, { ...rest, headers: jsonHeaders(init) });\n return parseJsonResponse<T>(res);\n}\n","export type SessionStorageLike = {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n};\n\nexport function createMemoryStorage(): SessionStorageLike {\n const map = new Map<string, string>();\n return {\n getItem(key) {\n return map.get(key) ?? null;\n },\n setItem(key, value) {\n map.set(key, value);\n },\n removeItem(key) {\n map.delete(key);\n },\n };\n}\n\n/**\n * Prefer localStorage so IdP sessions survive new tabs and browser restarts.\n * Fall back to in-memory storage when localStorage is unavailable (private\n * mode / non-browser); sessionStorage is a last resort if localStorage throws\n * but sessionStorage works.\n */\n/** One-shot: copy auth.session from sessionStorage → localStorage (cutover). */\nfunction migrateSessionKeyToLocal(key = \"auth.session\"): void {\n try {\n if (typeof localStorage === \"undefined\" || typeof sessionStorage === \"undefined\") return;\n if (localStorage.getItem(key)) return;\n const raw = sessionStorage.getItem(key);\n if (!raw) return;\n localStorage.setItem(key, raw);\n sessionStorage.removeItem(key);\n } catch {\n // ignore quota / private mode\n }\n}\n\nexport function defaultSessionStorage(): SessionStorageLike {\n try {\n if (typeof localStorage !== \"undefined\") {\n migrateSessionKeyToLocal();\n return localStorage;\n }\n } catch {\n // private mode / non-browser\n }\n try {\n if (typeof sessionStorage !== \"undefined\") return sessionStorage;\n } catch {\n // private mode / non-browser\n }\n return createMemoryStorage();\n}\n\nexport function readStoredJson<T>(\n storage: SessionStorageLike,\n key: string,\n): T | null {\n try {\n const raw = storage.getItem(key);\n if (!raw) return null;\n return JSON.parse(raw) as T;\n } catch {\n return null;\n }\n}\n","export type AuthUser = {\n id: string;\n email: string;\n};\n\nexport type AppInfo = {\n slug: string;\n name: string;\n active?: boolean;\n allowed_origins?: string[];\n redirect_uris?: string[];\n};\n\nexport type RoleScope = {\n role: string;\n orgnr: string | null;\n};\n\nexport type SessionPayload = {\n access_token: string;\n refresh_token: string;\n token_type?: string;\n expires_in?: number;\n expires_at?: number;\n session_id?: string;\n user: AuthUser;\n app: { slug: string; name: string };\n roles: string[];\n permissions: string[];\n scopes: RoleScope[];\n};\n\nexport type AuthApiResult<T> = {\n status: number;\n body: T;\n};\n\nexport type MagicLinkResponse = {\n ok?: boolean;\n error?: string;\n dev_token?: string;\n};\n\nexport type InviteRequest = {\n email: string;\n role: string;\n app?: string;\n orgnr?: string;\n return_url?: string;\n};\n\nexport type InviteResponse = {\n ok?: boolean;\n error?: string;\n invite?: {\n id: string;\n email: string;\n role: string;\n app: string;\n orgnr: string | null;\n status?: string;\n expires_at: string;\n };\n dev_token?: string;\n};\n\nexport type TokenErrorBody = {\n error?: string;\n};\n\nexport type RoleRow = {\n slug: string;\n name: string;\n description?: string | null;\n permissions: string[];\n};\n\nexport function isSessionPayload(value: unknown): value is SessionPayload {\n if (!value || typeof value !== \"object\") return false;\n const rec = value as Partial<SessionPayload>;\n return (\n typeof rec.access_token === \"string\" &&\n rec.access_token.length > 0 &&\n typeof rec.refresh_token === \"string\" &&\n !!rec.user &&\n typeof rec.user.id === \"string\" &&\n typeof rec.user.email === \"string\" &&\n !!rec.app &&\n typeof rec.app.slug === \"string\" &&\n Array.isArray(rec.roles) &&\n Array.isArray(rec.permissions)\n );\n}\n","export function joinAuthUrl(origin: string, path: string): string {\n const normalizedPath = path.startsWith(\"/\") ? path : `/${path}`;\n if (!origin) return normalizedPath;\n return `${origin.replace(/\\/$/, \"\")}${normalizedPath}`;\n}\n\nexport function isHttpUrl(value: string): boolean {\n try {\n const url = new URL(value);\n return url.protocol === \"http:\" || url.protocol === \"https:\";\n } catch {\n return false;\n }\n}\n\nexport function resolveReturnUrl(\n requested: string | null | undefined,\n fallback: string,\n): string {\n if (requested && isHttpUrl(requested)) return requested;\n return fallback;\n}\n\nexport function readCallbackCode(searchOrUrl: string): string | null {\n const raw = searchOrUrl.trim();\n if (!raw) return null;\n try {\n if (raw.includes(\"://\") || raw.startsWith(\"/\") || raw.startsWith(\"?\")) {\n const url = raw.includes(\"://\")\n ? new URL(raw)\n : new URL(raw, \"http://auth.local\");\n return url.searchParams.get(\"code\");\n }\n } catch {\n // treat as a bare code below\n }\n if (raw.includes(\"=\")) {\n return new URLSearchParams(raw.startsWith(\"?\") ? raw.slice(1) : raw).get(\n \"code\",\n );\n }\n return raw;\n}\n\nexport function buildLoginUrl(opts: {\n authOrigin: string;\n appSlug: string;\n returnUrl: string;\n loginPath?: string;\n}): string {\n const path = joinAuthUrl(opts.authOrigin, opts.loginPath ?? \"/login\");\n const url = path.includes(\"://\")\n ? new URL(path)\n : new URL(path, \"http://auth.local\");\n url.searchParams.set(\"app\", opts.appSlug);\n url.searchParams.set(\"return_url\", opts.returnUrl);\n if (path.includes(\"://\")) return url.toString();\n return `${url.pathname}${url.search}`;\n}\n","import {\n resolveAuthClientConfig,\n type AuthClientConfig,\n type ResolvedAuthClientConfig,\n} from \"./config\";\nimport {\n authJson,\n isUnauthorized,\n readErrorCode,\n type AuthFetchFn,\n} from \"./http\";\nimport {\n accessTokenExpiresAtMs,\n accessTokenNeedsRefresh,\n withExpiresAt,\n} from \"./session-expiry\";\nimport {\n defaultSessionStorage,\n readStoredJson,\n type SessionStorageLike,\n} from \"./storage\";\nimport {\n isSessionPayload,\n type AuthApiResult,\n type InviteRequest,\n type InviteResponse,\n type MagicLinkResponse,\n type SessionPayload,\n type TokenErrorBody,\n} from \"./types\";\nimport { buildLoginUrl, joinAuthUrl, readCallbackCode } from \"./urls\";\n\nexport type AuthClientOptions = {\n storage?: SessionStorageLike;\n fetch?: AuthFetchFn;\n};\n\n/** Outcome of a refresh_token grant attempt. */\ntype RefreshOutcome = \"refreshed\" | \"invalid\" | \"transient\";\n\nconst INVALID_REFRESH_ERRORS = new Set([\n \"invalid_refresh\",\n \"refresh_expired\",\n \"refresh_token_required\",\n \"invalid_grant\",\n]);\n\nfunction isInvalidRefreshResponse(status: number, body: unknown): boolean {\n if (status === 401) return true;\n if (status !== 400) return false;\n const code = readErrorCode(body);\n return INVALID_REFRESH_ERRORS.has(code);\n}\n\nexport class AuthClient {\n readonly config: ResolvedAuthClientConfig;\n private readonly storage: SessionStorageLike;\n private readonly fetchFn: AuthFetchFn;\n private current: SessionPayload | null;\n private refreshInFlight: Promise<RefreshOutcome> | null = null;\n\n constructor(config: AuthClientConfig, options: AuthClientOptions = {}) {\n this.config = resolveAuthClientConfig(config);\n this.storage = options.storage ?? defaultSessionStorage();\n this.fetchFn = options.fetch ?? fetch;\n this.current = this.loadFromStorage();\n }\n\n getSession(): SessionPayload | null {\n return this.current;\n }\n\n get token(): string {\n return this.current?.access_token ?? \"\";\n }\n\n get user(): SessionPayload[\"user\"] | null {\n return this.current?.user ?? null;\n }\n\n get app(): SessionPayload[\"app\"] | null {\n return this.current?.app ?? null;\n }\n\n get roles(): string[] {\n return this.current?.roles ?? [];\n }\n\n get permissions(): string[] {\n return this.current?.permissions ?? [];\n }\n\n can(permission: string): boolean {\n return this.permissions.includes(permission);\n }\n\n setSession(session: SessionPayload): SessionPayload {\n const next = withExpiresAt(session);\n this.current = next;\n this.storage.setItem(this.config.storageKey, JSON.stringify(next));\n return next;\n }\n\n clearSession(): void {\n this.current = null;\n this.storage.removeItem(this.config.storageKey);\n }\n\n loginUrl(): string {\n return buildLoginUrl(this.config);\n }\n\n /** Redirect the browser to the IdP login page (`app` + `return_url`). */\n redirectToLogin(assign: (url: string) => void = defaultAssign): void {\n assign(this.loginUrl());\n }\n\n async requestMagicLink(\n email: string,\n intent: \"login\" | \"privat\" = \"login\",\n ): Promise<AuthApiResult<MagicLinkResponse>> {\n return authJson<MagicLinkResponse>(this.fetchFn, this.url(\"/magic-link\"), {\n method: \"POST\",\n body: JSON.stringify({\n email,\n app: this.config.appSlug,\n return_url: this.config.returnUrl,\n intent,\n }),\n });\n }\n\n /** Authenticated `POST /invites`. Auth owns mail + accept. */\n async createInvite(\n input: InviteRequest,\n ): Promise<AuthApiResult<InviteResponse>> {\n return this.request<InviteResponse>(\"/invites\", {\n method: \"POST\",\n body: JSON.stringify({\n email: input.email,\n role: input.role,\n app: input.app ?? this.config.appSlug,\n orgnr: input.orgnr,\n return_url: input.return_url ?? this.config.returnUrl,\n }),\n });\n }\n\n async exchangeCode(\n code: string,\n ): Promise<AuthApiResult<SessionPayload & TokenErrorBody>> {\n const res = await authJson<SessionPayload & TokenErrorBody>(\n this.fetchFn,\n this.url(\"/oauth/token\"),\n {\n method: \"POST\",\n body: JSON.stringify({\n grant_type: \"authorization_code\",\n code,\n app: this.config.appSlug,\n }),\n },\n );\n if (res.status === 200 && isSessionPayload(res.body)) {\n this.setSession(res.body);\n }\n return res;\n }\n\n async handleCallback(codeOrSearch: string): Promise<SessionPayload> {\n const code = readCallbackCode(codeOrSearch);\n if (!code) {\n throw new Error(\"missing_code\");\n }\n const res = await this.exchangeCode(code);\n if (res.status !== 200 || !isSessionPayload(res.body)) {\n throw new Error(res.body.error ?? \"exchange_failed\");\n }\n return this.current!;\n }\n\n async logout(): Promise<void> {\n const token = this.token;\n if (token) {\n await authJson(this.fetchFn, this.url(\"/logout\"), {\n method: \"POST\",\n token,\n }).catch(() => undefined);\n }\n this.clearSession();\n }\n\n /**\n * Authenticated JSON request with Bearer token, proactive refresh, and a\n * single 401 retry — same shape as the auth console `SessionStore.request`.\n *\n * Clears the stored session only when refresh is known-invalid (expired /\n * revoked refresh token). Network blips and 5xx do not log the user out.\n */\n async request<T>(\n path: string,\n init: RequestInit = {},\n ): Promise<AuthApiResult<T>> {\n const fresh = await this.ensureFreshToken();\n if (!fresh) {\n if (!this.token) {\n return { status: 401, body: { error: \"unauthorized\" } as T };\n }\n // Transient refresh failure — keep session; caller can retry later.\n return { status: 503, body: { error: \"refresh_unavailable\" } as T };\n }\n const first = await authJson<T>(this.fetchFn, this.url(path), {\n ...init,\n token: this.token,\n });\n if (!isUnauthorized(first.status, first.body)) return first;\n const renewed = await this.refreshSessionDetailed();\n if (renewed === \"refreshed\") {\n return authJson<T>(this.fetchFn, this.url(path), {\n ...init,\n token: this.token,\n });\n }\n if (renewed === \"invalid\") {\n this.clearSession();\n }\n return first;\n }\n\n async ensureFreshToken(): Promise<boolean> {\n if (!this.token) return false;\n const expiresAt = accessTokenExpiresAtMs(this.current ?? {});\n if (\n !accessTokenNeedsRefresh(\n expiresAt,\n Date.now(),\n this.config.refreshSkewMs,\n )\n ) {\n return true;\n }\n const outcome = await this.refreshSessionDetailed();\n if (outcome === \"refreshed\") return true;\n if (outcome === \"invalid\") {\n this.clearSession();\n return false;\n }\n // Transient: keep stored session so the caller can retry.\n return false;\n }\n\n async refreshSession(): Promise<boolean> {\n const outcome = await this.refreshSessionDetailed();\n if (outcome === \"refreshed\") return true;\n if (outcome === \"invalid\") {\n this.clearSession();\n }\n return false;\n }\n\n private async refreshSessionDetailed(): Promise<RefreshOutcome> {\n if (this.refreshInFlight) return this.refreshInFlight;\n this.refreshInFlight = this.doRefresh().finally(() => {\n this.refreshInFlight = null;\n });\n return this.refreshInFlight;\n }\n\n private async doRefresh(): Promise<RefreshOutcome> {\n const refreshToken = this.current?.refresh_token;\n if (!refreshToken) return \"invalid\";\n try {\n const res = await authJson<SessionPayload & TokenErrorBody>(\n this.fetchFn,\n this.url(\"/oauth/token\"),\n {\n method: \"POST\",\n body: JSON.stringify({\n grant_type: \"refresh_token\",\n refresh_token: refreshToken,\n }),\n },\n );\n if (res.status === 200 && isSessionPayload(res.body)) {\n this.setSession(res.body);\n return \"refreshed\";\n }\n if (isInvalidRefreshResponse(res.status, res.body)) {\n return \"invalid\";\n }\n return \"transient\";\n } catch {\n // Network error / aborted fetch — do not clear session.\n return \"transient\";\n }\n }\n\n private url(path: string): string {\n return joinAuthUrl(this.config.authOrigin, path);\n }\n\n private loadFromStorage(): SessionPayload | null {\n const raw = readStoredJson<SessionPayload>(\n this.storage,\n this.config.storageKey,\n );\n if (!raw || !isSessionPayload(raw)) return null;\n return withExpiresAt(raw);\n }\n}\n\nexport function createAuthClient(\n config: AuthClientConfig,\n options?: AuthClientOptions,\n): AuthClient {\n return new AuthClient(config, options);\n}\n\nfunction defaultAssign(url: string): void {\n if (typeof location !== \"undefined\") {\n location.assign(url);\n return;\n }\n throw new Error(`redirectToLogin: no location (${url})`);\n}\n","import { InjectionToken } from \"@angular/core\";\nimport type { ResolvedAuthClientConfig } from \"./config\";\n\nexport const AUTH_CLIENT_CONFIG = new InjectionToken<ResolvedAuthClientConfig>(\n \"AUTH_CLIENT_CONFIG\",\n);\n","import { Injectable, computed, inject, signal } from \"@angular/core\";\nimport { Router } from \"@angular/router\";\nimport { AuthClient } from \"./client\";\nimport { AUTH_CLIENT_CONFIG } from \"./tokens\";\nimport type {\n AuthApiResult,\n InviteRequest,\n InviteResponse,\n MagicLinkResponse,\n SessionPayload,\n} from \"./types\";\n\n@Injectable()\nexport class SessionStore {\n private readonly client = inject(AuthClient);\n private readonly router = inject(Router);\n private readonly config = inject(AUTH_CLIENT_CONFIG);\n readonly session = signal<SessionPayload | null>(this.client.getSession());\n readonly token = computed(() => this.session()?.access_token ?? \"\");\n readonly user = computed(() => this.session()?.user ?? null);\n readonly app = computed(() => this.session()?.app ?? null);\n readonly roles = computed(() => this.session()?.roles ?? []);\n readonly permissions = computed(() => this.session()?.permissions ?? []);\n\n can(permission: string): boolean {\n return this.permissions().includes(permission);\n }\n\n set(session: SessionPayload): void {\n this.session.set(this.client.setSession(session));\n }\n\n clear(): void {\n this.client.clearSession();\n this.session.set(null);\n }\n\n expireToLogin(): void {\n this.clear();\n void this.router.navigateByUrl(this.config.expiredPath ?? \"/login?expired=1\");\n }\n\n async logout(): Promise<void> {\n await this.client.logout();\n this.session.set(null);\n }\n\n loginUrl(): string {\n return this.client.loginUrl();\n }\n\n redirectToLogin(): void {\n this.client.redirectToLogin();\n }\n\n async requestMagicLink(\n email: string,\n intent: \"login\" | \"privat\" = \"login\",\n ): Promise<AuthApiResult<MagicLinkResponse>> {\n return this.client.requestMagicLink(email, intent);\n }\n\n async createInvite(\n input: InviteRequest,\n ): Promise<AuthApiResult<InviteResponse>> {\n const result = await this.client.createInvite(input);\n this.session.set(this.client.getSession());\n return result;\n }\n\n async handleCallback(codeOrSearch: string): Promise<SessionPayload> {\n const session = await this.client.handleCallback(codeOrSearch);\n this.session.set(session);\n return session;\n }\n\n async request<T>(\n path: string,\n init: RequestInit = {},\n ): Promise<AuthApiResult<T>> {\n const result = await this.client.request<T>(path, init);\n this.session.set(this.client.getSession());\n if (result.status === 401 && !this.client.token) {\n this.expireToLogin();\n }\n return result;\n }\n\n async ensureFreshToken(): Promise<boolean> {\n const ok = await this.client.ensureFreshToken();\n this.session.set(this.client.getSession());\n return ok;\n }\n\n async refreshSession(): Promise<boolean> {\n const ok = await this.client.refreshSession();\n this.session.set(this.client.getSession());\n return ok;\n }\n}\n","import { makeEnvironmentProviders, type EnvironmentProviders } from \"@angular/core\";\nimport { AuthClient } from \"./client\";\nimport { resolveAuthClientConfig, type AuthClientConfig } from \"./config\";\nimport { SessionStore } from \"./session\";\nimport { defaultSessionStorage } from \"./storage\";\nimport { AUTH_CLIENT_CONFIG } from \"./tokens\";\n\nexport function provideAuthClient(\n config: AuthClientConfig,\n): EnvironmentProviders {\n const resolved = resolveAuthClientConfig(config);\n return makeEnvironmentProviders([\n { provide: AUTH_CLIENT_CONFIG, useValue: resolved },\n {\n provide: AuthClient,\n useFactory: () =>\n new AuthClient(resolved, { storage: defaultSessionStorage() }),\n },\n SessionStore,\n ]);\n}\n","import { inject } from \"@angular/core\";\nimport { type CanActivateFn, Router } from \"@angular/router\";\nimport { SessionStore } from \"./session\";\n\nexport function requireAuthSession(loginUrl = \"/login\"): CanActivateFn {\n return () => {\n const session = inject(SessionStore);\n const router = inject(Router);\n return session.token() ? true : router.parseUrl(loginUrl);\n };\n}\n","import { Component, input, output } from \"@angular/core\";\n\n/**\n * Placeholder for a shared grant-access dialog.\n * Product apps should keep their own UI in v1 — this is not blocking.\n */\n@Component({\n selector: \"jg-auth-grant-dialog\",\n template: `\n @if (open()) {\n <p class=\"jg-auth-grant-dialog-stub\">\n Grant dialog is not implemented in @byggnode/auth-client v1.\n </p>\n }\n `,\n})\nexport class GrantDialog {\n readonly open = input(false);\n readonly closed = output<void>();\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"byggnode-auth-client.mjs","sources":["../../src/lib/session-expiry.ts","../../src/lib/config.ts","../../src/lib/http.ts","../../src/lib/storage.ts","../../src/lib/types.ts","../../src/lib/pkce.ts","../../src/lib/urls.ts","../../src/lib/client.ts","../../src/lib/tokens.ts","../../src/lib/session.ts","../../src/lib/provide.ts","../../src/lib/guards.ts","../../src/lib/grant-dialog.ts","../../src/byggnode-auth-client.ts"],"sourcesContent":["export const ACCESS_REFRESH_SKEW_MS = 60_000;\n\nexport function jwtExpMs(accessToken: string): number | null {\n const parts = accessToken.split(\".\");\n if (parts.length < 2 || !parts[1]) return null;\n try {\n const raw = parts[1].replaceAll(\"-\", \"+\").replaceAll(\"_\", \"/\");\n const pad = raw.length % 4 === 0 ? \"\" : \"=\".repeat(4 - (raw.length % 4));\n const json = atob(raw + pad);\n const exp = (JSON.parse(json) as { exp?: unknown }).exp;\n return typeof exp === \"number\" && Number.isFinite(exp) ? exp * 1000 : null;\n } catch {\n return null;\n }\n}\n\nexport function accessTokenExpiresAtMs(session: {\n access_token?: string;\n expires_at?: number;\n}): number | null {\n if (typeof session.expires_at === \"number\" && Number.isFinite(session.expires_at)) {\n return session.expires_at;\n }\n return jwtExpMs(session.access_token ?? \"\");\n}\n\nexport function accessTokenNeedsRefresh(\n expiresAtMs: number | null,\n now = Date.now(),\n skewMs = ACCESS_REFRESH_SKEW_MS,\n): boolean {\n if (expiresAtMs == null) return false;\n return expiresAtMs - now <= skewMs;\n}\n\nexport function withExpiresAt<\n T extends { access_token: string; expires_in?: number; expires_at?: number },\n>(session: T, now = Date.now()): T & { expires_at?: number } {\n const fromJwt = jwtExpMs(session.access_token);\n const fromTtl =\n typeof session.expires_in === \"number\" && session.expires_in > 0\n ? now + session.expires_in * 1000\n : null;\n const expiresAt = session.expires_at ?? fromJwt ?? fromTtl ?? undefined;\n return expiresAt == null ? session : { ...session, expires_at: expiresAt };\n}\n","import { ACCESS_REFRESH_SKEW_MS } from \"./session-expiry\";\n\nexport type AuthClientConfig = {\n /** IdP origin, e.g. `https://auth.byggnode.no`. Empty string = same origin. */\n authOrigin: string;\n /** Registered app slug sent on magic-link / login redirect / token exchange. */\n appSlug: string;\n /** Allowed callback URL; verify redirects here with `?code=`. */\n returnUrl: string;\n /** Browser storage key (localStorage by default). Default matches the auth console (`auth.session`). */\n storageKey?: string;\n /** Path on the IdP for the hosted login page. Default `/login`. */\n loginPath?: string;\n /** Path on the IdP for the SSO redirect. Default `/authorize`. */\n authorizePath?: string;\n /** Refresh this many ms before access-token expiry. Default 60s. */\n refreshSkewMs?: number;\n /** Router path SessionStore uses after a failed refresh. Default `/login?expired=1`. */\n expiredPath?: string;\n};\n\nexport type ResolvedAuthClientConfig = {\n authOrigin: string;\n appSlug: string;\n returnUrl: string;\n storageKey: string;\n loginPath: string;\n authorizePath: string;\n refreshSkewMs: number;\n expiredPath: string;\n};\n\nexport function resolveAuthClientConfig(\n config: AuthClientConfig,\n): ResolvedAuthClientConfig {\n const appSlug = config.appSlug.trim();\n const returnUrl = config.returnUrl.trim();\n if (!appSlug) {\n throw new Error(\"provideAuthClient: appSlug is required\");\n }\n if (!returnUrl) {\n throw new Error(\"provideAuthClient: returnUrl is required\");\n }\n return {\n authOrigin: config.authOrigin.replace(/\\/$/, \"\"),\n appSlug,\n returnUrl,\n storageKey: config.storageKey?.trim() || \"auth.session\",\n loginPath: config.loginPath?.trim() || \"/login\",\n authorizePath: config.authorizePath?.trim() || \"/authorize\",\n refreshSkewMs: config.refreshSkewMs ?? ACCESS_REFRESH_SKEW_MS,\n expiredPath: config.expiredPath?.trim() || \"/login?expired=1\",\n };\n}\n","import type { AuthApiResult } from \"./types\";\n\nexport function readErrorCode(body: unknown): string {\n if (!body || typeof body !== \"object\") return \"\";\n const error = (body as { error?: unknown }).error;\n return typeof error === \"string\" ? error : \"\";\n}\n\nexport function isUnauthorized(status: number, body: unknown): boolean {\n if (status === 401) return true;\n return readErrorCode(body) === \"unauthorized\";\n}\n\nexport function jsonHeaders(\n init: RequestInit & { token?: string } = {},\n): Headers {\n const headers = new Headers(init.headers);\n if (init.body && !headers.has(\"content-type\")) {\n headers.set(\"content-type\", \"application/json\");\n }\n if (init.token) headers.set(\"authorization\", `Bearer ${init.token}`);\n return headers;\n}\n\nexport async function parseJsonResponse<T>(\n res: Response,\n): Promise<AuthApiResult<T>> {\n const body = (await res.json().catch(() => ({}))) as T;\n return { status: res.status, body };\n}\n\nexport type AuthFetchFn = (\n input: string,\n init?: RequestInit,\n) => Promise<Response>;\n\nexport async function authJson<T>(\n fetchFn: AuthFetchFn,\n url: string,\n init: RequestInit & { token?: string } = {},\n): Promise<AuthApiResult<T>> {\n const { token, ...rest } = init;\n const res = await fetchFn(url, { ...rest, headers: jsonHeaders(init) });\n return parseJsonResponse<T>(res);\n}\n","export type SessionStorageLike = {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n};\n\nexport function createMemoryStorage(): SessionStorageLike {\n const map = new Map<string, string>();\n return {\n getItem(key) {\n return map.get(key) ?? null;\n },\n setItem(key, value) {\n map.set(key, value);\n },\n removeItem(key) {\n map.delete(key);\n },\n };\n}\n\n/**\n * Prefer localStorage so IdP sessions survive new tabs and browser restarts.\n * Fall back to in-memory storage when localStorage is unavailable (private\n * mode / non-browser); sessionStorage is a last resort if localStorage throws\n * but sessionStorage works.\n */\n/** One-shot: copy auth.session from sessionStorage → localStorage (cutover). */\nfunction migrateSessionKeyToLocal(key = \"auth.session\"): void {\n try {\n if (typeof localStorage === \"undefined\" || typeof sessionStorage === \"undefined\") return;\n if (localStorage.getItem(key)) return;\n const raw = sessionStorage.getItem(key);\n if (!raw) return;\n localStorage.setItem(key, raw);\n sessionStorage.removeItem(key);\n } catch {\n // ignore quota / private mode\n }\n}\n\nexport function defaultSessionStorage(): SessionStorageLike {\n try {\n if (typeof localStorage !== \"undefined\") {\n migrateSessionKeyToLocal();\n return localStorage;\n }\n } catch {\n // private mode / non-browser\n }\n try {\n if (typeof sessionStorage !== \"undefined\") return sessionStorage;\n } catch {\n // private mode / non-browser\n }\n return createMemoryStorage();\n}\n\nexport function readStoredJson<T>(\n storage: SessionStorageLike,\n key: string,\n): T | null {\n try {\n const raw = storage.getItem(key);\n if (!raw) return null;\n return JSON.parse(raw) as T;\n } catch {\n return null;\n }\n}\n","export type AuthUser = {\n id: string;\n email: string;\n};\n\nexport type AppInfo = {\n slug: string;\n name: string;\n active?: boolean;\n allowed_origins?: string[];\n redirect_uris?: string[];\n};\n\nexport type RoleScope = {\n role: string;\n orgnr: string | null;\n};\n\nexport type SessionPayload = {\n access_token: string;\n refresh_token: string;\n token_type?: string;\n expires_in?: number;\n expires_at?: number;\n session_id?: string;\n user: AuthUser;\n app: { slug: string; name: string };\n roles: string[];\n permissions: string[];\n scopes: RoleScope[];\n};\n\nexport type AuthApiResult<T> = {\n status: number;\n body: T;\n};\n\nexport type MagicLinkResponse = {\n ok?: boolean;\n error?: string;\n dev_token?: string;\n dev_code?: string;\n};\n\nexport type InviteRequest = {\n email: string;\n role: string;\n app?: string;\n orgnr?: string;\n return_url?: string;\n};\n\nexport type InviteResponse = {\n ok?: boolean;\n error?: string;\n invite?: {\n id: string;\n email: string;\n role: string;\n app: string;\n orgnr: string | null;\n status?: string;\n expires_at: string;\n };\n dev_token?: string;\n};\n\nexport type TokenErrorBody = {\n error?: string;\n};\n\nexport type RoleRow = {\n slug: string;\n name: string;\n description?: string | null;\n permissions: string[];\n};\n\nexport function isSessionPayload(value: unknown): value is SessionPayload {\n if (!value || typeof value !== \"object\") return false;\n const rec = value as Partial<SessionPayload>;\n return (\n typeof rec.access_token === \"string\" &&\n rec.access_token.length > 0 &&\n typeof rec.refresh_token === \"string\" &&\n !!rec.user &&\n typeof rec.user.id === \"string\" &&\n typeof rec.user.email === \"string\" &&\n !!rec.app &&\n typeof rec.app.slug === \"string\" &&\n Array.isArray(rec.roles) &&\n Array.isArray(rec.permissions)\n );\n}\n","function base64url(bytes: Uint8Array): string {\n let binary = \"\";\n for (const byte of bytes) binary += String.fromCharCode(byte);\n return btoa(binary).replaceAll(\"+\", \"-\").replaceAll(\"/\", \"_\").replace(/=+$/, \"\");\n}\n\n/** RFC 7636 S256 pair: 43-char verifier, base64url(sha256(verifier)) challenge. */\nexport async function createPkcePair(): Promise<{\n verifier: string;\n challenge: string;\n}> {\n const verifier = base64url(crypto.getRandomValues(new Uint8Array(32)));\n const digest = await crypto.subtle.digest(\n \"SHA-256\",\n new TextEncoder().encode(verifier),\n );\n return { verifier, challenge: base64url(new Uint8Array(digest)) };\n}\n","export function joinAuthUrl(origin: string, path: string): string {\n const normalizedPath = path.startsWith(\"/\") ? path : `/${path}`;\n if (!origin) return normalizedPath;\n return `${origin.replace(/\\/$/, \"\")}${normalizedPath}`;\n}\n\nexport function isHttpUrl(value: string): boolean {\n try {\n const url = new URL(value);\n return url.protocol === \"http:\" || url.protocol === \"https:\";\n } catch {\n return false;\n }\n}\n\nexport function resolveReturnUrl(\n requested: string | null | undefined,\n fallback: string,\n): string {\n if (requested && isHttpUrl(requested)) return requested;\n return fallback;\n}\n\nexport function readCallbackCode(searchOrUrl: string): string | null {\n const raw = searchOrUrl.trim();\n if (!raw) return null;\n try {\n if (raw.includes(\"://\") || raw.startsWith(\"/\") || raw.startsWith(\"?\")) {\n const url = raw.includes(\"://\")\n ? new URL(raw)\n : new URL(raw, \"http://auth.local\");\n return url.searchParams.get(\"code\");\n }\n } catch {\n // treat as a bare code below\n }\n if (raw.includes(\"=\")) {\n return new URLSearchParams(raw.startsWith(\"?\") ? raw.slice(1) : raw).get(\n \"code\",\n );\n }\n return raw;\n}\n\n/** `error` on the callback URL, e.g. `login_required` after `prompt=none`. */\nexport function readCallbackError(searchOrUrl: string): string | null {\n const raw = searchOrUrl.trim();\n if (!raw.includes(\"=\")) return null;\n try {\n const url = raw.includes(\"://\") ? new URL(raw) : new URL(raw, \"http://auth.local\");\n return (\n url.searchParams.get(\"error\") ??\n new URLSearchParams(raw.startsWith(\"?\") ? raw.slice(1) : raw).get(\"error\")\n );\n } catch {\n return null;\n }\n}\n\nexport function buildAuthorizeUrl(opts: {\n authOrigin: string;\n appSlug: string;\n returnUrl: string;\n codeChallenge: string;\n authorizePath?: string;\n prompt?: \"none\";\n}): string {\n const url = new URL(\n joinAuthUrl(opts.authOrigin, opts.authorizePath ?? \"/authorize\"),\n \"http://auth.local\",\n );\n url.searchParams.set(\"app\", opts.appSlug);\n url.searchParams.set(\"return_url\", opts.returnUrl);\n url.searchParams.set(\"code_challenge\", opts.codeChallenge);\n url.searchParams.set(\"code_challenge_method\", \"S256\");\n if (opts.prompt) url.searchParams.set(\"prompt\", opts.prompt);\n return opts.authOrigin ? url.toString() : `${url.pathname}${url.search}`;\n}\n\n/** Auth `GET /logout`: ends SSO and every app session it issued. */\nexport function buildLogoutUrl(opts: {\n authOrigin: string;\n appSlug: string;\n returnUrl: string;\n}): string {\n const url = new URL(joinAuthUrl(opts.authOrigin, \"/logout\"), \"http://auth.local\");\n url.searchParams.set(\"app\", opts.appSlug);\n url.searchParams.set(\"return_url\", opts.returnUrl);\n return opts.authOrigin ? url.toString() : `${url.pathname}${url.search}`;\n}\n\nexport function buildLoginUrl(opts: {\n authOrigin: string;\n appSlug: string;\n returnUrl: string;\n loginPath?: string;\n}): string {\n const path = joinAuthUrl(opts.authOrigin, opts.loginPath ?? \"/login\");\n const url = path.includes(\"://\")\n ? new URL(path)\n : new URL(path, \"http://auth.local\");\n url.searchParams.set(\"app\", opts.appSlug);\n url.searchParams.set(\"return_url\", opts.returnUrl);\n if (path.includes(\"://\")) return url.toString();\n return `${url.pathname}${url.search}`;\n}\n","import {\n resolveAuthClientConfig,\n type AuthClientConfig,\n type ResolvedAuthClientConfig,\n} from \"./config\";\nimport {\n authJson,\n isUnauthorized,\n readErrorCode,\n type AuthFetchFn,\n} from \"./http\";\nimport {\n accessTokenExpiresAtMs,\n accessTokenNeedsRefresh,\n withExpiresAt,\n} from \"./session-expiry\";\nimport {\n createMemoryStorage,\n defaultSessionStorage,\n readStoredJson,\n type SessionStorageLike,\n} from \"./storage\";\nimport {\n isSessionPayload,\n type AuthApiResult,\n type InviteRequest,\n type InviteResponse,\n type MagicLinkResponse,\n type SessionPayload,\n type TokenErrorBody,\n} from \"./types\";\nimport { createPkcePair } from \"./pkce\";\nimport {\n buildAuthorizeUrl,\n buildLoginUrl,\n buildLogoutUrl,\n joinAuthUrl,\n readCallbackCode,\n readCallbackError,\n} from \"./urls\";\n\n/** The subset of the Web Locks API the client uses. */\nexport type LockManagerLike = {\n request<T>(name: string, callback: () => Promise<T>): Promise<T>;\n};\n\nexport type AuthClientOptions = {\n storage?: SessionStorageLike;\n /** Holds the PKCE verifier between /authorize and /callback. Default sessionStorage. */\n pkceStorage?: SessionStorageLike;\n fetch?: AuthFetchFn;\n /** Serializes refresh across tabs. Default `navigator.locks` when available. */\n locks?: LockManagerLike | null;\n};\n\nexport type RedirectToLoginOptions = {\n /** `none`: come back with `?error=login_required` instead of showing the login page. */\n prompt?: \"none\";\n assign?: (url: string) => void;\n};\n\n/** Outcome of a refresh_token grant attempt. */\ntype RefreshOutcome = \"refreshed\" | \"invalid\" | \"transient\";\n\nconst INVALID_REFRESH_ERRORS = new Set([\n \"invalid_refresh\",\n \"refresh_expired\",\n \"refresh_token_required\",\n \"invalid_grant\",\n]);\n\nfunction isInvalidRefreshResponse(status: number, body: unknown): boolean {\n if (status === 401) return true;\n if (status !== 400) return false;\n const code = readErrorCode(body);\n return INVALID_REFRESH_ERRORS.has(code);\n}\n\nexport class AuthClient {\n readonly config: ResolvedAuthClientConfig;\n private readonly storage: SessionStorageLike;\n private readonly pkceStorage: SessionStorageLike;\n private readonly fetchFn: AuthFetchFn;\n private readonly locks: LockManagerLike | null;\n private readonly listeners = new Set<(session: SessionPayload | null) => void>();\n private current: SessionPayload | null;\n private refreshInFlight: Promise<RefreshOutcome> | null = null;\n\n constructor(config: AuthClientConfig, options: AuthClientOptions = {}) {\n this.config = resolveAuthClientConfig(config);\n this.storage = options.storage ?? defaultSessionStorage();\n this.pkceStorage = options.pkceStorage ?? defaultPkceStorage();\n this.fetchFn = options.fetch ?? fetch;\n this.locks =\n options.locks === undefined ? defaultLocks() : options.locks;\n this.current = this.loadFromStorage();\n if (typeof window !== \"undefined\") {\n window.addEventListener(\"storage\", (event) => {\n if (event.key === this.config.storageKey || event.key === null) {\n this.reloadFromStorage();\n }\n });\n }\n }\n\n /** Called whenever the session changes, including from another tab. */\n onChange(listener: (session: SessionPayload | null) => void): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n /** Adopt what another tab (or app on this origin) stored. */\n reloadFromStorage(): SessionPayload | null {\n this.current = this.loadFromStorage();\n this.emit();\n return this.current;\n }\n\n private emit(): void {\n for (const listener of this.listeners) listener(this.current);\n }\n\n private get pkceKey(): string {\n return `${this.config.storageKey}.pkce`;\n }\n\n getSession(): SessionPayload | null {\n return this.current;\n }\n\n get token(): string {\n return this.current?.access_token ?? \"\";\n }\n\n get user(): SessionPayload[\"user\"] | null {\n return this.current?.user ?? null;\n }\n\n get app(): SessionPayload[\"app\"] | null {\n return this.current?.app ?? null;\n }\n\n get roles(): string[] {\n return this.current?.roles ?? [];\n }\n\n get permissions(): string[] {\n return this.current?.permissions ?? [];\n }\n\n can(permission: string): boolean {\n return this.permissions.includes(permission);\n }\n\n setSession(session: SessionPayload): SessionPayload {\n const next = withExpiresAt(session);\n this.current = next;\n this.storage.setItem(this.config.storageKey, JSON.stringify(next));\n this.emit();\n return next;\n }\n\n clearSession(): void {\n this.current = null;\n this.storage.removeItem(this.config.storageKey);\n this.emit();\n }\n\n /** Hosted login page without SSO (`app` + `return_url`). */\n loginUrl(): string {\n return buildLoginUrl(this.config);\n }\n\n /**\n * Auth `GET /authorize` with a fresh PKCE pair. Signed in on Auth already:\n * straight back with a code. Otherwise the hosted login page.\n */\n async authorizeUrl(prompt?: \"none\"): Promise<string> {\n const { verifier, challenge } = await createPkcePair();\n this.pkceStorage.setItem(this.pkceKey, verifier);\n return buildAuthorizeUrl({\n authOrigin: this.config.authOrigin,\n appSlug: this.config.appSlug,\n returnUrl: this.config.returnUrl,\n authorizePath: this.config.authorizePath,\n codeChallenge: challenge,\n prompt,\n });\n }\n\n /** Send the browser through Auth `/authorize` (SSO across app domains). */\n async redirectToLogin(\n options: RedirectToLoginOptions | ((url: string) => void) = {},\n ): Promise<void> {\n const { prompt, assign = defaultAssign } =\n typeof options === \"function\" ? { assign: options } : options;\n assign(await this.authorizeUrl(prompt));\n }\n\n /**\n * Log out of every app: ends this app session, then Auth `GET /logout`\n * ends SSO and every app session it issued. `returnUrl` must be allowlisted.\n */\n async logoutEverywhere(\n returnUrl = `${new URL(this.config.returnUrl).origin}/`,\n assign: (url: string) => void = defaultAssign,\n ): Promise<void> {\n await this.logout();\n assign(\n buildLogoutUrl({\n authOrigin: this.config.authOrigin,\n appSlug: this.config.appSlug,\n returnUrl,\n }),\n );\n }\n\n async requestMagicLink(\n email: string,\n intent: \"login\" | \"privat\" = \"login\",\n ): Promise<AuthApiResult<MagicLinkResponse>> {\n return authJson<MagicLinkResponse>(this.fetchFn, this.url(\"/magic-link\"), {\n method: \"POST\",\n body: JSON.stringify({\n email,\n app: this.config.appSlug,\n return_url: this.config.returnUrl,\n intent,\n }),\n });\n }\n\n /**\n * The 6-digit code from the mail `requestMagicLink()` sent, typed in the\n * app's own form. Logs in without leaving the app (no SSO cookie).\n */\n async loginWithCode(\n email: string,\n code: string,\n ): Promise<AuthApiResult<SessionPayload & TokenErrorBody>> {\n const res = await authJson<{ code?: string; error?: string }>(\n this.fetchFn,\n this.url(\"/magic-link/code\"),\n {\n method: \"POST\",\n body: JSON.stringify({ email, app: this.config.appSlug, code }),\n },\n );\n if (res.status !== 200 || !res.body.code) {\n const body = { error: res.body.error ?? \"invalid_code\" };\n return { status: res.status, body: body as SessionPayload & TokenErrorBody };\n }\n return this.exchangeCode(res.body.code);\n }\n\n /** Authenticated `POST /invites`. Auth owns mail + accept. */\n async createInvite(\n input: InviteRequest,\n ): Promise<AuthApiResult<InviteResponse>> {\n return this.request<InviteResponse>(\"/invites\", {\n method: \"POST\",\n body: JSON.stringify({\n email: input.email,\n role: input.role,\n app: input.app ?? this.config.appSlug,\n orgnr: input.orgnr,\n return_url: input.return_url ?? this.config.returnUrl,\n }),\n });\n }\n\n async exchangeCode(\n code: string,\n codeVerifier?: string,\n ): Promise<AuthApiResult<SessionPayload & TokenErrorBody>> {\n const res = await authJson<SessionPayload & TokenErrorBody>(\n this.fetchFn,\n this.url(\"/oauth/token\"),\n {\n method: \"POST\",\n body: JSON.stringify({\n grant_type: \"authorization_code\",\n code,\n app: this.config.appSlug,\n ...(codeVerifier ? { code_verifier: codeVerifier } : {}),\n }),\n },\n );\n if (res.status === 200 && isSessionPayload(res.body)) {\n this.setSession(res.body);\n }\n return res;\n }\n\n /**\n * `/callback?code=…` → tokens. Sends the PKCE verifier from\n * `redirectToLogin()` when there is one (codes from a magic link have none).\n * Throws the callback `error` (e.g. `login_required`) or `missing_code`.\n */\n async handleCallback(codeOrSearch: string): Promise<SessionPayload> {\n const error = readCallbackError(codeOrSearch);\n const verifier = this.pkceStorage.getItem(this.pkceKey) ?? undefined;\n this.pkceStorage.removeItem(this.pkceKey);\n if (error) {\n throw new Error(error);\n }\n const code = readCallbackCode(codeOrSearch);\n if (!code) {\n throw new Error(\"missing_code\");\n }\n const res = await this.exchangeCode(code, verifier);\n if (res.status !== 200 || !isSessionPayload(res.body)) {\n throw new Error(res.body.error ?? \"exchange_failed\");\n }\n return this.current!;\n }\n\n async logout(): Promise<void> {\n const token = this.token;\n if (token) {\n await authJson(this.fetchFn, this.url(\"/logout\"), {\n method: \"POST\",\n token,\n }).catch(() => undefined);\n }\n this.clearSession();\n }\n\n /**\n * Authenticated JSON request with Bearer token, proactive refresh, and a\n * single 401 retry — same shape as the auth console `SessionStore.request`.\n *\n * Clears the stored session only when refresh is known-invalid (expired /\n * revoked refresh token). Network blips and 5xx do not log the user out.\n */\n async request<T>(\n path: string,\n init: RequestInit = {},\n ): Promise<AuthApiResult<T>> {\n const fresh = await this.ensureFreshToken();\n if (!fresh) {\n if (!this.token) {\n return { status: 401, body: { error: \"unauthorized\" } as T };\n }\n // Transient refresh failure — keep session; caller can retry later.\n return { status: 503, body: { error: \"refresh_unavailable\" } as T };\n }\n const first = await authJson<T>(this.fetchFn, this.url(path), {\n ...init,\n token: this.token,\n });\n if (!isUnauthorized(first.status, first.body)) return first;\n const renewed = await this.refreshSessionDetailed();\n if (renewed === \"refreshed\") {\n return authJson<T>(this.fetchFn, this.url(path), {\n ...init,\n token: this.token,\n });\n }\n if (renewed === \"invalid\") {\n this.clearSession();\n }\n return first;\n }\n\n async ensureFreshToken(): Promise<boolean> {\n if (!this.token) return false;\n const expiresAt = accessTokenExpiresAtMs(this.current ?? {});\n if (\n !accessTokenNeedsRefresh(\n expiresAt,\n Date.now(),\n this.config.refreshSkewMs,\n )\n ) {\n return true;\n }\n const outcome = await this.refreshSessionDetailed();\n if (outcome === \"refreshed\") return true;\n if (outcome === \"invalid\") {\n this.clearSession();\n return false;\n }\n // Transient: keep stored session so the caller can retry.\n return false;\n }\n\n async refreshSession(): Promise<boolean> {\n const outcome = await this.refreshSessionDetailed();\n if (outcome === \"refreshed\") return true;\n if (outcome === \"invalid\") {\n this.clearSession();\n }\n return false;\n }\n\n private async refreshSessionDetailed(): Promise<RefreshOutcome> {\n if (this.refreshInFlight) return this.refreshInFlight;\n const run = () => this.doRefresh();\n this.refreshInFlight = (\n this.locks\n ? this.locks.request(`${this.config.storageKey}:refresh`, run)\n : run()\n ).finally(() => {\n this.refreshInFlight = null;\n });\n return this.refreshInFlight;\n }\n\n /**\n * Auth rotates the refresh token on every use and the old one dies at once,\n * so every tab on this origin must use the newest stored token: adopt what\n * another tab stored before refreshing, and after an `invalid_refresh`.\n */\n private async doRefresh(): Promise<RefreshOutcome> {\n const stored = this.loadFromStorage();\n if (stored && stored.refresh_token !== this.current?.refresh_token) {\n this.current = stored;\n this.emit();\n const expiresAt = accessTokenExpiresAtMs(stored);\n if (!accessTokenNeedsRefresh(expiresAt, Date.now(), this.config.refreshSkewMs)) {\n return \"refreshed\";\n }\n }\n const outcome = await this.postRefresh();\n if (outcome !== \"invalid\") return outcome;\n const latest = this.loadFromStorage();\n if (latest && latest.refresh_token !== this.current?.refresh_token) {\n this.current = latest;\n this.emit();\n return \"refreshed\";\n }\n return \"invalid\";\n }\n\n private async postRefresh(): Promise<RefreshOutcome> {\n const refreshToken = this.current?.refresh_token;\n if (!refreshToken) return \"invalid\";\n try {\n const res = await authJson<SessionPayload & TokenErrorBody>(\n this.fetchFn,\n this.url(\"/oauth/token\"),\n {\n method: \"POST\",\n body: JSON.stringify({\n grant_type: \"refresh_token\",\n refresh_token: refreshToken,\n }),\n },\n );\n if (res.status === 200 && isSessionPayload(res.body)) {\n this.setSession(res.body);\n return \"refreshed\";\n }\n if (isInvalidRefreshResponse(res.status, res.body)) {\n return \"invalid\";\n }\n return \"transient\";\n } catch {\n // Network error / aborted fetch — do not clear session.\n return \"transient\";\n }\n }\n\n private url(path: string): string {\n return joinAuthUrl(this.config.authOrigin, path);\n }\n\n private loadFromStorage(): SessionPayload | null {\n const raw = readStoredJson<SessionPayload>(\n this.storage,\n this.config.storageKey,\n );\n if (!raw || !isSessionPayload(raw)) return null;\n return withExpiresAt(raw);\n }\n}\n\nexport function createAuthClient(\n config: AuthClientConfig,\n options?: AuthClientOptions,\n): AuthClient {\n return new AuthClient(config, options);\n}\n\nfunction defaultPkceStorage(): SessionStorageLike {\n try {\n if (typeof sessionStorage !== \"undefined\") return sessionStorage;\n } catch {\n // private mode / non-browser\n }\n return createMemoryStorage();\n}\n\nfunction defaultLocks(): LockManagerLike | null {\n const locks = (globalThis as { navigator?: { locks?: LockManagerLike } })\n .navigator?.locks;\n return locks && typeof locks.request === \"function\" ? locks : null;\n}\n\nfunction defaultAssign(url: string): void {\n if (typeof location !== \"undefined\") {\n location.assign(url);\n return;\n }\n throw new Error(`redirectToLogin: no location (${url})`);\n}\n","import { InjectionToken } from \"@angular/core\";\nimport type { ResolvedAuthClientConfig } from \"./config\";\n\nexport const AUTH_CLIENT_CONFIG = new InjectionToken<ResolvedAuthClientConfig>(\n \"AUTH_CLIENT_CONFIG\",\n);\n","import { DestroyRef, Injectable, computed, inject, signal } from \"@angular/core\";\nimport { Router } from \"@angular/router\";\nimport { AuthClient, type RedirectToLoginOptions } from \"./client\";\nimport { AUTH_CLIENT_CONFIG } from \"./tokens\";\nimport type {\n AuthApiResult,\n InviteRequest,\n InviteResponse,\n MagicLinkResponse,\n SessionPayload,\n TokenErrorBody,\n} from \"./types\";\n\n@Injectable()\nexport class SessionStore {\n private readonly client = inject(AuthClient);\n private readonly router = inject(Router);\n private readonly config = inject(AUTH_CLIENT_CONFIG);\n readonly session = signal<SessionPayload | null>(this.client.getSession());\n readonly token = computed(() => this.session()?.access_token ?? \"\");\n readonly user = computed(() => this.session()?.user ?? null);\n readonly app = computed(() => this.session()?.app ?? null);\n readonly roles = computed(() => this.session()?.roles ?? []);\n readonly permissions = computed(() => this.session()?.permissions ?? []);\n\n constructor() {\n const stop = this.client.onChange((session) => this.session.set(session));\n inject(DestroyRef).onDestroy(stop);\n }\n\n can(permission: string): boolean {\n return this.permissions().includes(permission);\n }\n\n set(session: SessionPayload): void {\n this.session.set(this.client.setSession(session));\n }\n\n clear(): void {\n this.client.clearSession();\n this.session.set(null);\n }\n\n expireToLogin(): void {\n this.clear();\n void this.router.navigateByUrl(this.config.expiredPath ?? \"/login?expired=1\");\n }\n\n async logout(): Promise<void> {\n await this.client.logout();\n this.session.set(null);\n }\n\n loginUrl(): string {\n return this.client.loginUrl();\n }\n\n /** Auth `/authorize`: SSO if signed in on Auth, else the hosted login page. */\n redirectToLogin(options: RedirectToLoginOptions = {}): Promise<void> {\n return this.client.redirectToLogin(options);\n }\n\n /** Ends SSO and every app session it issued, then returns to `returnUrl`. */\n logoutEverywhere(returnUrl?: string): Promise<void> {\n return this.client.logoutEverywhere(returnUrl);\n }\n\n async requestMagicLink(\n email: string,\n intent: \"login\" | \"privat\" = \"login\",\n ): Promise<AuthApiResult<MagicLinkResponse>> {\n return this.client.requestMagicLink(email, intent);\n }\n\n /** The 6-digit code from that mail, typed in the app's own form. */\n async loginWithCode(\n email: string,\n code: string,\n ): Promise<AuthApiResult<SessionPayload & TokenErrorBody>> {\n const result = await this.client.loginWithCode(email, code);\n this.session.set(this.client.getSession());\n return result;\n }\n\n async createInvite(\n input: InviteRequest,\n ): Promise<AuthApiResult<InviteResponse>> {\n const result = await this.client.createInvite(input);\n this.session.set(this.client.getSession());\n return result;\n }\n\n async handleCallback(codeOrSearch: string): Promise<SessionPayload> {\n const session = await this.client.handleCallback(codeOrSearch);\n this.session.set(session);\n return session;\n }\n\n async request<T>(\n path: string,\n init: RequestInit = {},\n ): Promise<AuthApiResult<T>> {\n const result = await this.client.request<T>(path, init);\n this.session.set(this.client.getSession());\n if (result.status === 401 && !this.client.token) {\n this.expireToLogin();\n }\n return result;\n }\n\n async ensureFreshToken(): Promise<boolean> {\n const ok = await this.client.ensureFreshToken();\n this.session.set(this.client.getSession());\n return ok;\n }\n\n async refreshSession(): Promise<boolean> {\n const ok = await this.client.refreshSession();\n this.session.set(this.client.getSession());\n return ok;\n }\n}\n","import { makeEnvironmentProviders, type EnvironmentProviders } from \"@angular/core\";\nimport { AuthClient } from \"./client\";\nimport { resolveAuthClientConfig, type AuthClientConfig } from \"./config\";\nimport { SessionStore } from \"./session\";\nimport { defaultSessionStorage } from \"./storage\";\nimport { AUTH_CLIENT_CONFIG } from \"./tokens\";\n\nexport function provideAuthClient(\n config: AuthClientConfig,\n): EnvironmentProviders {\n const resolved = resolveAuthClientConfig(config);\n return makeEnvironmentProviders([\n { provide: AUTH_CLIENT_CONFIG, useValue: resolved },\n {\n provide: AuthClient,\n useFactory: () =>\n new AuthClient(resolved, { storage: defaultSessionStorage() }),\n },\n SessionStore,\n ]);\n}\n","import { inject } from \"@angular/core\";\nimport { type CanActivateFn, Router } from \"@angular/router\";\nimport { SessionStore } from \"./session\";\n\nexport function requireAuthSession(loginUrl = \"/login\"): CanActivateFn {\n return () => {\n const session = inject(SessionStore);\n const router = inject(Router);\n return session.token() ? true : router.parseUrl(loginUrl);\n };\n}\n","import { Component, input, output } from \"@angular/core\";\n\n/**\n * Placeholder for a shared grant-access dialog.\n * Product apps should keep their own UI in v1 — this is not blocking.\n */\n@Component({\n selector: \"jg-auth-grant-dialog\",\n template: `\n @if (open()) {\n <p class=\"jg-auth-grant-dialog-stub\">\n Grant dialog is not implemented in @byggnode/auth-client v1.\n </p>\n }\n `,\n})\nexport class GrantDialog {\n readonly open = input(false);\n readonly closed = output<void>();\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@byggnode/auth-client",
3
- "version": "0.0.1",
3
+ "version": "0.1.0",
4
4
  "description": "Thin Angular client for the Byggnode central auth IdP.",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,
@@ -4,7 +4,7 @@ import * as _byggnode_auth_client from '@byggnode/auth-client';
4
4
  import { CanActivateFn } from '@angular/router';
5
5
 
6
6
  type AuthClientConfig = {
7
- /** IdP origin, e.g. `https://auth.handverksradet.no`. Empty string = same origin. */
7
+ /** IdP origin, e.g. `https://auth.byggnode.no`. Empty string = same origin. */
8
8
  authOrigin: string;
9
9
  /** Registered app slug sent on magic-link / login redirect / token exchange. */
10
10
  appSlug: string;
@@ -14,6 +14,8 @@ type AuthClientConfig = {
14
14
  storageKey?: string;
15
15
  /** Path on the IdP for the hosted login page. Default `/login`. */
16
16
  loginPath?: string;
17
+ /** Path on the IdP for the SSO redirect. Default `/authorize`. */
18
+ authorizePath?: string;
17
19
  /** Refresh this many ms before access-token expiry. Default 60s. */
18
20
  refreshSkewMs?: number;
19
21
  /** Router path SessionStore uses after a failed refresh. Default `/login?expired=1`. */
@@ -25,6 +27,7 @@ type ResolvedAuthClientConfig = {
25
27
  returnUrl: string;
26
28
  storageKey: string;
27
29
  loginPath: string;
30
+ authorizePath: string;
28
31
  refreshSkewMs: number;
29
32
  expiredPath: string;
30
33
  };
@@ -73,6 +76,7 @@ type MagicLinkResponse = {
73
76
  ok?: boolean;
74
77
  error?: string;
75
78
  dev_token?: string;
79
+ dev_code?: string;
76
80
  };
77
81
  type InviteRequest = {
78
82
  email: string;
@@ -106,36 +110,6 @@ type RoleRow = {
106
110
  };
107
111
  declare function isSessionPayload(value: unknown): value is SessionPayload;
108
112
 
109
- declare class SessionStore {
110
- private readonly client;
111
- private readonly router;
112
- private readonly config;
113
- readonly session: _angular_core.WritableSignal<SessionPayload | null>;
114
- readonly token: _angular_core.Signal<string>;
115
- readonly user: _angular_core.Signal<_byggnode_auth_client.AuthUser | null>;
116
- readonly app: _angular_core.Signal<{
117
- slug: string;
118
- name: string;
119
- } | null>;
120
- readonly roles: _angular_core.Signal<string[]>;
121
- readonly permissions: _angular_core.Signal<string[]>;
122
- can(permission: string): boolean;
123
- set(session: SessionPayload): void;
124
- clear(): void;
125
- expireToLogin(): void;
126
- logout(): Promise<void>;
127
- loginUrl(): string;
128
- redirectToLogin(): void;
129
- requestMagicLink(email: string, intent?: "login" | "privat"): Promise<AuthApiResult<MagicLinkResponse>>;
130
- createInvite(input: InviteRequest): Promise<AuthApiResult<InviteResponse>>;
131
- handleCallback(codeOrSearch: string): Promise<SessionPayload>;
132
- request<T>(path: string, init?: RequestInit): Promise<AuthApiResult<T>>;
133
- ensureFreshToken(): Promise<boolean>;
134
- refreshSession(): Promise<boolean>;
135
- static ɵfac: _angular_core.ɵɵFactoryDeclaration<SessionStore, never>;
136
- static ɵprov: _angular_core.ɵɵInjectableDeclaration<any>;
137
- }
138
-
139
113
  declare function readErrorCode(body: unknown): string;
140
114
  declare function isUnauthorized(status: number, body: unknown): boolean;
141
115
  declare function jsonHeaders(init?: RequestInit & {
@@ -156,17 +130,39 @@ declare function createMemoryStorage(): SessionStorageLike;
156
130
  declare function defaultSessionStorage(): SessionStorageLike;
157
131
  declare function readStoredJson<T>(storage: SessionStorageLike, key: string): T | null;
158
132
 
133
+ /** The subset of the Web Locks API the client uses. */
134
+ type LockManagerLike = {
135
+ request<T>(name: string, callback: () => Promise<T>): Promise<T>;
136
+ };
159
137
  type AuthClientOptions = {
160
138
  storage?: SessionStorageLike;
139
+ /** Holds the PKCE verifier between /authorize and /callback. Default sessionStorage. */
140
+ pkceStorage?: SessionStorageLike;
161
141
  fetch?: AuthFetchFn;
142
+ /** Serializes refresh across tabs. Default `navigator.locks` when available. */
143
+ locks?: LockManagerLike | null;
144
+ };
145
+ type RedirectToLoginOptions = {
146
+ /** `none`: come back with `?error=login_required` instead of showing the login page. */
147
+ prompt?: "none";
148
+ assign?: (url: string) => void;
162
149
  };
163
150
  declare class AuthClient {
164
151
  readonly config: ResolvedAuthClientConfig;
165
152
  private readonly storage;
153
+ private readonly pkceStorage;
166
154
  private readonly fetchFn;
155
+ private readonly locks;
156
+ private readonly listeners;
167
157
  private current;
168
158
  private refreshInFlight;
169
159
  constructor(config: AuthClientConfig, options?: AuthClientOptions);
160
+ /** Called whenever the session changes, including from another tab. */
161
+ onChange(listener: (session: SessionPayload | null) => void): () => void;
162
+ /** Adopt what another tab (or app on this origin) stored. */
163
+ reloadFromStorage(): SessionPayload | null;
164
+ private emit;
165
+ private get pkceKey();
170
166
  getSession(): SessionPayload | null;
171
167
  get token(): string;
172
168
  get user(): SessionPayload["user"] | null;
@@ -176,13 +172,34 @@ declare class AuthClient {
176
172
  can(permission: string): boolean;
177
173
  setSession(session: SessionPayload): SessionPayload;
178
174
  clearSession(): void;
175
+ /** Hosted login page without SSO (`app` + `return_url`). */
179
176
  loginUrl(): string;
180
- /** Redirect the browser to the IdP login page (`app` + `return_url`). */
181
- redirectToLogin(assign?: (url: string) => void): void;
177
+ /**
178
+ * Auth `GET /authorize` with a fresh PKCE pair. Signed in on Auth already:
179
+ * straight back with a code. Otherwise the hosted login page.
180
+ */
181
+ authorizeUrl(prompt?: "none"): Promise<string>;
182
+ /** Send the browser through Auth `/authorize` (SSO across app domains). */
183
+ redirectToLogin(options?: RedirectToLoginOptions | ((url: string) => void)): Promise<void>;
184
+ /**
185
+ * Log out of every app: ends this app session, then Auth `GET /logout`
186
+ * ends SSO and every app session it issued. `returnUrl` must be allowlisted.
187
+ */
188
+ logoutEverywhere(returnUrl?: string, assign?: (url: string) => void): Promise<void>;
182
189
  requestMagicLink(email: string, intent?: "login" | "privat"): Promise<AuthApiResult<MagicLinkResponse>>;
190
+ /**
191
+ * The 6-digit code from the mail `requestMagicLink()` sent, typed in the
192
+ * app's own form. Logs in without leaving the app (no SSO cookie).
193
+ */
194
+ loginWithCode(email: string, code: string): Promise<AuthApiResult<SessionPayload & TokenErrorBody>>;
183
195
  /** Authenticated `POST /invites`. Auth owns mail + accept. */
184
196
  createInvite(input: InviteRequest): Promise<AuthApiResult<InviteResponse>>;
185
- exchangeCode(code: string): Promise<AuthApiResult<SessionPayload & TokenErrorBody>>;
197
+ exchangeCode(code: string, codeVerifier?: string): Promise<AuthApiResult<SessionPayload & TokenErrorBody>>;
198
+ /**
199
+ * `/callback?code=…` → tokens. Sends the PKCE verifier from
200
+ * `redirectToLogin()` when there is one (codes from a magic link have none).
201
+ * Throws the callback `error` (e.g. `login_required`) or `missing_code`.
202
+ */
186
203
  handleCallback(codeOrSearch: string): Promise<SessionPayload>;
187
204
  logout(): Promise<void>;
188
205
  /**
@@ -196,12 +213,60 @@ declare class AuthClient {
196
213
  ensureFreshToken(): Promise<boolean>;
197
214
  refreshSession(): Promise<boolean>;
198
215
  private refreshSessionDetailed;
216
+ /**
217
+ * Auth rotates the refresh token on every use and the old one dies at once,
218
+ * so every tab on this origin must use the newest stored token: adopt what
219
+ * another tab stored before refreshing, and after an `invalid_refresh`.
220
+ */
199
221
  private doRefresh;
222
+ private postRefresh;
200
223
  private url;
201
224
  private loadFromStorage;
202
225
  }
203
226
  declare function createAuthClient(config: AuthClientConfig, options?: AuthClientOptions): AuthClient;
204
227
 
228
+ declare class SessionStore {
229
+ private readonly client;
230
+ private readonly router;
231
+ private readonly config;
232
+ readonly session: _angular_core.WritableSignal<SessionPayload | null>;
233
+ readonly token: _angular_core.Signal<string>;
234
+ readonly user: _angular_core.Signal<_byggnode_auth_client.AuthUser | null>;
235
+ readonly app: _angular_core.Signal<{
236
+ slug: string;
237
+ name: string;
238
+ } | null>;
239
+ readonly roles: _angular_core.Signal<string[]>;
240
+ readonly permissions: _angular_core.Signal<string[]>;
241
+ constructor();
242
+ can(permission: string): boolean;
243
+ set(session: SessionPayload): void;
244
+ clear(): void;
245
+ expireToLogin(): void;
246
+ logout(): Promise<void>;
247
+ loginUrl(): string;
248
+ /** Auth `/authorize`: SSO if signed in on Auth, else the hosted login page. */
249
+ redirectToLogin(options?: RedirectToLoginOptions): Promise<void>;
250
+ /** Ends SSO and every app session it issued, then returns to `returnUrl`. */
251
+ logoutEverywhere(returnUrl?: string): Promise<void>;
252
+ requestMagicLink(email: string, intent?: "login" | "privat"): Promise<AuthApiResult<MagicLinkResponse>>;
253
+ /** The 6-digit code from that mail, typed in the app's own form. */
254
+ loginWithCode(email: string, code: string): Promise<AuthApiResult<SessionPayload & TokenErrorBody>>;
255
+ createInvite(input: InviteRequest): Promise<AuthApiResult<InviteResponse>>;
256
+ handleCallback(codeOrSearch: string): Promise<SessionPayload>;
257
+ request<T>(path: string, init?: RequestInit): Promise<AuthApiResult<T>>;
258
+ ensureFreshToken(): Promise<boolean>;
259
+ refreshSession(): Promise<boolean>;
260
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<SessionStore, never>;
261
+ static ɵprov: _angular_core.ɵɵInjectableDeclaration<any>;
262
+ }
263
+
264
+ /** RFC 7636 S256 pair: 43-char verifier, base64url(sha256(verifier)) challenge. */
265
+ declare function createPkcePair(): Promise<{
266
+ verifier: string;
267
+ challenge: string;
268
+ }>;
269
+
205
270
  declare function requireAuthSession(loginUrl?: string): CanActivateFn;
206
271
 
207
272
  /**
@@ -234,6 +299,22 @@ declare function joinAuthUrl(origin: string, path: string): string;
234
299
  declare function isHttpUrl(value: string): boolean;
235
300
  declare function resolveReturnUrl(requested: string | null | undefined, fallback: string): string;
236
301
  declare function readCallbackCode(searchOrUrl: string): string | null;
302
+ /** `error` on the callback URL, e.g. `login_required` after `prompt=none`. */
303
+ declare function readCallbackError(searchOrUrl: string): string | null;
304
+ declare function buildAuthorizeUrl(opts: {
305
+ authOrigin: string;
306
+ appSlug: string;
307
+ returnUrl: string;
308
+ codeChallenge: string;
309
+ authorizePath?: string;
310
+ prompt?: "none";
311
+ }): string;
312
+ /** Auth `GET /logout`: ends SSO and every app session it issued. */
313
+ declare function buildLogoutUrl(opts: {
314
+ authOrigin: string;
315
+ appSlug: string;
316
+ returnUrl: string;
317
+ }): string;
237
318
  declare function buildLoginUrl(opts: {
238
319
  authOrigin: string;
239
320
  appSlug: string;
@@ -241,5 +322,5 @@ declare function buildLoginUrl(opts: {
241
322
  loginPath?: string;
242
323
  }): string;
243
324
 
244
- export { ACCESS_REFRESH_SKEW_MS, AUTH_CLIENT_CONFIG, AuthClient, GrantDialog, SessionStore, accessTokenExpiresAtMs, accessTokenNeedsRefresh, authJson, buildLoginUrl, createAuthClient, createMemoryStorage, defaultSessionStorage, isHttpUrl, isSessionPayload, isUnauthorized, joinAuthUrl, jsonHeaders, jwtExpMs, parseJsonResponse, provideAuthClient, readCallbackCode, readErrorCode, readStoredJson, requireAuthSession, resolveAuthClientConfig, resolveReturnUrl, withExpiresAt };
245
- export type { AppInfo, AuthApiResult, AuthClientConfig, AuthFetchFn, AuthUser, InviteRequest, InviteResponse, MagicLinkResponse, ResolvedAuthClientConfig, RoleRow, RoleScope, SessionPayload, SessionStorageLike, TokenErrorBody };
325
+ export { ACCESS_REFRESH_SKEW_MS, AUTH_CLIENT_CONFIG, AuthClient, GrantDialog, SessionStore, accessTokenExpiresAtMs, accessTokenNeedsRefresh, authJson, buildAuthorizeUrl, buildLoginUrl, buildLogoutUrl, createAuthClient, createMemoryStorage, createPkcePair, defaultSessionStorage, isHttpUrl, isSessionPayload, isUnauthorized, joinAuthUrl, jsonHeaders, jwtExpMs, parseJsonResponse, provideAuthClient, readCallbackCode, readCallbackError, readErrorCode, readStoredJson, requireAuthSession, resolveAuthClientConfig, resolveReturnUrl, withExpiresAt };
326
+ export type { AppInfo, AuthApiResult, AuthClientConfig, AuthClientOptions, AuthFetchFn, AuthUser, InviteRequest, InviteResponse, LockManagerLike, MagicLinkResponse, RedirectToLoginOptions, ResolvedAuthClientConfig, RoleRow, RoleScope, SessionPayload, SessionStorageLike, TokenErrorBody };