@lenne.tech/ng-base 22.0.5 → 22.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/CLAUDE.md CHANGED
@@ -28,6 +28,11 @@ relatively. Changes to the vendored core belong upstream in `lenneTech/ng-base`.
28
28
 
29
29
  - Auth goes through `LtAuthService` only. Never send a plaintext password: the service hashes every credential
30
30
  (SHA-256) itself, so pass the raw form value once and never hash it yourself as well.
31
+ - `lt-jwt-token` holds a JWT or nothing. After a passkey or 2FA sign-in never store a token yourself:
32
+ `authenticateWithPasskey()` returns `{ success, error?, user? }`, and `twoFactor.verifyTotp()` / `verifyBackupCode()`
33
+ set the session and return no session token.
34
+ - SSR backend calls are anonymous (also `fetchWithAuth`): load user data in the browser (`afterNextRender()`, or a
35
+ resource that only starts when `isPlatformBrowser(…)`), render public data on the server, never decide access there.
31
36
  - `lt-auth-state` is a UI cache. Guards (`ltAuthGuard`, `ltRoleGuard`) gate UI only; the backend authorises every
32
37
  request. Do not build security decisions on `isAdmin()`.
33
38
  - Redirect/callback URLs for auth mails are absolute app URLs from `NG_PUBLIC_SITE_URL` (`LtAuthService.absoluteAppUrl`),
package/README.md CHANGED
@@ -153,6 +153,31 @@ root route `canActivateChild: [ltSystemSetupGuard]`. Import the guards, `ltAuthI
153
153
  no Better Auth, 2FA or passkey code. `LtAuthService` (`/auth`) shares the same state and belongs on the pages that sign
154
154
  in, register, verify 2FA or manage passkeys. `/auth` re-exports everything of `/auth/provide`, so either import works.
155
155
 
156
+ `lt-jwt-token` holds a JWT or nothing. After 2FA or a passkey sign-in never store a token yourself: the service puts the
157
+ session in place, `authenticateWithPasskey()` returns `{ success, error?, user? }`, and `twoFactor.verifyTotp()` /
158
+ `verifyBackupCode()` drop Better Auth's opaque session token from their answer (a JWT of `cookies: false` stays).
159
+
160
+ ## Server-side rendering: backend calls are anonymous
161
+
162
+ During SSR every API call of ng-base goes out **without** the user's session: `LT_FETCH` / `fetchWithAuth`,
163
+ `ltAuthInterceptor`, the AI services and offline sync. The server takes the auth state from the `lt-auth-state` cookie,
164
+ a display cache for UI gating and no proof of a session, and never reads `lt-jwt-token`; Node's `fetch` does not forward
165
+ the browser's cookies. The session cookie `iam.session_token` belongs to the API host anyway (host-only unless
166
+ nest-server's `crossSubDomainCookies` is set), so the app server usually never receives it.
167
+
168
+ For pages that show user data:
169
+
170
+ - **Load it in the browser**, for example in `afterNextRender()` or a resource that only starts when
171
+ `isPlatformBrowser(…)`, with a skeleton for the first paint. `resource()` / `httpResource()` run during SSR too.
172
+ - **Render only public data on the server**, and never decide access there: a guard that reads `lt-auth-state` decides
173
+ what to render, the API decides what the user may see.
174
+ - If a page truly needs user data in the SSR HTML, that needs a route of the app's own server that forwards the
175
+ request's session to the API, set up per project together with a shared cookie domain. ng-base does not do it.
176
+
177
+ Unlike nuxt-extensions, `fetchWithAuth` is anonymous during SSR too (nuxt-extensions sends the request's `lt-jwt-token`
178
+ as bearer there): no SSR output depends on one user's token, and a cached route can never serve one user's data to the
179
+ next.
180
+
156
181
  ## Texts (de/en)
157
182
 
158
183
  Library texts are German and English, chosen by the browser language, `LtI18nService.setLocale()` or an app i18n
@@ -221,19 +246,23 @@ Differences to bug.lt 1.6.9:
221
246
  has no subtree error capture): the app's `ErrorHandler` is wrapped (it keeps handling every error) and
222
247
  `LtBugReportService.reportError()` reports by hand; `LtBugReportService.open()` replaces `useBugReport().openModal()`.
223
248
 
224
- ## Differences to nuxt-extensions 1.25.0
249
+ ## Differences to nuxt-extensions 4.0.1
250
+
251
+ ng-base follows nuxt-extensions release for release (last compared: 1.26.0 to 4.0.1). Behaviour that is the same in both
252
+ (2FA and passkey sign-in never expose the session token, one auth fetch with JWT renewal and `basePath`, file URLs on the
253
+ API with `GET /files/id/:id`, TUS on `/tus`, `enableAdmin` off with a warning, error translations that never hold back the
254
+ start, `includeDisabled` for AI prompts) is not listed. What differs on purpose:
225
255
 
226
256
  - Composables with per-call state are `inject…()` factories bound to the injection context (`injectLtTusUpload()`,
227
257
  `injectLtAiChat()`, `injectLtOfflineSync()`); shared state lives in root services (`LtAuthService`, `LtAi…Service`).
228
- - Bugs not ported: the user is set after 2FA verification; one shared auth fetch (`LT_FETCH`) refreshes a stale JWT for
229
- AI, SSE and sync too; file URLs resolve against the API; the TUS endpoint defaults to `/tus`; the missing
230
- `passkeyAlreadyRegistered` text exists; transitions use Angular `animate.enter`/`animate.leave` classes.
258
+ - `fetchWithAuth` is anonymous during SSR too (see "Server-side rendering: backend calls are anonymous").
259
+ - Transitions use Angular `animate.enter` / `animate.leave` classes with CSS variables for the durations, no Tailwind
260
+ duration utilities.
231
261
  - nest-server's real JWT mode (`cookies: false`, env `NSC__COOKIES=false`) works end to end: a JWT in a sign-in, 2FA or
232
262
  passkey answer puts the store into JWT mode, session checks keep the mode (a bearer-proven session never falls back to
233
263
  cookie mode), and a JWT expiring within 60 s is refreshed with itself as bearer before it is sent (nest-server's JWTs
234
264
  live 15 minutes and there is no cookie to mint a new one). An opaque Better Auth session token is never stored. Before
235
265
  nest-server 12.7.0 the browser must reach the API same-origin in this mode (`NG_PUBLIC_API_PROXY=true`).
236
- - The Better Auth admin plugin is off by default (`enableAdmin`), nest-server does not serve it.
237
266
  - Passkeys work without `@better-auth/passkey` (WebAuthn calls go straight to `/iam/passkey/*`); the plugin can still be
238
267
  passed via `provideLtAuth({ plugins })`.
239
268
  - Pre-hydration input is preserved (`provideLtNgBase({ preHydrationInput })`, on by default): the first change
@@ -242,8 +271,6 @@ Differences to bug.lt 1.6.9:
242
271
  HTML that blocks the native submit of forms without `action` before hydration (no password in a GET URL).
243
272
  - Not ported: the form-label association repair (a Nuxt UI/reka-ui id divergence that Angular's id handling does not
244
273
  have).
245
- - File URLs use nest-server 12.5's real routes (`/files/id/:id`, `/files/info/:id`); nuxt-extensions built
246
- `/files/:id` and `/files/download/:id`, which answer 404.
247
274
 
248
275
  ## Vendor mode
249
276
 
@@ -1,7 +1,7 @@
1
1
  import * as i0 from "@angular/core";
2
2
  import { InjectionToken, Injector, PLATFORM_ID, Service, TransferState, computed, inject, makeEnvironmentProviders, makeStateKey, provideAppInitializer, signal } from "@angular/core";
3
3
  import { Router } from "@angular/router";
4
- import { LT_FETCH, LT_RUNTIME_CONFIG, LtApiUrlService, LtCookieService, isLtAbsoluteUrl, ltCredentialsFetch, ltSha256, mergeLtFeatureOptions } from "@lenne.tech/ng-base";
4
+ import { LT_FETCH, LT_RUNTIME_CONFIG, LtApiUrlService, LtCookieService, isLtAbsoluteUrl, ltCredentialsFetch, ltSha256, mergeLtFeatureOptions, warnLtOnce } from "@lenne.tech/ng-base";
5
5
  import { DOCUMENT, isPlatformServer } from "@angular/common";
6
6
  import { HttpErrorResponse } from "@angular/common/http";
7
7
  import { catchError, from, switchMap, throwError } from "rxjs";
@@ -657,6 +657,7 @@ var LtAuthStore = class LtAuthStore {
657
657
  if (body && typeof body === "object" && !Array.isArray(body)) {
658
658
  this.featuresState.set(body);
659
659
  this.featuresLoaded = true;
660
+ if (this.options.enableAdmin && body["admin"] !== true) warnLtOnce("admin-plugin-unserved", "[@lenne.tech/ng-base] provideLtAuth({ enableAdmin: true }) is set, but the API reports no admin plugin (no `admin` in /iam/features): admin.* calls answer 404, and ng-base does not hash the passwords of admin.createUser / admin.setUserPassword. Switch enableAdmin off unless the backend serves the Better Auth admin routes.");
660
661
  if (this.isServer) this.transferState.set(LT_AUTH_FEATURES_STATE_KEY, body);
661
662
  }
662
663
  }
@@ -940,6 +941,6 @@ function provideLtAuth(options = {}) {
940
941
  })
941
942
  ]);
942
943
  }
943
- export { LT_AUTH_COOKIE_MAX_AGE, LT_AUTH_COOKIE_PATHS, LT_AUTH_DEFAULTS, LT_AUTH_ENDPOINT_FRAGMENTS, LT_AUTH_FEATURES_STATE_KEY, LT_AUTH_INPUT_OPTIONS, LT_AUTH_OPTIONS, LT_AUTH_PUBLIC_PATHS, LT_AUTH_SIGN_OUT_HOOKS, LT_AUTH_STATE_COOKIE_DEFAULT, LT_JWT_TOKEN_COOKIE_DEFAULT, LT_SYSTEM_SETUP_STATE_KEY, LT_SYSTEM_SETUP_TOKEN_HEADER, LT_VALIDATE_SESSION_TIMEOUT_MS, LtAuthStateService, LtAuthStore, LtSystemSetupService, LtUnauthorizedHandler, hasLtRole, isLtApiRequest, isLtAuthEndpoint, ltAuthGuard, ltAuthInterceptor, ltGuestGuard, ltRoleGuard, ltSystemSetupGuard, provideLtAuth, readLtAuthAnswer, resolveLtAuthCookieNames, resolveLtAuthState, resolveLtCookiePrefix };
944
+ export { LT_AUTH_COOKIE_MAX_AGE, LT_AUTH_COOKIE_PATHS, LT_AUTH_DEFAULTS, LT_AUTH_ENDPOINT_FRAGMENTS, LT_AUTH_FEATURES_STATE_KEY, LT_AUTH_INPUT_OPTIONS, LT_AUTH_OPTIONS, LT_AUTH_PUBLIC_PATHS, LT_AUTH_SIGN_OUT_HOOKS, LT_AUTH_STATE_COOKIE_DEFAULT, LT_JWT_TOKEN_COOKIE_DEFAULT, LT_SYSTEM_SETUP_STATE_KEY, LT_SYSTEM_SETUP_TOKEN_HEADER, LT_VALIDATE_SESSION_TIMEOUT_MS, LtAuthStateService, LtAuthStore, LtSystemSetupService, LtUnauthorizedHandler, hasLtRole, isLtApiRequest, isLtAuthEndpoint, isLtJwtShaped, ltAuthGuard, ltAuthInterceptor, ltGuestGuard, ltRoleGuard, ltSystemSetupGuard, provideLtAuth, readLtAuthAnswer, resolveLtAuthCookieNames, resolveLtAuthState, resolveLtCookiePrefix };
944
945
 
945
946
  //# sourceMappingURL=lenne.tech-ng-base-auth-provide.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"lenne.tech-ng-base-auth-provide.mjs","names":[],"sources":["../../../projects/ng-base/auth/provide/src/lib/auth-options.ts","../../../projects/ng-base/auth/provide/src/lib/auth-state.ts","../../../projects/ng-base/auth/provide/src/lib/auth-urls.ts","../../../projects/ng-base/auth/provide/src/lib/session-loss.ts","../../../projects/ng-base/auth/provide/src/lib/auth-store.ts","../../../projects/ng-base/auth/provide/src/lib/system-setup.ts","../../../projects/ng-base/auth/provide/src/lib/auth-guards.ts","../../../projects/ng-base/auth/provide/src/lib/auth-interceptor.ts","../../../projects/ng-base/auth/provide/src/lib/provide-lt-auth.ts"],"sourcesContent":["import { inject, InjectionToken } from '@angular/core';\nimport { LT_RUNTIME_CONFIG, mergeLtFeatureOptions } from '@lenne.tech/ng-base';\n\n/** Cookie names (exact names win over the `NG_PUBLIC_COOKIE_PREFIX` derived ones). */\nexport interface LtAuthCookieNamesOptions {\n /** Auth-state cookie (default `lt-auth-state`, or `<prefix>-auth-state`). */\n state?: string;\n /** JWT cookie (default `lt-jwt-token`, or `<prefix>-jwt-token`). */\n token?: string;\n}\n\n/** Options of `provideLtAuth()`; runtime overrides via `NG_PUBLIC_LT_AUTH_*`. */\nexport interface LtAuthOptions {\n /** Better Auth base path, must equal nest-server `betterAuth.basePath` (default `/iam`). */\n basePath?: string;\n /** Cookie names. */\n cookieNames?: LtAuthCookieNamesOptions;\n /**\n * Register Better Auth's `adminClient()` plugin (default **`false`**). nest-server does not serve the admin plugin\n * today (routes 404), and its methods are not hashed. Enable only together with a backend that registers `admin()`.\n */\n enableAdmin?: boolean;\n /**\n * Offer passkeys (default `true`); `false` makes `passkeySupported()` false. The passkey flows call\n * `/iam/passkey/*` directly, so `@better-auth/passkey` is not needed (add `passkeyClient()` via `plugins` if wanted).\n */\n enablePasskey?: boolean;\n /** Register the two-factor plugin (default `true`). */\n enableTwoFactor?: boolean;\n /** 401 handling. */\n interceptor?: {\n /** Detect session loss on 401 and redirect to the login page (default `true`). */\n enabled?: boolean;\n /** Extra route prefixes on which a 401 never logs out (merged with the built-in auth pages). */\n publicPaths?: string[];\n };\n /** Login page (default `/auth/login`). */\n loginPath?: string;\n /** Extra Better Auth client plugins (same as `registerLtAuthPlugins()`). */\n plugins?: unknown[];\n /** First-admin setup flow. */\n systemSetup?: {\n /** Enable the setup redirect of `ltSystemSetupGuard` (default `false`). */\n enabled?: boolean;\n /** Setup page (default `/auth/setup`). */\n setupPath?: string;\n };\n /** Where a sign-in that needs a second factor redirects (default `/auth/2fa`). */\n twoFactorRedirectPath?: string;\n}\n\n/** Fully resolved auth options. */\nexport interface LtResolvedAuthOptions {\n basePath: string;\n cookieNames: Required<LtAuthCookieNamesOptions>;\n enableAdmin: boolean;\n enablePasskey: boolean;\n enableTwoFactor: boolean;\n interceptor: { enabled: boolean; publicPaths: string[] };\n loginPath: string;\n plugins: unknown[];\n systemSetup: { enabled: boolean; setupPath: string };\n twoFactorRedirectPath: string;\n}\n\n/** Default name of the auth-state cookie. */\nexport const LT_AUTH_STATE_COOKIE_DEFAULT = 'lt-auth-state';\n/** Default name of the JWT cookie. */\nexport const LT_JWT_TOKEN_COOKIE_DEFAULT = 'lt-jwt-token';\n\n/** Defaults of {@link LtAuthOptions}. */\nexport const LT_AUTH_DEFAULTS: LtResolvedAuthOptions = {\n basePath: '/iam',\n cookieNames: { state: LT_AUTH_STATE_COOKIE_DEFAULT, token: LT_JWT_TOKEN_COOKIE_DEFAULT },\n enableAdmin: false,\n enablePasskey: true,\n enableTwoFactor: true,\n interceptor: { enabled: true, publicPaths: [] },\n loginPath: '/auth/login',\n plugins: [],\n systemSetup: { enabled: false, setupPath: '/auth/setup' },\n twoFactorRedirectPath: '/auth/2fa',\n};\n\n/** Options passed to `provideLtAuth()` (raw). */\nexport const LT_AUTH_INPUT_OPTIONS = new InjectionToken<LtAuthOptions>('LT_AUTH_INPUT_OPTIONS', { factory: () => ({}) });\n\n/**\n * Resolved auth options: defaults ← `provideLtAuth(options)` ← runtime `NG_PUBLIC_LT_AUTH_*`; cookie names derived\n * from `NG_PUBLIC_COOKIE_PREFIX` unless set explicitly.\n */\nexport const LT_AUTH_OPTIONS = new InjectionToken<LtResolvedAuthOptions>('LT_AUTH_OPTIONS', {\n factory: () => {\n const runtime = inject(LT_RUNTIME_CONFIG, { optional: true });\n const merged = mergeLtFeatureOptions(LT_AUTH_DEFAULTS, inject(LT_AUTH_INPUT_OPTIONS), runtime?.lt.auth);\n return { ...merged, cookieNames: resolveLtAuthCookieNames(merged.cookieNames, runtime?.cookiePrefix) };\n },\n});\n\n/** Sanitise a cookie prefix to RFC 6265 token characters (`[A-Za-z0-9._-]`). */\nexport function resolveLtCookiePrefix(raw: unknown): string {\n return typeof raw === 'string' ? raw.trim().replace(/[^A-Za-z0-9._-]/g, '') : '';\n}\n\n/**\n * Cookie names: an explicit non-default name wins, else `<prefix>-auth-state` / `<prefix>-jwt-token`, else the defaults.\n * Mirror the prefix on the backend (`COOKIE_PREFIX`) so both sides agree.\n */\nexport function resolveLtAuthCookieNames(configured: LtAuthCookieNamesOptions | undefined, cookiePrefix?: unknown): Required<LtAuthCookieNamesOptions> {\n const prefix = resolveLtCookiePrefix(cookiePrefix);\n const state = configured?.state && configured.state !== LT_AUTH_STATE_COOKIE_DEFAULT ? configured.state : prefix ? `${prefix}-auth-state` : LT_AUTH_STATE_COOKIE_DEFAULT;\n const token = configured?.token && configured.token !== LT_JWT_TOKEN_COOKIE_DEFAULT ? configured.token : prefix ? `${prefix}-jwt-token` : LT_JWT_TOKEN_COOKIE_DEFAULT;\n return { state, token };\n}\n","import { DOCUMENT, isPlatformServer } from '@angular/common';\nimport { inject, PLATFORM_ID, Service } from '@angular/core';\nimport { LtCookieService } from '@lenne.tech/ng-base';\n\nimport { LT_AUTH_OPTIONS } from './auth-options';\nimport type { LtAuthMode, LtAuthState, LtUser } from './auth-types';\n\n/** Lifetime of the auth-state and JWT cookies (7 days). */\nexport const LT_AUTH_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;\n\n/** Better Auth session cookies cleared best-effort on sign-out / session loss (the real ones are httpOnly). */\nconst BETTER_AUTH_COOKIES = ['better-auth.session_token', 'better-auth.session', '__Secure-better-auth.session_token', 'session_token', 'session'];\nconst BETTER_AUTH_COOKIE_PATHS = ['/', '/api', '/api/iam', '/iam'];\n\n/**\n * Resolve the auth state from a raw cookie string, tolerating several cookies of the same name.\n *\n * A host-only cookie and a domain-scoped twin can coexist and disagree; the one carrying a `user` wins. Preferring the\n * user-less twin caused random logouts in the Nuxt stack. `lt-auth-state` is a UI cache, the session itself is the\n * httpOnly `iam.session_token`, so a forged cookie yields at most a UI that looks signed in while every API call 401s.\n */\nexport function resolveLtAuthState(cookieString: string, stateCookieName: string): LtAuthState | null {\n const prefix = `${stateCookieName}=`;\n let fallback: LtAuthState | null = null;\n for (const entry of (cookieString || '').split(/;\\s*/)) {\n if (!entry.startsWith(prefix)) {\n continue;\n }\n try {\n const state = JSON.parse(decodeURIComponent(entry.slice(prefix.length))) as LtAuthState | null;\n if (state?.user) {\n return state;\n }\n if (state) {\n fallback = state;\n }\n } catch {\n // malformed entry\n }\n }\n return fallback;\n}\n\n/**\n * Read/write access to the auth cookies (`lt-auth-state`, `lt-jwt-token`) with the duplicate-tolerant read and the\n * SSR no-write rule. The server reads the request cookies and never writes.\n */\n@Service()\nexport class LtAuthStateService {\n private readonly cookies = inject(LtCookieService);\n private readonly options = inject(LT_AUTH_OPTIONS);\n private readonly document = inject(DOCUMENT);\n private readonly isServer = isPlatformServer(inject(PLATFORM_ID));\n\n /** Cookie names in use. */\n get cookieNames(): { state: string; token: string } {\n return this.options.cookieNames;\n }\n\n /** Current auth state from the cookies (`null` when none). */\n readState(): LtAuthState | null {\n return resolveLtAuthState(this.cookies.raw(), this.cookieNames.state);\n }\n\n /** Write the auth state (browser only; never writes a user-less state during SSR). */\n writeState(state: LtAuthState): void {\n if (this.isServer) {\n return;\n }\n const encoded = JSON.stringify(state);\n if (encodeURIComponent(encoded).length > 3500 && typeof ngDevMode !== 'undefined' && ngDevMode) {\n console.warn(`[@lenne.tech/ng-base] The auth-state cookie is ${encodeURIComponent(encoded).length} bytes; browsers drop cookies over 4 KB.`);\n }\n this.cookies.set(this.cookieNames.state, encoded, { maxAge: LT_AUTH_COOKIE_MAX_AGE });\n }\n\n /** Auth mode from the state cookie; `cookie` when unknown. */\n getAuthMode(): LtAuthMode {\n return this.readState()?.authMode === 'jwt' ? 'jwt' : 'cookie';\n }\n\n /** Merge `authMode` into the state cookie (keeps the user). */\n setAuthMode(mode: LtAuthMode): void {\n const current = this.readState();\n this.writeState({ authMode: mode, user: current?.user ?? null });\n }\n\n /**\n * First JWT of the token cookie(s), or `null`. Browser only. A value that is not JWT-shaped (e.g. an opaque Better Auth\n * session token an older version stored) is never returned and is deleted. A JSON-quoted JWT (`\"eyJ…\"`, written by\n * 22.0.0) is returned and rewritten raw, the form nest-server reads from the cookie.\n */\n getJwtToken(): string | null {\n if (this.isServer) {\n return null;\n }\n let invalid = false;\n for (const raw of this.cookies.getAll(this.cookieNames.token)) {\n const token = parseToken(raw);\n if (isLtJwtShaped(token)) {\n if (raw !== token) {\n this.deleteEverywhere(this.cookieNames.token);\n this.cookies.set(this.cookieNames.token, token, { maxAge: LT_AUTH_COOKIE_MAX_AGE });\n }\n return token;\n }\n invalid ||= !!token;\n }\n if (invalid) {\n this.deleteEverywhere(this.cookieNames.token);\n }\n return null;\n }\n\n /**\n * Store or clear the JWT (7 days). Browser only. Only JWT-shaped values are accepted: the token cookie is readable by\n * scripts, so an opaque session token (the body of `two-factor/*` or `passkey/*` answers) must never land there.\n * Returns whether the value was stored.\n */\n setJwtToken(token: string | null): boolean {\n if (this.isServer) {\n return false;\n }\n if (!token) {\n this.deleteEverywhere(this.cookieNames.token);\n return false;\n }\n if (!isLtJwtShaped(token)) {\n this.deleteEverywhere(this.cookieNames.token);\n console.warn('[@lenne.tech/ng-base] Refused to store a token that is not a JWT as JWT.');\n return false;\n }\n // Raw, not JSON-quoted: nest-server reads the cookie as the JWT itself (`lt-jwt-token`, JWT cookie strategy), as\n // nuxt-extensions writes it.\n this.cookies.set(this.cookieNames.token, token, { maxAge: LT_AUTH_COOKIE_MAX_AGE });\n return true;\n }\n\n /**\n * Delete a stored token that is not JWT-shaped and fall back to cookie mode (run at startup in the browser). Returns\n * whether something was purged.\n */\n purgeInvalidJwt(): boolean {\n if (this.isServer) {\n return false;\n }\n const stored = this.cookies.getAll(this.cookieNames.token).map(parseToken);\n if (!stored.some((token) => token && !isLtJwtShaped(token))) {\n return false;\n }\n this.deleteEverywhere(this.cookieNames.token);\n const state = this.readState();\n if (state?.authMode === 'jwt') {\n this.writeState({ authMode: 'cookie', user: state.user });\n }\n return true;\n }\n\n /** Whether any state cookie carries a user. */\n isAuthenticated(): boolean {\n return !!this.readState()?.user;\n }\n\n /** The user from the state cookie. */\n readUser(): LtUser | null {\n return this.readState()?.user ?? null;\n }\n\n /**\n * Delete the auth-state and JWT cookies on the host-only and the `domain=<host>` slot, and best-effort the Better Auth\n * cookies on the usual paths. Browser only.\n */\n clearAuthCookies(): void {\n if (this.isServer) {\n return;\n }\n this.deleteEverywhere(this.cookieNames.state);\n this.deleteEverywhere(this.cookieNames.token);\n for (const name of BETTER_AUTH_COOKIES) {\n for (const path of BETTER_AUTH_COOKIE_PATHS) {\n this.cookies.delete(name, { path });\n }\n }\n }\n\n private deleteEverywhere(name: string): void {\n const host = this.document.location?.hostname;\n this.cookies.delete(name, { path: '/' });\n if (host) {\n this.cookies.delete(name, { domain: host, path: '/' });\n }\n }\n}\n\n/** Whether `value` looks like a JWT (`eyJ…`, three segments), the same test nest-server applies (`isJwtShaped`). */\nexport function isLtJwtShaped(value: unknown): value is string {\n return typeof value === 'string' && value.startsWith('eyJ') && value.split('.').length === 3 && value.split('.').every((part) => part.length > 0);\n}\n\n/**\n * Whether the JWT expires within `marginMs` (by its `exp` claim, read without verification: only for timing the\n * refresh, never for trust). A token without a readable `exp` counts as not expiring; the server decides.\n */\nexport function ltJwtExpiresWithin(token: string, marginMs: number, now: number = Date.now()): boolean {\n const payload = token.split('.')[1];\n if (!payload) {\n return false;\n }\n try {\n const base64 = payload.replace(/-/g, '+').replace(/_/g, '/');\n const claims = JSON.parse(atob(base64.padEnd(base64.length + ((4 - (base64.length % 4)) % 4), '='))) as { exp?: unknown };\n return typeof claims.exp === 'number' && claims.exp * 1000 - now <= marginMs;\n } catch {\n return false;\n }\n}\n\nfunction parseToken(raw: string): string | null {\n if (!raw) {\n return null;\n }\n try {\n const parsed: unknown = JSON.parse(raw);\n return typeof parsed === 'string' && parsed ? parsed : null;\n } catch {\n return raw;\n }\n}\n","/** Built-in route prefixes on which a 401 never logs out (auth pages). Extend via `provideLtAuth({ interceptor: { publicPaths } })`. */\nexport const LT_AUTH_PUBLIC_PATHS: readonly string[] = [\n '/auth/login',\n '/auth/register',\n '/auth/forgot-password',\n '/auth/reset-password',\n '/auth/2fa',\n '/auth/setup',\n '/auth/verify-email',\n];\n\n/** API path fragments whose 401 is part of a normal auth flow (never treated as session loss). */\nexport const LT_AUTH_ENDPOINT_FRAGMENTS: readonly string[] = [\n '/sign-in',\n '/sign-up',\n '/sign-out',\n '/forgot-password',\n '/reset-password',\n '/verify-email',\n '/get-session',\n '/list-sessions',\n '/revoke-session',\n '/revoke-other-sessions',\n '/token',\n '/passkey/',\n '/list-user-passkeys',\n '/generate-register-options',\n '/verify-registration',\n '/generate-authenticate-options',\n '/verify-authentication',\n '/two-factor/',\n];\n\n/**\n * Paths that need the session cookie even in JWT mode: passkey and 2FA challenges are bound to cookies, and\n * `/get-session` resolves the session from the cookie only (a bearer-only request gets `200 null`, which the session\n * probe would read as \"dead\" for every user in JWT mode).\n */\nexport const LT_AUTH_COOKIE_PATHS: readonly string[] = ['/passkey/', '/two-factor/', '/2fa/', '/get-session'];\n\n/** Whether a 401 from `url` belongs to an auth flow (wrong password, wrong code, probe) rather than a lost session. */\nexport function isLtAuthEndpoint(url: string): boolean {\n return LT_AUTH_ENDPOINT_FRAGMENTS.some((fragment) => url.includes(fragment));\n}\n\n/** Whether `url` needs the session cookie even in JWT mode (see {@link LT_AUTH_COOKIE_PATHS}). */\nexport function ltUrlRequiresCookies(url: string): boolean {\n return LT_AUTH_COOKIE_PATHS.some((path) => url.includes(path));\n}\n\n/** URL string of a fetch input. */\nexport function ltRequestUrl(input: RequestInfo | URL): string {\n if (typeof input === 'string') {\n return input;\n }\n return input instanceof URL ? input.href : input.url;\n}\n\n/** Path of a URL or route (`/a/b?x=1#y` → `/a/b`). */\nexport function ltPathOf(url: string): string {\n return url.split(/[?#]/)[0] || '/';\n}\n\n/**\n * Whether `url` goes to the API, so it may carry credentials and the Bearer token:\n *\n * - it starts with the API base URL at a path boundary (`https://api.example.com` matches `https://api.example.com/x`,\n * never `https://api.example.com.evil.test`);\n * - or it is same-origin and starts with `/api` (dev proxy);\n * - or no API URL is configured and it is same-origin (same-origin reverse proxy).\n *\n * Absolute URLs on `origin` are compared by their path. Foreign hosts never get the token.\n */\nexport function isLtApiRequest(url: string, baseUrl: string, origin?: string | null): boolean {\n if (baseUrl && hasPrefix(url, baseUrl)) {\n return true;\n }\n let path = url;\n if (origin && origin !== 'null' && hasPrefix(url, origin)) {\n path = url.slice(origin.length) || '/';\n }\n if (!path.startsWith('/') || path.startsWith('//')) {\n return false;\n }\n if (hasPrefix(path, '/api')) {\n return true;\n }\n if (!baseUrl) {\n return true;\n }\n return baseUrl.startsWith('/') && hasPrefix(path, baseUrl);\n}\n\nfunction hasPrefix(value: string, prefix: string): boolean {\n if (!value.startsWith(prefix)) {\n return false;\n }\n const next = value.charAt(prefix.length);\n return next === '' || next === '/' || next === '?' || next === '#' || prefix.endsWith('/');\n}\n","import { isLtAuthEndpoint, ltPathOf } from './auth-urls';\n\n/** How long a positive (\"session is alive\") verdict is reused. Only `true` is cached, never `false` or \"no verdict\". */\nexport const LT_SESSION_ALIVE_TTL_MS = 1000;\n/** How long the guard is held after a logout, so a burst of parallel 401s redirects once. */\nexport const LT_SESSION_LOSS_GUARD_MS = 1000;\n\n/** What the detector needs from the auth service. */\nexport interface LtSessionLossHost {\n clearUser(): void;\n /** Current router URL (full path incl. query), `null` when unknown. */\n currentUrl(): string | null;\n isAuthenticated(): boolean;\n isJwtMode(): boolean;\n navigate(url: string): void;\n /** `GET <auth>/get-session` with the session cookie. */\n probeSession(): Promise<Response>;\n switchToJwtMode(): Promise<boolean>;\n}\n\n/** Configuration of the detector. */\nexport interface LtSessionLossConfig {\n enabled: boolean;\n loginPath: string;\n publicPaths: readonly string[];\n}\n\n/** The detector: `handle(url)` resolves to `true` when the session was found dead and the user was redirected. */\nexport interface LtSessionLossDetector {\n handle(url?: string): Promise<boolean>;\n}\n\n/**\n * Session-loss detection on 401 (port of the nuxt-extensions auth interceptor).\n *\n * A 401 from a domain endpoint is no proof of an expired session (backends mislabel permission errors as 401), so the\n * session endpoint is probed first: only a confirmed dead session (`401`/`403` or an empty body) clears the state and\n * redirects to `loginPath?redirect=<current url>`. A probe without verdict (network error, 429, 5xx) never logs out.\n * The guard is claimed as late as possible and held only after a logout, so a 401 that was ignored (auth endpoint,\n * public page, user not hydrated yet) never swallows the ones right behind it.\n */\nexport function createLtSessionLossDetector(host: LtSessionLossHost, config: LtSessionLossConfig): LtSessionLossDetector {\n let handling = false;\n let aliveUntil = 0;\n\n const isPublicRoute = (): boolean => {\n const current = host.currentUrl();\n if (!current) {\n return false;\n }\n const path = ltPathOf(current);\n return config.publicPaths.some((publicPath) => path.startsWith(publicPath));\n };\n\n const probe = async (): Promise<boolean | null> => {\n if (Date.now() < aliveUntil) {\n return true;\n }\n let verdict: boolean | null;\n try {\n const response = await host.probeSession();\n if (response.status === 401 || response.status === 403) {\n return false;\n }\n if (!response.ok) {\n return null;\n }\n const data = (await response.json().catch(() => null)) as { session?: unknown; user?: unknown } | null;\n verdict = Boolean(data && (data.user || data.session));\n // In JWT mode the 401 came from the bearer while the verdict came from the cookie: the bearer is stale.\n if (verdict && host.isJwtMode()) {\n await host.switchToJwtMode().catch(() => false);\n }\n } catch {\n return null;\n }\n if (verdict) {\n aliveUntil = Date.now() + LT_SESSION_ALIVE_TTL_MS;\n }\n return verdict;\n };\n\n return {\n async handle(url?: string): Promise<boolean> {\n if (!config.enabled || handling || (url && isLtAuthEndpoint(url)) || isPublicRoute() || !host.isAuthenticated()) {\n return false;\n }\n handling = true;\n let loggedOut = false;\n try {\n const alive = await probe();\n if (alive !== false) {\n return false;\n }\n loggedOut = true;\n host.clearUser();\n const current = host.currentUrl();\n const redirect = current && ltPathOf(current) !== config.loginPath ? `?redirect=${encodeURIComponent(current)}` : '';\n host.navigate(`${config.loginPath}${redirect}`);\n return true;\n } catch (error) {\n console.error('[@lenne.tech/ng-base] Handling a 401 failed:', error);\n return loggedOut;\n } finally {\n if (loggedOut) {\n setTimeout(() => {\n handling = false;\n }, LT_SESSION_LOSS_GUARD_MS);\n } else {\n handling = false;\n }\n }\n },\n };\n}\n","import { DOCUMENT, isPlatformServer } from '@angular/common';\nimport { computed, inject, InjectionToken, Injector, makeStateKey, PLATFORM_ID, Service, signal, TransferState, type Signal } from '@angular/core';\nimport { Router } from '@angular/router';\nimport { isLtAbsoluteUrl, LT_RUNTIME_CONFIG, ltCredentialsFetch, LtApiUrlService } from '@lenne.tech/ng-base';\n\nimport { LT_AUTH_OPTIONS } from './auth-options';\nimport { isLtJwtShaped, LtAuthStateService, ltJwtExpiresWithin } from './auth-state';\nimport type { LtAuthCallOptions, LtAuthFeatures, LtAuthMode, LtUser, LtUserInput } from './auth-types';\nimport { isLtApiRequest, isLtAuthEndpoint, LT_AUTH_PUBLIC_PATHS, ltRequestUrl, ltUrlRequiresCookies } from './auth-urls';\nimport { createLtSessionLossDetector } from './session-loss';\n\n/**\n * Hooks around `signOut()`. `provideLtSync()` registers one that flushes the offline outbox before the session ends and\n * deletes the IndexedDB afterwards; projects can add their own (multi provider).\n */\nexport interface LtAuthSignOutHook {\n /** Runs before `POST /iam/sign-out` with the id of the signed-in user (errors are logged, never block sign-out). */\n beforeSignOut?(userId: string | null): Promise<void> | void;\n /** Runs after the local state was cleared. */\n afterSignOut?(): Promise<void> | void;\n}\n\n/** Sign-out hooks (multi). */\nexport const LT_AUTH_SIGN_OUT_HOOKS = new InjectionToken<LtAuthSignOutHook[]>('LT_AUTH_SIGN_OUT_HOOKS', { factory: () => [] });\n\n/** TransferState key of the feature flags fetched during SSR. */\nexport const LT_AUTH_FEATURES_STATE_KEY = makeStateKey<LtAuthFeatures>('lt-auth-features');\n\n/** Upper bound for `validateSession()` waiting on `GET /iam/get-session`. */\nexport const LT_VALIDATE_SESSION_TIMEOUT_MS = 10_000;\n\n/**\n * A JWT that expires within this margin is refreshed before it is sent (nest-server's JWTs live 15 minutes; with\n * `cookies: false` there is no session cookie to mint a new one once it has expired).\n */\nexport const LT_JWT_REFRESH_MARGIN_MS = 60_000;\n\n/** Upper bound for `GET /iam/features` (keeps a slow API from holding the SSR response). */\nconst FEATURES_TIMEOUT_MS = 5000;\n\n/**\n * Authorization keys the session owns. A key the session omits is dropped from the merged user (fail-closed), so a\n * backend downgrade (role removed, ban) is never masked by a stale cached value.\n */\nconst AUTHZ_KEYS = ['banExpires', 'banReason', 'banned', 'emailVerified', 'role', 'roles', 'twoFactorEnabled'] as const satisfies readonly (keyof LtUser)[];\n\n/** What a sign-in / sign-up answer says (nest-server's own shape and Better Auth's native one). */\nexport interface LtAuthAnswer {\n /** Sign-up with email verification: nest-server revoked the session, nobody is signed in yet. */\n emailVerificationRequired: boolean;\n /** A JWT the server put into the body on purpose (`cookies: false` or `exposeTokenInBody` with the JWT plugin). */\n jwt: string | null;\n /** The answer proves a session (`success: true`, a token or a session object) and reports no failure. */\n session: boolean;\n /** A second factor is required (`requiresTwoFactor` from nest-server, `twoFactorRedirect` from Better Auth). */\n twoFactor: boolean;\n user: LtUserInput | null;\n}\n\n/**\n * Read a sign-in / sign-up answer, either the Better Auth client envelope (`{ data, error }`) or a raw body.\n *\n * nest-server answers `{ requiresTwoFactor, success, user }` (the token is removed from the body while cookies are on),\n * `{ requiresTwoFactor: true, success: false }` when 2FA is required and `{ emailVerificationRequired: true, user }`\n * after a sign-up whose session it revoked. Better Auth's native answers carry `twoFactorRedirect` or\n * `{ token: <opaque session token>, user }`. Only a JWT-shaped body token counts as JWT: an opaque session token never\n * becomes a bearer kept in a script-readable cookie.\n */\nexport function readLtAuthAnswer(result: unknown): LtAuthAnswer {\n const outer = (result && typeof result === 'object' ? result : {}) as Record<string, unknown>;\n const data = (outer['data'] && typeof outer['data'] === 'object' ? outer['data'] : {}) as Record<string, unknown>;\n const pick = (key: string): unknown => data[key] ?? outer[key];\n const token = pick('token');\n const user = pick('user');\n const sessionObject = pick('session');\n const failed = !!outer['error'] || pick('success') === false;\n const proven = pick('success') === true || (typeof token === 'string' && !!token) || (!!sessionObject && typeof sessionObject === 'object');\n return {\n emailVerificationRequired: pick('emailVerificationRequired') === true,\n jwt: isLtJwtShaped(token) ? token : null,\n session: proven && !failed,\n twoFactor: pick('requiresTwoFactor') === true || pick('twoFactorRedirect') === true,\n user: user && typeof user === 'object' ? (user as LtUserInput) : null,\n };\n}\n\n/**\n * The light half of authentication: session state, transport and session validation, without Better Auth.\n *\n * Guards, `ltAuthInterceptor`, `provideLtAuth()` (and through it `LT_FETCH`) and offline sync use this store, so an\n * app's initial bundle carries no passkey, 2FA or Better Auth code. `LtAuthService` (`@lenne.tech/ng-base/auth`)\n * exposes the same state and methods and adds the Better Auth flows; both read and write the same signals.\n *\n * State is signal-based and backed by the client-readable `lt-auth-state` cookie, so SSR renders the signed-in shell\n * from the request cookie (UI gating only; the session itself is the httpOnly `iam.session_token`). Transport: session\n * cookie first; when cookies do not work (401 while the state says signed in) the store switches to JWT mode\n * (`GET /iam/token`, Bearer header) and refreshes a stale JWT once per burst. nest-server with `cookies: false` answers\n * every sign-in with a JWT, which puts the store into JWT mode directly; session checks never change the mode, and a\n * JWT close to its expiry is refreshed with itself as bearer before it is sent.\n */\n@Service()\nexport class LtAuthStore {\n private readonly api = inject(LtApiUrlService);\n private readonly authState = inject(LtAuthStateService);\n private readonly options = inject(LT_AUTH_OPTIONS);\n private readonly runtimeConfig = inject(LT_RUNTIME_CONFIG, { optional: true });\n private readonly transferState = inject(TransferState);\n private readonly injector = inject(Injector);\n private readonly document = inject(DOCUMENT);\n private readonly signOutHooks = inject(LT_AUTH_SIGN_OUT_HOOKS);\n private readonly isServer = isPlatformServer(inject(PLATFORM_ID));\n\n // ── state ──────────────────────────────────────────────────────────────────────────────────────────────────────────\n /** A non-JWT value in the JWT cookie (opaque session token from an older version) is deleted before anything reads it. */\n private readonly purgedInvalidJwt = this.authState.purgeInvalidJwt();\n private readonly initialState = this.authState.readState();\n private readonly transferredFeatures = this.isServer ? null : this.transferState.get(LT_AUTH_FEATURES_STATE_KEY, null);\n private readonly userState = signal<LtUser | null>(this.initialState?.user ?? null);\n private readonly modeState = signal<LtAuthMode>(this.initialState?.authMode === 'jwt' ? 'jwt' : 'cookie');\n private readonly loadingState = signal(false);\n private readonly featuresState = signal<LtAuthFeatures>(this.transferredFeatures ?? {});\n private featuresLoaded = this.transferredFeatures !== null;\n private featuresRequest: Promise<LtAuthFeatures> | null = null;\n /** In-flight `GET /token` shared by every 401 of a burst (cookie → JWT switch and stale-JWT refresh alike). */\n private jwtMint: Promise<string | null> | null = null;\n private readonly sessionLoss = createLtSessionLossDetector(\n {\n clearUser: () => this.clearUser(),\n currentUrl: () => this.currentUrl(),\n isAuthenticated: () => this.isAuthenticated(),\n isJwtMode: () => this.isJwtMode(),\n navigate: (url) => this.navigate(url),\n probeSession: () => this.fetchWithAuth(this.authUrl('/get-session'), { method: 'GET' }),\n switchToJwtMode: () => this.switchToJwtMode(),\n },\n {\n enabled: this.options.interceptor.enabled,\n loginPath: this.options.loginPath,\n publicPaths: [\n ...new Set([\n ...LT_AUTH_PUBLIC_PATHS,\n ...this.options.interceptor.publicPaths,\n this.options.loginPath,\n this.options.twoFactorRedirectPath,\n this.options.systemSetup.setupPath,\n ]),\n ],\n },\n );\n\n /** Signed-in user (from the auth-state cookie, refreshed by `validateSession()` and every sign-in). */\n readonly user: Signal<LtUser | null> = this.userState.asReadonly();\n /** Whether a user is signed in (UI gating; the backend decides). */\n readonly isAuthenticated = computed(() => !!this.userState());\n /** Current transport. */\n readonly authMode: Signal<LtAuthMode> = this.modeState.asReadonly();\n /** Whether the JWT fallback is active. */\n readonly isJwtMode = computed(() => this.modeState() === 'jwt');\n /** Whether a sign-in/up/out or passkey flow is running. */\n readonly isLoading: Signal<boolean> = this.loadingState.asReadonly();\n /** Whether the user has the `admin` role (UX gate only). */\n readonly isAdmin = computed(() => hasLtRole(this.userState(), 'admin'));\n /** Whether two-factor authentication is enabled for the user. */\n readonly is2FAEnabled = computed(() => !!this.userState()?.twoFactorEnabled);\n /** nest-server feature flags (`GET /iam/features`); fetched on the server and transferred to the browser. */\n readonly features: Signal<LtAuthFeatures> = this.featuresState.asReadonly();\n /** Whether passkeys can be used: enabled in the options, backend feature on and WebAuthn available in this browser. */\n readonly passkeySupported = computed(() => this.options.enablePasskey && !!this.featuresState()['passkey'] && typeof globalThis.PublicKeyCredential !== 'undefined');\n\n /** The current JWT (JWT mode only), or `null`. */\n jwtToken(): string | null {\n return this.isJwtMode() ? this.authState.getJwtToken() : null;\n }\n\n // ── roles ──────────────────────────────────────────────────────────────────────────────────────────────────────────\n /** `user.role === role` or `user.roles` contains it (case-sensitive). */\n hasRole(role: string): boolean {\n return hasLtRole(this.userState(), role);\n }\n\n /** Whether the user has any of the roles. */\n hasAnyRole(...roles: string[]): boolean {\n return roles.some((role) => hasLtRole(this.userState(), role));\n }\n\n // ── features ───────────────────────────────────────────────────────────────────────────────────────────────────────\n /**\n * Fetch the feature flags (`GET /iam/features`, anonymous). One request in flight at a time; errors keep the last\n * known flags. On the server the result is put into `TransferState` for the browser. SSR without API URL: no fetch.\n */\n fetchFeatures(): Promise<LtAuthFeatures> {\n if (this.isServer && this.api.resolve().missing) {\n return Promise.resolve(this.featuresState());\n }\n if (!this.featuresRequest) {\n this.featuresRequest = this.loadFeatures().finally(() => {\n this.featuresRequest = null;\n });\n }\n return this.featuresRequest;\n }\n\n /** The feature flags, fetched at most once (transferred from SSR, or fetched on first call). */\n ensureFeatures(): Promise<LtAuthFeatures> {\n return this.featuresLoaded ? Promise.resolve(this.featuresState()) : this.fetchFeatures();\n }\n\n // ── session ────────────────────────────────────────────────────────────────────────────────────────────────────────\n /** Store the user in state and cookie (browser always; SSR never writes). `image: null` becomes `undefined`. */\n setUser(user: LtUserInput | null, mode: LtAuthMode = 'cookie'): void {\n const normalised: LtUser | null = user ? ({ ...user, image: user.image ?? undefined } as LtUser) : null;\n this.userState.set(normalised);\n this.modeState.set(mode);\n this.authState.writeState({ authMode: mode, user: normalised });\n }\n\n /** Clear local auth state and cookies (browser). Does not delete offline data (use `signOut()` for that). */\n clearUser(): void {\n this.userState.set(null);\n this.modeState.set('cookie');\n this.authState.clearAuthCookies();\n }\n\n /**\n * Sign out: sign-out hooks (offline sync flush), `POST /iam/sign-out`, local state cleared, after-hooks (sync wipe).\n * The request is a plain {@link fetchWithAuth} call, so signing out never loads Better Auth. The local state is\n * cleared even when the request fails (offline device): the failure is logged, not thrown.\n */\n async signOut(options?: LtAuthCallOptions): Promise<void> {\n this.loadingState.set(true);\n try {\n const userId = this.userState()?.id;\n for (const hook of this.signOutHooks) {\n await runHook(() => hook.beforeSignOut?.(userId ? String(userId) : null), 'beforeSignOut');\n }\n try {\n await this.fetchWithAuth(this.authUrl('/sign-out'), {\n body: '{}',\n credentials: 'include',\n headers: { ...options?.headers, 'content-type': 'application/json' },\n method: 'POST',\n signal: options?.signal,\n });\n } catch (error) {\n console.error('[@lenne.tech/ng-base] The sign-out request failed; the local session is cleared anyway:', error);\n }\n this.clearUser();\n for (const hook of this.signOutHooks) {\n await runHook(() => hook.afterSignOut?.(), 'afterSignOut');\n }\n } finally {\n this.loadingState.set(false);\n }\n }\n\n /**\n * Validate the session (app start / hard reload). Server: answers from the cookie, no backend call. Browser:\n * `GET /iam/get-session` (≤ 10 s, plain {@link fetchWithAuth}); a user is merged with the cached one\n * (nest-server-only fields kept, authz keys fail-closed); no user but a cached one → `true` (e.g. right after 2FA);\n * else `false`.\n */\n async validateSession(): Promise<boolean> {\n if (this.isServer) {\n return !!this.cachedUser();\n }\n try {\n const session = await withTimeout(this.fetchSession(), LT_VALIDATE_SESSION_TIMEOUT_MS);\n const sessionUser = session?.user;\n if (sessionUser) {\n // The answer proves the session, not the transport: keep the mode (a bearer-proven session stays in JWT mode).\n this.setUser(this.mergeSessionUser(sessionUser), this.modeState());\n if (!this.isJwtMode()) {\n this.prefetchJwt();\n }\n return true;\n }\n const cached = this.cachedUser();\n if (cached) {\n if (!this.userState()) {\n this.userState.set(cached);\n }\n this.prefetchJwt();\n return true;\n }\n return false;\n } catch {\n return !!this.cachedUser();\n }\n }\n\n /**\n * The auth-aware fetch (also provided as `LT_FETCH` by `provideLtAuth()` and used by the Better Auth client).\n *\n * For API URLs (see {@link isLtApiRequest}): cookie mode sends `credentials: 'include'`; JWT mode adds\n * `Authorization: Bearer` and omits cookies except for passkey/2FA/`get-session` (an explicit `init.credentials`\n * wins). On a 401 while signed in: cookie mode → switch to JWT and retry once; JWT mode → one shared refresh per\n * burst and retry once. A 401 that survives is handed to the session-loss detection. Auth-flow endpoints\n * (sign-in, 2FA, …) are never retried: their 401 is the flow's verdict. Other URLs are fetched unchanged (no token).\n */\n async fetchWithAuth(input: RequestInfo | URL, init: RequestInit = {}): Promise<Response> {\n const url = ltRequestUrl(input);\n if (!isLtApiRequest(url, this.api.resolve().baseUrl, this.document.location?.origin)) {\n return ltCredentialsFetch(input, init);\n }\n const ownAuthorization = new Headers(init.headers ?? (input instanceof Request ? input.headers : undefined)).has('Authorization');\n const sentToken = !ownAuthorization && this.isJwtMode() ? await this.currentJwt() : null;\n const retryInput = input instanceof Request ? input.clone() : input;\n const response = await this.send(input, init, url, sentToken, this.isJwtMode());\n if (response.status !== 401) {\n return response;\n }\n const freshToken = ownAuthorization ? null : await this.recoverFrom401(url, sentToken);\n const final = freshToken ? await this.send(retryInput, init, url, freshToken, true) : response;\n if (final.status === 401) {\n void this.handleUnauthorized(url);\n }\n return final;\n }\n\n /**\n * Token to retry a request with after a 401, or `null` when no retry makes sense (server, auth-flow endpoint, not\n * signed in, cookie-bound path in JWT mode, no token obtainable). `sentToken` is the bearer the request carried\n * (`null` in cookie mode). Used by {@link fetchWithAuth} and `ltAuthInterceptor`.\n */\n async recoverFrom401(url: string, sentToken: string | null): Promise<string | null> {\n if (this.isServer || isLtAuthEndpoint(url) || !this.isAuthenticated()) {\n return null;\n }\n if (sentToken && ltUrlRequiresCookies(url)) {\n return null;\n }\n const current = this.jwtToken();\n if (current && current !== sentToken) {\n // A parallel request already switched or refreshed.\n return current;\n }\n return this.mintJwt();\n }\n\n /** Session-loss handling for a 401 from `url` (see `LtUnauthorizedHandler`). */\n handleUnauthorized(url?: string): Promise<boolean> {\n if (this.isServer) {\n return Promise.resolve(false);\n }\n return this.sessionLoss.handle(url);\n }\n\n /** `GET /iam/token` → store the JWT and switch to JWT mode. One request per burst. Browser only. */\n async switchToJwtMode(): Promise<boolean> {\n return !!(await this.mintJwt());\n }\n\n /** Re-mint the JWT (JWT mode only). */\n async refreshJwtToken(): Promise<boolean> {\n if (!this.isJwtMode() || !this.authState.getJwtToken()) {\n return false;\n }\n return !!(await this.mintJwt());\n }\n\n /**\n * Build an absolute app URL from `NG_PUBLIC_SITE_URL` (or the browser origin), for `redirectTo` / `callbackURL`.\n * Never derived from the request: on the server without a site URL it throws. Absolute input is returned unchanged.\n */\n absoluteAppUrl(path: string): string {\n if (isLtAbsoluteUrl(path)) {\n return path;\n }\n const origin = this.isServer ? '' : this.document.location?.origin;\n const base = (this.runtimeConfig?.siteUrl || (origin && origin !== 'null' ? origin : '')).replace(/\\/+$/, '');\n if (!base) {\n throw new Error('[@lenne.tech/ng-base] absoluteAppUrl: no site URL known. Set NG_PUBLIC_SITE_URL.');\n }\n return `${base}${path.startsWith('/') ? path : `/${path}`}`;\n }\n\n // ── plumbing for LtAuthService (@lenne.tech/ng-base/auth) ──────────────────────────────────────────────────────────\n /** Full URL of a Better Auth route (`/get-session` → `<api>/iam/get-session`). */\n authUrl(path: string): string {\n const basePath = (this.options.basePath || '/iam').replace(/\\/+$/, '');\n return this.api.buildUrl(`${basePath.startsWith('/') ? basePath : `/${basePath}`}${path}`);\n }\n\n /** Run `task` with `isLoading()` set. */\n async withLoading<T>(task: () => Promise<T>): Promise<T> {\n this.loadingState.set(true);\n try {\n return await task();\n } finally {\n this.loadingState.set(false);\n }\n }\n\n /**\n * Apply a sign-in / sign-up answer (see {@link readLtAuthAnswer}). No user is set when a second factor or an email\n * verification is pending, or when the answer proves no session. A JWT-shaped body token → JWT mode; otherwise\n * cookie mode plus a background JWT from `GET /iam/token` (cookie stays primary). `true` when a user was set.\n */\n applyAuthResult(result: unknown): boolean {\n const answer = readLtAuthAnswer(result);\n if (answer.twoFactor || answer.emailVerificationRequired || !answer.session || !answer.user) {\n return false;\n }\n if (answer.jwt) {\n this.authState.setJwtToken(answer.jwt);\n this.setUser(answer.user, 'jwt');\n return true;\n }\n this.setUser(answer.user, 'cookie');\n this.prefetchJwt();\n return true;\n }\n\n /**\n * Take a session another flow already proved (2FA verification, passkey sign-in), as after a password sign-in:\n * a JWT-shaped `token` in the answer (nest-server with `cookies: false` or `exposeTokenInBody`) → JWT mode; otherwise\n * cookie mode plus a background JWT. Any other token is Better Auth's opaque session token and is never kept. An\n * answer without user loads it from `GET /iam/get-session` (with the bearer in JWT mode). Resolves to the user, or\n * `undefined` when no session could be loaded (local state then stays signed out).\n */\n async adoptVerifiedSession(result: unknown): Promise<LtUser | undefined> {\n const { jwt, user } = readLtAuthAnswer(result);\n const mode: LtAuthMode = jwt && this.authState.setJwtToken(jwt) ? 'jwt' : 'cookie';\n if (user) {\n this.setUser(user, mode);\n if (mode === 'cookie') {\n this.prefetchJwt();\n }\n return this.userState() ?? undefined;\n }\n this.modeState.set(mode);\n const loaded = await this.loadSessionUser(mode);\n if (!loaded) {\n this.clearUser();\n }\n return loaded;\n }\n\n /**\n * Load the user via `GET /iam/get-session`, merge it with the cached one and store it, in `mode` (default: the\n * current mode; the session answer proves the session, not the transport).\n */\n async loadSessionUser(mode: LtAuthMode = this.modeState()): Promise<LtUser | undefined> {\n try {\n const data = await this.fetchSession();\n if (!data?.user) {\n return undefined;\n }\n this.setUser(this.mergeSessionUser(data.user), mode);\n if (mode === 'cookie') {\n this.prefetchJwt();\n }\n return this.userState() ?? undefined;\n } catch {\n return undefined;\n }\n }\n\n /**\n * The JWT to send (JWT mode only, else `null`), refreshed first when it expires within\n * {@link LT_JWT_REFRESH_MARGIN_MS} (one refresh per burst; the old token is kept when the refresh fails).\n */\n async currentJwt(): Promise<string | null> {\n const token = this.jwtToken();\n return this.jwtNeedsRefresh() ? ((await this.mintJwt()) ?? token) : token;\n }\n\n /** Whether the JWT of JWT mode expires within {@link LT_JWT_REFRESH_MARGIN_MS} (browser only). */\n jwtNeedsRefresh(): boolean {\n const token = this.jwtToken();\n return !!token && !this.isServer && ltJwtExpiresWithin(token, LT_JWT_REFRESH_MARGIN_MS);\n }\n\n /**\n * Navigate to `path` and keep the `redirect` query parameter of the current URL (login → 2FA must not lose where the\n * user wanted to go). A `redirect` already in `path` wins; only app-relative targets (`/…`, not `//…`) are kept.\n */\n navigateKeepingRedirect(path: string): void {\n const router = this.injector.get(Router, null);\n const current = router ? router.parseUrl(router.url).queryParams['redirect'] : new URLSearchParams(this.document.location?.search ?? '').get('redirect');\n const redirect = typeof current === 'string' && current.startsWith('/') && !current.startsWith('//') ? current : null;\n if (!redirect || /[?&]redirect=/.test(path)) {\n this.navigate(path);\n return;\n }\n this.navigate(`${path}${path.includes('?') ? '&' : '?'}redirect=${encodeURIComponent(redirect)}`);\n }\n\n /** Navigate within the app (Router when available, else a full page load in the browser). */\n navigate(url: string): void {\n const router = this.injector.get(Router, null);\n if (router) {\n void router.navigateByUrl(url).catch((error: unknown) => console.error('[@lenne.tech/ng-base] Navigation failed:', error));\n return;\n }\n if (!this.isServer) {\n this.document.location.assign(url);\n }\n }\n\n // ── internals ──────────────────────────────────────────────────────────────────────────────────────────────────────\n /**\n * Merge a session user onto the cached user of the SAME identity: nest-server-only fields survive, authorization\n * keys always reflect the session. A different / absent / id-less identity yields the session user verbatim.\n */\n private mergeSessionUser(sessionUser: LtUser): LtUser {\n const cached = this.cachedUser();\n if (!cached?.id || !sessionUser?.id || cached.id !== sessionUser.id) {\n return sessionUser;\n }\n const merged: Record<string, unknown> = { ...cached, ...sessionUser };\n for (const key of AUTHZ_KEYS) {\n if (!(key in sessionUser)) {\n delete merged[key];\n }\n }\n return merged as LtUser;\n }\n\n /** Cached user: the duplicate-tolerant cookie view, else the in-memory state. */\n private cachedUser(): LtUser | null {\n return this.authState.readUser() ?? this.userState();\n }\n\n /** `GET /iam/get-session` with the session cookie (bearer too in JWT mode); `null` without a session. */\n private async fetchSession(): Promise<{ session?: unknown; user?: LtUser } | null> {\n const response = await this.fetchWithAuth(this.authUrl('/get-session'), { method: 'GET' });\n if (!response.ok) {\n return null;\n }\n return ((await response.json().catch(() => null)) as { session?: unknown; user?: LtUser } | null) ?? null;\n }\n\n /** Mint a JWT and switch to JWT mode; one `GET /token` per burst. */\n private mintJwt(): Promise<string | null> {\n if (this.isServer) {\n return Promise.resolve(null);\n }\n if (!this.jwtMint) {\n this.jwtMint = this.requestJwt()\n .then((token) => {\n if (token && this.authState.setJwtToken(token)) {\n this.modeState.set('jwt');\n this.persistState();\n return token;\n }\n return null;\n })\n .finally(() => {\n this.jwtMint = null;\n });\n }\n return this.jwtMint;\n }\n\n /** Fetch a JWT in the background so it is ready as fallback; keeps the current mode (cookie stays primary). */\n private prefetchJwt(): void {\n if (this.isServer) {\n return;\n }\n void this.requestJwt().then((token) => {\n if (token && this.userState()) {\n this.authState.setJwtToken(token);\n }\n });\n }\n\n /**\n * `GET <auth>/token` with the session cookie and, in JWT mode, the current JWT as bearer while it is still valid\n * (with `cookies: false` the bearer is the only proof of the session); the token or `null`.\n */\n private async requestJwt(): Promise<string | null> {\n const current = this.jwtToken();\n const bearer = current && !ltJwtExpiresWithin(current, 0) ? current : null;\n try {\n const response = await fetch(this.authUrl('/token'), {\n credentials: 'include',\n headers: bearer ? { Authorization: `Bearer ${bearer}` } : undefined,\n method: 'GET',\n });\n if (!response.ok) {\n return null;\n }\n const data = (await response.json()) as { token?: unknown } | null;\n return stringOrNull(data?.token);\n } catch {\n return null;\n }\n }\n\n private async loadFeatures(): Promise<LtAuthFeatures> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), FEATURES_TIMEOUT_MS);\n try {\n const response = await fetch(this.authUrl('/features'), { headers: { accept: 'application/json' }, signal: controller.signal });\n if (response.ok) {\n const body: unknown = await response.json();\n if (body && typeof body === 'object' && !Array.isArray(body)) {\n this.featuresState.set(body as LtAuthFeatures);\n this.featuresLoaded = true;\n if (this.isServer) {\n this.transferState.set(LT_AUTH_FEATURES_STATE_KEY, body as LtAuthFeatures);\n }\n }\n }\n } catch {\n // keep the last known flags\n } finally {\n clearTimeout(timer);\n }\n return this.featuresState();\n }\n\n private send(input: RequestInfo | URL, init: RequestInit, url: string, token: string | null, jwtMode: boolean): Promise<Response> {\n const headers = new Headers(init.headers ?? (input instanceof Request ? input.headers : undefined));\n if (token && !headers.has('Authorization')) {\n headers.set('Authorization', `Bearer ${token}`);\n }\n const credentials: RequestCredentials = init.credentials ?? (jwtMode && !ltUrlRequiresCookies(url) ? 'omit' : 'include');\n return fetch(input, { ...init, credentials, headers });\n }\n\n private persistState(): void {\n this.authState.writeState({ authMode: this.modeState(), user: this.userState() });\n }\n\n private currentUrl(): string | null {\n const router = this.injector.get(Router, null);\n if (router) {\n return router.url;\n }\n const location = this.document.location;\n return location ? `${location.pathname}${location.search}${location.hash}` : null;\n }\n}\n\n/** `user.role === role` or `user.roles` (array) contains it; malformed shapes → `false`. */\nexport function hasLtRole(user: LtUser | null | undefined, role: string): boolean {\n if (!user || !role) {\n return false;\n }\n return user.role === role || (Array.isArray(user.roles) && user.roles.includes(role));\n}\n\nfunction stringOrNull(value: unknown): string | null {\n return typeof value === 'string' && value ? value : null;\n}\n\nasync function runHook(hook: () => Promise<void> | void, name: string): Promise<void> {\n try {\n await hook();\n } catch (error) {\n console.error(`[@lenne.tech/ng-base] A sign-out hook (${name}) failed:`, error);\n }\n}\n\nfunction withTimeout<T>(promise: Promise<T>, ms: number): Promise<T | null> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timeout = new Promise<null>((resolve) => {\n timer = setTimeout(() => resolve(null), ms);\n });\n return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));\n}\n","import { isPlatformServer } from '@angular/common';\nimport { inject, makeStateKey, PLATFORM_ID, Service, signal, TransferState } from '@angular/core';\nimport { LT_FETCH, LtApiUrlService, ltSha256 } from '@lenne.tech/ng-base';\n\nimport type { LtSystemSetupInput, LtSystemSetupStatus } from './auth-types';\n\n/** TransferState key of the setup status read during SSR. */\nexport const LT_SYSTEM_SETUP_STATE_KEY = makeStateKey<LtSystemSetupStatus>('lt-system-setup');\n\n/** Header that carries the setup token. Sent unhashed and never in the body (the server compares it as given). */\nexport const LT_SYSTEM_SETUP_TOKEN_HEADER = 'X-System-Setup-Token';\n\n/**\n * Error thrown by `initSetup()` for a non-2xx answer. `data` is the response body, so\n * `LtErrorTranslationService.translateError(error)` reads its `#LTNS_xxxx` code (`LTNS_0053` HTTP setup disabled,\n * `LTNS_0054` wrong token).\n */\nexport interface LtSystemSetupError extends Error {\n data: unknown;\n status: number;\n statusCode: number;\n}\n\n/**\n * First-admin setup of nest-server (`/system-setup/*`), the counterpart of `useSystemSetup()`.\n * Status is fetched once and transferred SSR → browser.\n */\n@Service()\nexport class LtSystemSetupService {\n private readonly api = inject(LtApiUrlService);\n private readonly fetch = inject(LT_FETCH);\n private readonly transferState = inject(TransferState);\n private readonly isServer = isPlatformServer(inject(PLATFORM_ID));\n private readonly transferred = this.isServer ? null : this.transferState.get(LT_SYSTEM_SETUP_STATE_KEY, null);\n private readonly needsSetupState = signal<boolean | null>(this.transferred ? this.transferred.needsSetup : null);\n private readonly httpInitEnabledState = signal<boolean | null>(this.transferred ? this.transferred.httpInitEnabled !== false : null);\n private readonly httpInitTokenRequiredState = signal<boolean | null>(this.transferred ? this.transferred.httpInitTokenRequired === true : null);\n private statusRequest: Promise<LtSystemSetupStatus> | null = null;\n\n /** `true` when no admin exists yet; `null` until checked. */\n readonly needsSetup = this.needsSetupState.asReadonly();\n /** Whether HTTP init is open (nest-server ≥ 12.4; `true` for older APIs); `null` until checked. */\n readonly httpInitEnabled = this.httpInitEnabledState.asReadonly();\n /** Whether HTTP init requires `X-System-Setup-Token`; `null` until checked. */\n readonly httpInitTokenRequired = this.httpInitTokenRequiredState.asReadonly();\n\n /** `GET /system-setup/status`. Errors/404 → `needsSetup = false`. SSR without API URL → `false` without fetching. */\n checkSetupStatus(): Promise<LtSystemSetupStatus> {\n // Without an API URL the path stays relative; on the server it would never reach the backend. Leave the status\n // undecided, the browser checks once it can resolve the URL.\n if (this.isServer && this.api.resolve().missing) {\n this.api.reportMissing();\n return Promise.resolve({ needsSetup: false });\n }\n if (!this.statusRequest) {\n this.statusRequest = this.loadStatus().finally(() => {\n this.statusRequest = null;\n });\n }\n return this.statusRequest;\n }\n\n /**\n * `POST /system-setup/init` with `{ email, password: sha256, name }`; the token (trimmed, unhashed) goes into\n * `X-System-Setup-Token`. On 403 the status is re-read and the error rethrown (`LTNS_0053` disabled, `LTNS_0054` wrong token).\n */\n async initSetup(input: LtSystemSetupInput): Promise<void> {\n const url = this.api.buildUrl('/system-setup/init');\n const password = await ltSha256(input.password);\n // A header value carries no surrounding whitespace; a pasted line break would make fetch throw.\n const token = input.token?.trim();\n const headers: Record<string, string> = { accept: 'application/json', 'content-type': 'application/json' };\n if (token) {\n headers[LT_SYSTEM_SETUP_TOKEN_HEADER] = token;\n }\n const response = await this.fetch(url, { body: JSON.stringify({ email: input.email, name: input.name, password }), headers, method: 'POST' });\n if (!response.ok) {\n const error = await toSetupError(response);\n if (response.status === 403) {\n // The API may have been restarted with another configuration since the status was read.\n await this.checkSetupStatus();\n }\n throw error;\n }\n this.needsSetupState.set(false);\n }\n\n private async loadStatus(): Promise<LtSystemSetupStatus> {\n try {\n const response = await this.fetch(this.api.buildUrl('/system-setup/status'), { headers: { accept: 'application/json' } });\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`);\n }\n const data = ((await response.json()) ?? {}) as Record<string, unknown>;\n const status: LtSystemSetupStatus = {\n // Missing or malformed reads as released: an API before 12.4.0 accepts the HTTP setup, its 403 is the boundary.\n httpInitEnabled: typeof data['httpInitEnabled'] === 'boolean' ? data['httpInitEnabled'] : true,\n httpInitTokenRequired: data['httpInitTokenRequired'] === true,\n needsSetup: data['needsSetup'] === true,\n };\n this.needsSetupState.set(status.needsSetup);\n this.httpInitEnabledState.set(status.httpInitEnabled ?? true);\n this.httpInitTokenRequiredState.set(status.httpInitTokenRequired ?? false);\n if (this.isServer) {\n this.transferState.set(LT_SYSTEM_SETUP_STATE_KEY, status);\n }\n return status;\n } catch {\n // Error or 404: the endpoint does not exist (older API) → no setup flow.\n this.needsSetupState.set(false);\n return { needsSetup: false };\n }\n }\n}\n\nasync function toSetupError(response: Response): Promise<LtSystemSetupError> {\n const text = await response.text().catch(() => '');\n let data: unknown = text;\n try {\n data = text ? JSON.parse(text) : null;\n } catch {\n // keep the text\n }\n const bodyMessage = data && typeof data === 'object' ? (data as { message?: unknown }).message : data;\n const message = typeof bodyMessage === 'string' && bodyMessage ? bodyMessage : `[POST] /system-setup/init: ${response.status} ${response.statusText}`.trim();\n return Object.assign(new Error(message), { data, status: response.status, statusCode: response.status }) as LtSystemSetupError;\n}\n","import { inject } from '@angular/core';\nimport { Router, type CanActivateChildFn, type CanActivateFn, type RouterStateSnapshot, type UrlTree } from '@angular/router';\n\nimport { LT_AUTH_OPTIONS } from './auth-options';\nimport { hasLtRole, LtAuthStore } from './auth-store';\nimport { ltPathOf } from './auth-urls';\nimport { LtSystemSetupService } from './system-setup';\n\n/**\n * Requires a signed-in user (auth-state cookie; on the server the request cookie). Otherwise redirects to\n * `loginPath?redirect=<url>`. UI gating only: the backend authorises every request itself.\n */\nexport const ltAuthGuard: CanActivateFn & CanActivateChildFn = (_route, state) => (inject(LtAuthStore).isAuthenticated() ? true : loginTree(state));\n\n/** Only for signed-out users (login, register…); a signed-in user is redirected to `redirectTo` (default `/app`). */\nexport function ltGuestGuard(redirectTo = '/app'): CanActivateFn & CanActivateChildFn {\n return () => (inject(LtAuthStore).isAuthenticated() ? inject(Router).parseUrl(redirectTo) : true);\n}\n\n/** Requires one of the roles (`role` or `roles`); signed-out → login, missing role → `redirectTo` (default `/`). */\nexport function ltRoleGuard(roles: string | string[], redirectTo = '/'): CanActivateFn & CanActivateChildFn {\n const wanted = Array.isArray(roles) ? roles : [roles];\n return (_route, state) => {\n const auth = inject(LtAuthStore);\n if (!auth.isAuthenticated()) {\n return loginTree(state);\n }\n const user = auth.user();\n return wanted.some((role) => hasLtRole(user, role)) ? true : inject(Router).parseUrl(redirectTo);\n };\n}\n\n/**\n * First-admin setup redirect (enable with `provideLtAuth({ systemSetup: { enabled: true } })`), put it on the root\n * route as `canActivateChild`. `needsSetup && httpInitEnabled !== false` → setup page; setup done but on the setup\n * page → login page; errors never redirect.\n */\nexport const ltSystemSetupGuard: CanActivateFn & CanActivateChildFn = async (_route, state) => {\n const options = inject(LT_AUTH_OPTIONS);\n if (!options.systemSetup.enabled) {\n return true;\n }\n const setup = inject(LtSystemSetupService);\n const router = inject(Router);\n try {\n if (setup.needsSetup() === null) {\n await setup.checkSetupStatus();\n }\n const path = ltPathOf(state.url);\n const setupPath = options.systemSetup.setupPath;\n // Only an explicit \"HTTP init not released\" suppresses the redirect (the page shows a notice instead).\n if (setup.needsSetup() === true && setup.httpInitEnabled() !== false && path !== setupPath) {\n return router.parseUrl(setupPath);\n }\n if (setup.needsSetup() === false && path === setupPath) {\n return router.parseUrl(options.loginPath);\n }\n } catch {\n // errors never redirect\n }\n return true;\n};\n\nfunction loginTree(state: RouterStateSnapshot): UrlTree {\n const options = inject(LT_AUTH_OPTIONS);\n return inject(Router).createUrlTree([options.loginPath], { queryParams: { redirect: state.url } });\n}\n","import { DOCUMENT, isPlatformServer } from '@angular/common';\nimport { HttpErrorResponse, type HttpEvent, type HttpHandlerFn, type HttpInterceptorFn, type HttpRequest } from '@angular/common/http';\nimport { inject, PLATFORM_ID, Service } from '@angular/core';\nimport { LtApiUrlService } from '@lenne.tech/ng-base';\nimport { catchError, from, type Observable, switchMap, throwError } from 'rxjs';\n\nimport { LtAuthStore } from './auth-store';\nimport { isLtApiRequest } from './auth-urls';\n\n/**\n * Session-loss detection shared by `ltAuthInterceptor` (HttpClient) and the auth-aware `LT_FETCH` (fetch, SSE, sync).\n *\n * On a 401 outside the auth endpoints and public pages while the state says signed in, it probes\n * `GET /iam/get-session` (positive verdict cached 1 s): dead → `clearUser()` and a redirect to\n * `loginPath?redirect=<current url>` (guard held 1 s), alive → nothing (in JWT mode the JWT is re-minted).\n * A probe without verdict (network error, 429, 5xx) never logs out. Server: no-op.\n */\n@Service()\nexport class LtUnauthorizedHandler {\n private readonly auth = inject(LtAuthStore);\n\n /** Handle a 401 for `url`. Resolves to `true` when the session was found dead and the user was redirected. */\n handle(url?: string): Promise<boolean> {\n return this.auth.handleUnauthorized(url);\n }\n}\n\n/**\n * `HttpClient` interceptor for API requests (URLs under the configured API base):\n * sends credentials, adds `Authorization: Bearer` in JWT mode (refreshed first when it is about to expire), retries once after a cookie → JWT switch or a JWT\n * refresh, and hands an unrecoverable 401 to {@link LtUnauthorizedHandler}. A request that brings its own\n * `Authorization` header keeps it and is not retried. Other URLs and server-side requests pass through unchanged.\n *\n * @example provideHttpClient(withInterceptors([ltAuthInterceptor]))\n */\nexport const ltAuthInterceptor: HttpInterceptorFn = (request: HttpRequest<unknown>, next: HttpHandlerFn): Observable<HttpEvent<unknown>> => {\n if (isPlatformServer(inject(PLATFORM_ID))) {\n return next(request);\n }\n const api = inject(LtApiUrlService);\n if (!isLtApiRequest(request.url, api.resolve().baseUrl, inject(DOCUMENT).location?.origin)) {\n return next(request);\n }\n const auth = inject(LtAuthStore);\n const handler = inject(LtUnauthorizedHandler);\n const url = request.urlWithParams;\n const ownAuthorization = request.headers.has('Authorization');\n const authorize = (token: string | null): HttpRequest<unknown> =>\n request.clone(token && !ownAuthorization ? { setHeaders: { Authorization: `Bearer ${token}` }, withCredentials: true } : { withCredentials: true });\n const reportAndRethrow = (error: unknown): Observable<never> => {\n if (isUnauthorized(error)) {\n void handler.handle(url);\n }\n return throwError(() => error);\n };\n const send = (sentToken: string | null): Observable<HttpEvent<unknown>> =>\n next(authorize(sentToken)).pipe(\n catchError((error: unknown) => {\n if (!isUnauthorized(error) || ownAuthorization) {\n return reportAndRethrow(error);\n }\n return from(auth.recoverFrom401(url, sentToken)).pipe(switchMap((token) => (token ? next(authorize(token)).pipe(catchError(reportAndRethrow)) : reportAndRethrow(error))));\n }),\n );\n // In JWT mode a token close to its expiry is refreshed before the request goes out; otherwise it goes out at once.\n return !ownAuthorization && auth.jwtNeedsRefresh() ? from(auth.currentJwt()).pipe(switchMap(send)) : send(ownAuthorization ? null : auth.jwtToken());\n};\n\nfunction isUnauthorized(error: unknown): boolean {\n return error instanceof HttpErrorResponse && error.status === 401;\n}\n","import { isPlatformServer } from '@angular/common';\nimport { inject, makeEnvironmentProviders, PLATFORM_ID, provideAppInitializer, type EnvironmentProviders } from '@angular/core';\nimport { LT_FETCH, type LtFetch } from '@lenne.tech/ng-base';\n\nimport { LT_AUTH_INPUT_OPTIONS, type LtAuthOptions } from './auth-options';\nimport { LtAuthStore } from './auth-store';\n\n/**\n * Auth providers: options, the auth-aware `LT_FETCH` (Bearer in JWT mode, JWT switch/refresh, session-loss handling)\n * used by error translation, AI, sync and the Better Auth client, session validation and feature flags at startup\n * (features fetched on the server and transferred), and the user restored from the auth-state cookie.\n *\n * Startup: the server fetches the feature flags (bounded, anonymous) before rendering and transfers them; the browser\n * takes them from `TransferState` (fetching only when nothing was transferred) and validates the session in the\n * background, so the first render is never held up by the backend.\n *\n * Add `ltAuthInterceptor` to `provideHttpClient(withInterceptors([...]))` for `HttpClient` calls (generated API client).\n */\nexport function provideLtAuth(options: LtAuthOptions = {}): EnvironmentProviders {\n return makeEnvironmentProviders([\n { provide: LT_AUTH_INPUT_OPTIONS, useValue: options },\n {\n provide: LT_FETCH,\n useFactory: (): LtFetch => {\n const auth = inject(LtAuthStore);\n return (input, init) => auth.fetchWithAuth(input, init);\n },\n },\n provideAppInitializer(() => {\n const auth = inject(LtAuthStore);\n if (isPlatformServer(inject(PLATFORM_ID))) {\n return auth.ensureFeatures().then(() => undefined);\n }\n void auth.ensureFeatures();\n void auth.validateSession();\n return undefined;\n }),\n ]);\n}\n"],"mappings":";;;;;;;AAkEA,MAAa,+BAA+B;AAE5C,MAAa,8BAA8B;AAG3C,MAAa,mBAA0C;CACrD,UAAU;CACV,aAAa;EAAE,OAAO;EAA8B,OAAO;CAA2B;CACtF,aAAa;CACb,eAAe;CACf,iBAAiB;CACjB,aAAa;EAAE,SAAS;EAAM,aAAa,CAAA;CAAE;CAC7C,WAAW;CACX,SAAS,CAAA;CACT,aAAa;EAAE,SAAS;EAAO,WAAW;CAAa;CACvD,uBAAuB;AACxB;AAGD,MAAa,wBAAwB,IAAI,eAA8B,yBAAyB,EAAE,gBAAgB,CAAA,GAAG,CAAE;AAMvH,MAAa,kBAAkB,IAAI,eAAsC,mBAAmB,EAC1F,eAAc;CACZ,MAAM,UAAU,OAAO,mBAAmB,EAAE,UAAU,KAAI,CAAE;CAC5D,MAAM,SAAS,sBAAsB,kBAAkB,OAAO,qBAAqB,GAAG,SAAS,GAAG,IAAI;CACtG,OAAO;EAAE,GAAG;EAAQ,aAAa,yBAAyB,OAAO,aAAa,SAAS,YAAY;CAAC;AACtG,EACD,CAAA;AAGD,SAAgB,sBAAsB,KAAY;CAChD,OAAO,OAAO,QAAQ,WAAW,IAAI,KAAI,CAAE,CAAC,QAAQ,oBAAoB,EAAE,IAAI;AAChF;AAMA,SAAgB,yBAAyB,YAAkD,cAAsB;CAC/G,MAAM,SAAS,sBAAsB,YAAY;CAGjD,OAAO;EAAE,OAFK,YAAY,SAAS,WAAW,UAAA,kBAAyC,WAAW,QAAQ,SAAS,GAAG,OAAM,eAAgB;EAE5H,OADF,YAAY,SAAS,WAAW,UAAA,iBAAwC,WAAW,QAAQ,SAAS,GAAG,OAAM,cAAe;CACrH;AACvB;ACzGA,MAAa,yBAAyB;AAGtC,MAAM,sBAAsB;CAAC;CAA6B;CAAuB;CAAsC;CAAiB;AAAS;AACjJ,MAAM,2BAA2B;CAAC;CAAK;CAAQ;CAAY;AAAM;AASjE,SAAgB,mBAAmB,cAAsB,iBAAuB;CAC9E,MAAM,SAAS,GAAG,gBAAe;CACjC,IAAI,WAA+B;CACnC,KAAK,MAAM,UAAU,gBAAgB,GAAA,CAAI,MAAM,MAAM,GAAG;EACtD,IAAI,CAAC,MAAM,WAAW,MAAM,GAC1B;EAEF,IAAI;GACF,MAAM,QAAQ,KAAK,MAAM,mBAAmB,MAAM,MAAM,OAAO,MAAM,CAAC,CAAC;GACvE,IAAI,OAAO,MACT,OAAO;GAET,IAAI,OACF,WAAW;EAEf,QAAQ,CAER;CACF;CACA,OAAO;AACT;AAOA,IAAa,qBAAb,MAAa,mBAAkB;CACZ,UAAU,OAAO,eAAe;CAChC,UAAU,OAAO,eAAe;CAChC,WAAW,OAAO,QAAQ;CAC1B,WAAW,iBAAiB,OAAO,WAAW,CAAC;CAGhE,IAAI,cAAW;EACb,OAAO,KAAK,QAAQ;CACtB;CAGA,YAAS;EACP,OAAO,mBAAmB,KAAK,QAAQ,IAAG,GAAI,KAAK,YAAY,KAAK;CACtE;CAGA,WAAW,OAAkB;EAC3B,IAAI,KAAK,UACP;EAEF,MAAM,UAAU,KAAK,UAAU,KAAK;EACpC,IAAI,mBAAmB,OAAO,CAAC,CAAC,SAAS,QAAQ,OAAO,cAAc,eAAe,WACnF,QAAQ,KAAK,kDAAkD,mBAAmB,OAAO,CAAC,CAAC,OAAM,yCAA0C;EAE7I,KAAK,QAAQ,IAAI,KAAK,YAAY,OAAO,SAAS,EAAE,QAAQ,uBAAsB,CAAE;CACtF;CAGA,cAAW;EACT,OAAO,KAAK,UAAS,CAAE,EAAE,aAAa,QAAQ,QAAQ;CACxD;CAGA,YAAY,MAAgB;EAC1B,MAAM,UAAU,KAAK,UAAS;EAC9B,KAAK,WAAW;GAAE,UAAU;GAAM,MAAM,SAAS,QAAQ;EAAI,CAAE;CACjE;CAOA,cAAW;EACT,IAAI,KAAK,UACP,OAAO;EAET,IAAI,UAAU;EACd,KAAK,MAAM,OAAO,KAAK,QAAQ,OAAO,KAAK,YAAY,KAAK,GAAG;GAC7D,MAAM,QAAQ,WAAW,GAAG;GAC5B,IAAI,cAAc,KAAK,GAAG;IACxB,IAAI,QAAQ,OAAO;KACjB,KAAK,iBAAiB,KAAK,YAAY,KAAK;KAC5C,KAAK,QAAQ,IAAI,KAAK,YAAY,OAAO,OAAO,EAAE,QAAQ,uBAAsB,CAAE;IACpF;IACA,OAAO;GACT;GACA,YAAY,CAAC,CAAC;EAChB;EACA,IAAI,SACF,KAAK,iBAAiB,KAAK,YAAY,KAAK;EAE9C,OAAO;CACT;CAOA,YAAY,OAAoB;EAC9B,IAAI,KAAK,UACP,OAAO;EAET,IAAI,CAAC,OAAO;GACV,KAAK,iBAAiB,KAAK,YAAY,KAAK;GAC5C,OAAO;EACT;EACA,IAAI,CAAC,cAAc,KAAK,GAAG;GACzB,KAAK,iBAAiB,KAAK,YAAY,KAAK;GAC5C,QAAQ,KAAK,0EAA0E;GACvF,OAAO;EACT;EAGA,KAAK,QAAQ,IAAI,KAAK,YAAY,OAAO,OAAO,EAAE,QAAQ,uBAAsB,CAAE;EAClF,OAAO;CACT;CAMA,kBAAe;EACb,IAAI,KAAK,UACP,OAAO;EAGT,IAAI,CADW,KAAK,QAAQ,OAAO,KAAK,YAAY,KAAK,CAAC,CAAC,IAAI,UAC1D,CAAA,CAAO,MAAM,UAAU,SAAS,CAAC,cAAc,KAAK,CAAC,GACxD,OAAO;EAET,KAAK,iBAAiB,KAAK,YAAY,KAAK;EAC5C,MAAM,QAAQ,KAAK,UAAS;EAC5B,IAAI,OAAO,aAAa,OACtB,KAAK,WAAW;GAAE,UAAU;GAAU,MAAM,MAAM;EAAI,CAAE;EAE1D,OAAO;CACT;CAGA,kBAAe;EACb,OAAO,CAAC,CAAC,KAAK,UAAS,CAAE,EAAE;CAC7B;CAGA,WAAQ;EACN,OAAO,KAAK,UAAS,CAAE,EAAE,QAAQ;CACnC;CAMA,mBAAgB;EACd,IAAI,KAAK,UACP;EAEF,KAAK,iBAAiB,KAAK,YAAY,KAAK;EAC5C,KAAK,iBAAiB,KAAK,YAAY,KAAK;EAC5C,KAAK,MAAM,QAAQ,qBACjB,KAAK,MAAM,QAAQ,0BACjB,KAAK,QAAQ,OAAO,MAAM,EAAE,KAAI,CAAE;CAGxC;CAEQ,iBAAiB,MAAY;EACnC,MAAM,OAAO,KAAK,SAAS,UAAU;EACrC,KAAK,QAAQ,OAAO,MAAM,EAAE,MAAM,IAAG,CAAE;EACvC,IAAI,MACF,KAAK,QAAQ,OAAO,MAAM;GAAE,QAAQ;GAAM,MAAM;EAAG,CAAE;CAEzD;CA/IW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAkB,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAlB,OAAA,QAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;CAAkB,CAAA;;AAAlB,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAkB,YAAA,CAAA,EAD9B,MAAA,Q;;AAoJD,SAAgB,cAAc,OAAc;CAC1C,OAAO,OAAO,UAAU,YAAY,MAAM,WAAW,KAAK,KAAK,MAAM,MAAM,GAAG,CAAC,CAAC,WAAW,KAAK,MAAM,MAAM,GAAG,CAAC,CAAC,OAAO,SAAS,KAAK,SAAS,CAAC;AAClJ;AAMA,SAAgB,mBAAmB,OAAe,UAAkB,MAAc,KAAK,IAAG,GAAE;CAC1F,MAAM,UAAU,MAAM,MAAM,GAAG,CAAC,CAAC;CACjC,IAAI,CAAC,SACH,OAAO;CAET,IAAI;EACF,MAAM,SAAS,QAAQ,QAAQ,MAAM,GAAG,CAAC,CAAC,QAAQ,MAAM,GAAG;EAC3D,MAAM,SAAS,KAAK,MAAM,KAAK,OAAO,OAAO,OAAO,UAAW,IAAK,OAAO,SAAS,KAAM,GAAI,GAAG,CAAC,CAAC;EACnG,OAAO,OAAO,OAAO,QAAQ,YAAY,OAAO,MAAM,MAAO,OAAO;CACtE,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAS,WAAW,KAAW;CAC7B,IAAI,CAAC,KACH,OAAO;CAET,IAAI;EACF,MAAM,SAAkB,KAAK,MAAM,GAAG;EACtC,OAAO,OAAO,WAAW,YAAY,SAAS,SAAS;CACzD,QAAQ;EACN,OAAO;CACT;AACF;AClOA,MAAa,uBAA0C;CACrD;CACA;CACA;CACA;CACA;CACA;CACA;AACD;AAGD,MAAa,6BAAgD;CAC3D;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACD;AAOD,MAAa,uBAA0C;CAAC;CAAa;CAAgB;CAAS;AAAc;AAG5G,SAAgB,iBAAiB,KAAW;CAC1C,OAAO,2BAA2B,MAAM,aAAa,IAAI,SAAS,QAAQ,CAAC;AAC7E;AAGA,SAAgB,qBAAqB,KAAW;CAC9C,OAAO,qBAAqB,MAAM,SAAS,IAAI,SAAS,IAAI,CAAC;AAC/D;AAGA,SAAgB,aAAa,OAAwB;CACnD,IAAI,OAAO,UAAU,UACnB,OAAO;CAET,OAAO,iBAAiB,MAAM,MAAM,OAAO,MAAM;AACnD;AAGA,SAAgB,SAAS,KAAW;CAClC,OAAO,IAAI,MAAM,MAAM,CAAC,CAAC,MAAM;AACjC;AAYA,SAAgB,eAAe,KAAa,SAAiB,QAAsB;CACjF,IAAI,WAAW,UAAU,KAAK,OAAO,GACnC,OAAO;CAET,IAAI,OAAO;CACX,IAAI,UAAU,WAAW,UAAU,UAAU,KAAK,MAAM,GACtD,OAAO,IAAI,MAAM,OAAO,MAAM,KAAK;CAErC,IAAI,CAAC,KAAK,WAAW,GAAG,KAAK,KAAK,WAAW,IAAI,GAC/C,OAAO;CAET,IAAI,UAAU,MAAM,MAAM,GACxB,OAAO;CAET,IAAI,CAAC,SACH,OAAO;CAET,OAAO,QAAQ,WAAW,GAAG,KAAK,UAAU,MAAM,OAAO;AAC3D;AAEA,SAAS,UAAU,OAAe,QAAc;CAC9C,IAAI,CAAC,MAAM,WAAW,MAAM,GAC1B,OAAO;CAET,MAAM,OAAO,MAAM,OAAO,OAAO,MAAM;CACvC,OAAO,SAAS,MAAM,SAAS,OAAO,SAAS,OAAO,SAAS,OAAO,OAAO,SAAS,GAAG;AAC3F;AChGA,MAAa,0BAA0B;AAEvC,MAAa,2BAA2B;AAoCxC,SAAgB,4BAA4B,MAAyB,QAA2B;CAC9F,IAAI,WAAW;CACf,IAAI,aAAa;CAEjB,MAAM,sBAA8B;EAClC,MAAM,UAAU,KAAK,WAAU;EAC/B,IAAI,CAAC,SACH,OAAO;EAET,MAAM,OAAO,SAAS,OAAO;EAC7B,OAAO,OAAO,YAAY,MAAM,eAAe,KAAK,WAAW,UAAU,CAAC;CAC5E;CAEA,MAAM,QAAQ,YAAoC;EAChD,IAAI,KAAK,IAAG,IAAK,YACf,OAAO;EAET,IAAI;EACJ,IAAI;GACF,MAAM,WAAW,MAAM,KAAK,aAAY;GACxC,IAAI,SAAS,WAAW,OAAO,SAAS,WAAW,KACjD,OAAO;GAET,IAAI,CAAC,SAAS,IACZ,OAAO;GAET,MAAM,OAAQ,MAAM,SAAS,KAAI,CAAE,CAAC,YAAY,IAAI;GACpD,UAAU,QAAQ,SAAS,KAAK,QAAQ,KAAK,QAAQ;GAErD,IAAI,WAAW,KAAK,UAAS,GAC3B,MAAM,KAAK,gBAAe,CAAE,CAAC,YAAY,KAAK;EAElD,QAAQ;GACN,OAAO;EACT;EACA,IAAI,SACF,aAAa,KAAK,IAAG,IAAK;EAE5B,OAAO;CACT;CAEA,OAAO,EACL,MAAM,OAAO,KAAY;EACvB,IAAI,CAAC,OAAO,WAAW,YAAa,OAAO,iBAAiB,GAAG,KAAM,cAAa,KAAM,CAAC,KAAK,gBAAe,GAC3G,OAAO;EAET,WAAW;EACX,IAAI,YAAY;EAChB,IAAI;GAEF,IAAI,MADgB,MAAK,MACX,OACZ,OAAO;GAET,YAAY;GACZ,KAAK,UAAS;GACd,MAAM,UAAU,KAAK,WAAU;GAC/B,MAAM,WAAW,WAAW,SAAS,OAAO,MAAM,OAAO,YAAY,aAAa,mBAAmB,OAAO,MAAM;GAClH,KAAK,SAAS,GAAG,OAAO,YAAY,UAAU;GAC9C,OAAO;EACT,SAAS,OAAO;GACd,QAAQ,MAAM,gDAAgD,KAAK;GACnE,OAAO;EACT,UAAU;GACR,IAAI,WACF,iBAAgB;IACd,WAAW;GACb,GAAG,wBAAwB;QAE3B,WAAW;EAEf;CACF,EACD;AACH;AC3FA,MAAa,yBAAyB,IAAI,eAAoC,0BAA0B,EAAE,eAAe,CAAA,EAAE,CAAE;AAG7H,MAAa,6BAA6B,aAA6B,kBAAkB;AAGzF,MAAa,iCAAiC;AAS9C,MAAM,sBAAsB;AAM5B,MAAM,aAAa;CAAC;CAAc;CAAa;CAAU;CAAiB;CAAQ;CAAS;AAAkB;AAwB7G,SAAgB,iBAAiB,QAAe;CAC9C,MAAM,QAAS,UAAU,OAAO,WAAW,WAAW,SAAS,CAAA;CAC/D,MAAM,OAAQ,MAAM,WAAW,OAAO,MAAM,YAAY,WAAW,MAAM,UAAU,CAAA;CACnF,MAAM,QAAQ,QAAyB,KAAK,QAAQ,MAAM;CAC1D,MAAM,QAAQ,KAAK,OAAO;CAC1B,MAAM,OAAO,KAAK,MAAM;CACxB,MAAM,gBAAgB,KAAK,SAAS;CACpC,MAAM,SAAS,CAAC,CAAC,MAAM,YAAY,KAAK,SAAS,MAAM;CACvD,MAAM,SAAS,KAAK,SAAS,MAAM,QAAS,OAAO,UAAU,YAAY,CAAC,CAAC,SAAW,CAAC,CAAC,iBAAiB,OAAO,kBAAkB;CAClI,OAAO;EACL,2BAA2B,KAAK,2BAA2B,MAAM;EACjE,KAAK,cAAc,KAAK,IAAI,QAAQ;EACpC,SAAS,UAAU,CAAC;EACpB,WAAW,KAAK,mBAAmB,MAAM,QAAQ,KAAK,mBAAmB,MAAM;EAC/E,MAAM,QAAQ,OAAO,SAAS,WAAY,OAAuB;CAClE;AACH;AAiBA,IAAa,cAAb,MAAa,YAAW;CACL,MAAM,OAAO,eAAe;CAC5B,YAAY,OAAO,kBAAkB;CACrC,UAAU,OAAO,eAAe;CAChC,gBAAgB,OAAO,mBAAmB,EAAE,UAAU,KAAI,CAAE;CAC5D,gBAAgB,OAAO,aAAa;CACpC,WAAW,OAAO,QAAQ;CAC1B,WAAW,OAAO,QAAQ;CAC1B,eAAe,OAAO,sBAAsB;CAC5C,WAAW,iBAAiB,OAAO,WAAW,CAAC;CAI/C,mBAAmB,KAAK,UAAU,gBAAe;CACjD,eAAe,KAAK,UAAU,UAAS;CACvC,sBAAsB,KAAK,WAAW,OAAO,KAAK,cAAc,IAAI,4BAA4B,IAAI;CACpG,YAAY,OAAsB,KAAK,cAAc,QAAQ,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;CAClE,YAAY,OAAmB,KAAK,cAAc,aAAa,QAAQ,QAAQ,UAAS,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;CACxF,eAAe,OAAO,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,eAAA,CAAA,+BAAA,CAAA,CAAA;CAC5B,gBAAgB,OAAuB,KAAK,uBAAuB,CAAA,GAAG,GAAA,YAAA,CAAA,EAAA,WAAA,gBAAA,CAAA,+BAAA,CAAA,CAAA;CAC/E,iBAAiB,KAAK,wBAAwB;CAC9C,kBAAkD;CAElD,UAAyC;CAChC,cAAc,4BAC7B;EACE,iBAAiB,KAAK,UAAS;EAC/B,kBAAkB,KAAK,WAAU;EACjC,uBAAuB,KAAK,gBAAe;EAC3C,iBAAiB,KAAK,UAAS;EAC/B,WAAW,QAAQ,KAAK,SAAS,GAAG;EACpC,oBAAoB,KAAK,cAAc,KAAK,QAAQ,cAAc,GAAG,EAAE,QAAQ,MAAK,CAAE;EACtF,uBAAuB,KAAK,gBAAe;CAC5C,GACD;EACE,SAAS,KAAK,QAAQ,YAAY;EAClC,WAAW,KAAK,QAAQ;EACxB,aAAa,CACX,mBAAG,IAAI,IAAI;GACT,GAAG;GACH,GAAG,KAAK,QAAQ,YAAY;GAC5B,KAAK,QAAQ;GACb,KAAK,QAAQ;GACb,KAAK,QAAQ,YAAY;EAC1B,CAAA,CACF;CACF,CAAA;CAIM,OAA8B,KAAK,UAAU,WAAU;CAEvD,kBAAkB,eAAe,CAAC,CAAC,KAAK,UAAS,GAAG,GAAA,YAAA,CAAA,EAAA,WAAA,kBAAA,CAAA,+BAAA,CAAA,CAAA;CAEpD,WAA+B,KAAK,UAAU,WAAU;CAExD,YAAY,eAAe,KAAK,UAAS,MAAO,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;CAEtD,YAA6B,KAAK,aAAa,WAAU;CAEzD,UAAU,eAAe,UAAU,KAAK,UAAS,GAAI,OAAO,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CAE9D,eAAe,eAAe,CAAC,CAAC,KAAK,UAAS,CAAE,EAAE,kBAAiB,GAAA,YAAA,CAAA,EAAA,WAAA,eAAA,CAAA,+BAAA,CAAA,CAAA;CAEnE,WAAmC,KAAK,cAAc,WAAU;CAEhE,mBAAmB,eAAe,KAAK,QAAQ,iBAAiB,CAAC,CAAC,KAAK,cAAa,CAAE,CAAC,cAAc,OAAO,WAAW,wBAAwB,aAAY,GAAA,YAAA,CAAA,EAAA,WAAA,mBAAA,CAAA,+BAAA,CAAA,CAAA;CAGpK,WAAQ;EACN,OAAO,KAAK,UAAS,IAAK,KAAK,UAAU,YAAW,IAAK;CAC3D;CAIA,QAAQ,MAAY;EAClB,OAAO,UAAU,KAAK,UAAS,GAAI,IAAI;CACzC;CAGA,WAAW,GAAG,OAAe;EAC3B,OAAO,MAAM,MAAM,SAAS,UAAU,KAAK,UAAS,GAAI,IAAI,CAAC;CAC/D;CAOA,gBAAa;EACX,IAAI,KAAK,YAAY,KAAK,IAAI,QAAO,CAAE,CAAC,SACtC,OAAO,QAAQ,QAAQ,KAAK,cAAa,CAAE;EAE7C,IAAI,CAAC,KAAK,iBACR,KAAK,kBAAkB,KAAK,aAAY,CAAE,CAAC,cAAa;GACtD,KAAK,kBAAkB;EACzB,CAAC;EAEH,OAAO,KAAK;CACd;CAGA,iBAAc;EACZ,OAAO,KAAK,iBAAiB,QAAQ,QAAQ,KAAK,cAAa,CAAE,IAAI,KAAK,cAAa;CACzF;CAIA,QAAQ,MAA0B,OAAmB,UAAQ;EAC3D,MAAM,aAA4B,OAAQ;GAAE,GAAG;GAAM,OAAO,KAAK,SAAS;EAAS,IAAgB;EACnG,KAAK,UAAU,IAAI,UAAU;EAC7B,KAAK,UAAU,IAAI,IAAI;EACvB,KAAK,UAAU,WAAW;GAAE,UAAU;GAAM,MAAM;EAAU,CAAE;CAChE;CAGA,YAAS;EACP,KAAK,UAAU,IAAI,IAAI;EACvB,KAAK,UAAU,IAAI,QAAQ;EAC3B,KAAK,UAAU,iBAAgB;CACjC;CAOA,MAAM,QAAQ,SAA2B;EACvC,KAAK,aAAa,IAAI,IAAI;EAC1B,IAAI;GACF,MAAM,SAAS,KAAK,UAAS,CAAE,EAAE;GACjC,KAAK,MAAM,QAAQ,KAAK,cACtB,MAAM,cAAc,KAAK,gBAAgB,SAAS,OAAO,MAAM,IAAI,IAAI,GAAG,eAAe;GAE3F,IAAI;IACF,MAAM,KAAK,cAAc,KAAK,QAAQ,WAAW,GAAG;KAClD,MAAM;KACN,aAAa;KACb,SAAS;MAAE,GAAG,SAAS;MAAS,gBAAgB;KAAkB;KAClE,QAAQ;KACR,QAAQ,SAAS;IAClB,CAAA;GACH,SAAS,OAAO;IACd,QAAQ,MAAM,2FAA2F,KAAK;GAChH;GACA,KAAK,UAAS;GACd,KAAK,MAAM,QAAQ,KAAK,cACtB,MAAM,cAAc,KAAK,eAAc,GAAI,cAAc;EAE7D,UAAU;GACR,KAAK,aAAa,IAAI,KAAK;EAC7B;CACF;CAQA,MAAM,kBAAe;EACnB,IAAI,KAAK,UACP,OAAO,CAAC,CAAC,KAAK,WAAU;EAE1B,IAAI;GAEF,MAAM,eAAc,MADE,YAAY,KAAK,aAAY,GAAI,8BAA8B,EACjE,EAAS;GAC7B,IAAI,aAAa;IAEf,KAAK,QAAQ,KAAK,iBAAiB,WAAW,GAAG,KAAK,UAAS,CAAE;IACjE,IAAI,CAAC,KAAK,UAAS,GACjB,KAAK,YAAW;IAElB,OAAO;GACT;GACA,MAAM,SAAS,KAAK,WAAU;GAC9B,IAAI,QAAQ;IACV,IAAI,CAAC,KAAK,UAAS,GACjB,KAAK,UAAU,IAAI,MAAM;IAE3B,KAAK,YAAW;IAChB,OAAO;GACT;GACA,OAAO;EACT,QAAQ;GACN,OAAO,CAAC,CAAC,KAAK,WAAU;EAC1B;CACF;CAWA,MAAM,cAAc,OAA0B,OAAoB,CAAA,GAAE;EAClE,MAAM,MAAM,aAAa,KAAK;EAC9B,IAAI,CAAC,eAAe,KAAK,KAAK,IAAI,QAAO,CAAE,CAAC,SAAS,KAAK,SAAS,UAAU,MAAM,GACjF,OAAO,mBAAmB,OAAO,IAAI;EAEvC,MAAM,mBAAmB,IAAI,QAAQ,KAAK,YAAY,iBAAiB,UAAU,MAAM,UAAU,OAAU,CAAC,CAAC,IAAI,eAAe;EAChI,MAAM,YAAY,CAAC,oBAAoB,KAAK,UAAS,IAAK,MAAM,KAAK,WAAU,IAAK;EACpF,MAAM,aAAa,iBAAiB,UAAU,MAAM,MAAK,IAAK;EAC9D,MAAM,WAAW,MAAM,KAAK,KAAK,OAAO,MAAM,KAAK,WAAW,KAAK,UAAS,CAAE;EAC9E,IAAI,SAAS,WAAW,KACtB,OAAO;EAET,MAAM,aAAa,mBAAmB,OAAO,MAAM,KAAK,eAAe,KAAK,SAAS;EACrF,MAAM,QAAQ,aAAa,MAAM,KAAK,KAAK,YAAY,MAAM,KAAK,YAAY,IAAI,IAAI;EACtF,IAAI,MAAM,WAAW,KACnB,AAAK,KAAK,mBAAmB,GAAG;EAElC,OAAO;CACT;CAOA,MAAM,eAAe,KAAa,WAAwB;EACxD,IAAI,KAAK,YAAY,iBAAiB,GAAG,KAAK,CAAC,KAAK,gBAAe,GACjE,OAAO;EAET,IAAI,aAAa,qBAAqB,GAAG,GACvC,OAAO;EAET,MAAM,UAAU,KAAK,SAAQ;EAC7B,IAAI,WAAW,YAAY,WAEzB,OAAO;EAET,OAAO,KAAK,QAAO;CACrB;CAGA,mBAAmB,KAAY;EAC7B,IAAI,KAAK,UACP,OAAO,QAAQ,QAAQ,KAAK;EAE9B,OAAO,KAAK,YAAY,OAAO,GAAG;CACpC;CAGA,MAAM,kBAAe;EACnB,OAAO,CAAC,CAAE,MAAM,KAAK,QAAO;CAC9B;CAGA,MAAM,kBAAe;EACnB,IAAI,CAAC,KAAK,UAAS,KAAM,CAAC,KAAK,UAAU,YAAW,GAClD,OAAO;EAET,OAAO,CAAC,CAAE,MAAM,KAAK,QAAO;CAC9B;CAMA,eAAe,MAAY;EACzB,IAAI,gBAAgB,IAAI,GACtB,OAAO;EAET,MAAM,SAAS,KAAK,WAAW,KAAK,KAAK,SAAS,UAAU;EAC5D,MAAM,QAAQ,KAAK,eAAe,YAAY,UAAU,WAAW,SAAS,SAAS,IAAA,CAAK,QAAQ,QAAQ,EAAE;EAC5G,IAAI,CAAC,MACH,MAAM,IAAI,MAAM,kFAAkF;EAEpG,OAAO,GAAG,OAAO,KAAK,WAAW,GAAG,IAAI,OAAO,IAAI;CACrD;CAIA,QAAQ,MAAY;EAClB,MAAM,YAAY,KAAK,QAAQ,YAAY,OAAA,CAAQ,QAAQ,QAAQ,EAAE;EACrE,OAAO,KAAK,IAAI,SAAS,GAAG,SAAS,WAAW,GAAG,IAAI,WAAW,IAAI,aAAa,MAAM;CAC3F;CAGA,MAAM,YAAe,MAAsB;EACzC,KAAK,aAAa,IAAI,IAAI;EAC1B,IAAI;GACF,OAAO,MAAM,KAAI;EACnB,UAAU;GACR,KAAK,aAAa,IAAI,KAAK;EAC7B;CACF;CAOA,gBAAgB,QAAe;EAC7B,MAAM,SAAS,iBAAiB,MAAM;EACtC,IAAI,OAAO,aAAa,OAAO,6BAA6B,CAAC,OAAO,WAAW,CAAC,OAAO,MACrF,OAAO;EAET,IAAI,OAAO,KAAK;GACd,KAAK,UAAU,YAAY,OAAO,GAAG;GACrC,KAAK,QAAQ,OAAO,MAAM,KAAK;GAC/B,OAAO;EACT;EACA,KAAK,QAAQ,OAAO,MAAM,QAAQ;EAClC,KAAK,YAAW;EAChB,OAAO;CACT;CASA,MAAM,qBAAqB,QAAe;EACxC,MAAM,EAAE,KAAK,SAAS,iBAAiB,MAAM;EAC7C,MAAM,OAAmB,OAAO,KAAK,UAAU,YAAY,GAAG,IAAI,QAAQ;EAC1E,IAAI,MAAM;GACR,KAAK,QAAQ,MAAM,IAAI;GACvB,IAAI,SAAS,UACX,KAAK,YAAW;GAElB,OAAO,KAAK,UAAS,KAAM;EAC7B;EACA,KAAK,UAAU,IAAI,IAAI;EACvB,MAAM,SAAS,MAAM,KAAK,gBAAgB,IAAI;EAC9C,IAAI,CAAC,QACH,KAAK,UAAS;EAEhB,OAAO;CACT;CAMA,MAAM,gBAAgB,OAAmB,KAAK,UAAS,GAAE;EACvD,IAAI;GACF,MAAM,OAAO,MAAM,KAAK,aAAY;GACpC,IAAI,CAAC,MAAM,MACT;GAEF,KAAK,QAAQ,KAAK,iBAAiB,KAAK,IAAI,GAAG,IAAI;GACnD,IAAI,SAAS,UACX,KAAK,YAAW;GAElB,OAAO,KAAK,UAAS,KAAM;EAC7B,QAAQ;GACN;EACF;CACF;CAMA,MAAM,aAAU;EACd,MAAM,QAAQ,KAAK,SAAQ;EAC3B,OAAO,KAAK,gBAAe,IAAO,MAAM,KAAK,QAAO,KAAO,QAAS;CACtE;CAGA,kBAAe;EACb,MAAM,QAAQ,KAAK,SAAQ;EAC3B,OAAO,CAAC,CAAC,SAAS,CAAC,KAAK,YAAY,mBAAmB,OAAA,GAA+B;CACxF;CAMA,wBAAwB,MAAY;EAClC,MAAM,SAAS,KAAK,SAAS,IAAI,QAAQ,IAAI;EAC7C,MAAM,UAAU,SAAS,OAAO,SAAS,OAAO,GAAG,CAAC,CAAC,YAAY,cAAc,IAAI,gBAAgB,KAAK,SAAS,UAAU,UAAU,EAAE,CAAC,CAAC,IAAI,UAAU;EACvJ,MAAM,WAAW,OAAO,YAAY,YAAY,QAAQ,WAAW,GAAG,KAAK,CAAC,QAAQ,WAAW,IAAI,IAAI,UAAU;EACjH,IAAI,CAAC,YAAY,gBAAgB,KAAK,IAAI,GAAG;GAC3C,KAAK,SAAS,IAAI;GAClB;EACF;EACA,KAAK,SAAS,GAAG,OAAO,KAAK,SAAS,GAAG,IAAI,MAAM,IAAG,WAAY,mBAAmB,QAAQ,GAAG;CAClG;CAGA,SAAS,KAAW;EAClB,MAAM,SAAS,KAAK,SAAS,IAAI,QAAQ,IAAI;EAC7C,IAAI,QAAQ;GACV,AAAK,OAAO,cAAc,GAAG,CAAC,CAAC,OAAO,UAAmB,QAAQ,MAAM,4CAA4C,KAAK,CAAC;GACzH;EACF;EACA,IAAI,CAAC,KAAK,UACR,KAAK,SAAS,SAAS,OAAO,GAAG;CAErC;CAOQ,iBAAiB,aAAmB;EAC1C,MAAM,SAAS,KAAK,WAAU;EAC9B,IAAI,CAAC,QAAQ,MAAM,CAAC,aAAa,MAAM,OAAO,OAAO,YAAY,IAC/D,OAAO;EAET,MAAM,SAAkC;GAAE,GAAG;GAAQ,GAAG;EAAW;EACnE,KAAK,MAAM,OAAO,YAChB,IAAI,EAAE,OAAO,cACX,OAAO,OAAO;EAGlB,OAAO;CACT;CAGQ,aAAU;EAChB,OAAO,KAAK,UAAU,SAAQ,KAAM,KAAK,UAAS;CACpD;CAGQ,MAAM,eAAY;EACxB,MAAM,WAAW,MAAM,KAAK,cAAc,KAAK,QAAQ,cAAc,GAAG,EAAE,QAAQ,MAAK,CAAE;EACzF,IAAI,CAAC,SAAS,IACZ,OAAO;EAET,OAAS,MAAM,SAAS,KAAI,CAAE,CAAC,YAAY,IAAI,KAAsD;CACvG;CAGQ,UAAO;EACb,IAAI,KAAK,UACP,OAAO,QAAQ,QAAQ,IAAI;EAE7B,IAAI,CAAC,KAAK,SACR,KAAK,UAAU,KAAK,WAAU,CAAE,CAC7B,MAAM,UAAS;GACd,IAAI,SAAS,KAAK,UAAU,YAAY,KAAK,GAAG;IAC9C,KAAK,UAAU,IAAI,KAAK;IACxB,KAAK,aAAY;IACjB,OAAO;GACT;GACA,OAAO;EACT,CAAC,CAAC,CACD,cAAa;GACZ,KAAK,UAAU;EACjB,CAAC;EAEL,OAAO,KAAK;CACd;CAGQ,cAAW;EACjB,IAAI,KAAK,UACP;EAEF,AAAK,KAAK,WAAU,CAAE,CAAC,MAAM,UAAS;GACpC,IAAI,SAAS,KAAK,UAAS,GACzB,KAAK,UAAU,YAAY,KAAK;EAEpC,CAAC;CACH;CAMQ,MAAM,aAAU;EACtB,MAAM,UAAU,KAAK,SAAQ;EAC7B,MAAM,SAAS,WAAW,CAAC,mBAAmB,SAAS,CAAC,IAAI,UAAU;EACtE,IAAI;GACF,MAAM,WAAW,MAAM,MAAM,KAAK,QAAQ,QAAQ,GAAG;IACnD,aAAa;IACb,SAAS,SAAS,EAAE,eAAe,UAAU,SAAQ,IAAK;IAC1D,QAAQ;GACT,CAAA;GACD,IAAI,CAAC,SAAS,IACZ,OAAO;GAGT,OAAO,cAAa,MADA,SAAS,KAAI,EACb,EAAM,KAAK;EACjC,QAAQ;GACN,OAAO;EACT;CACF;CAEQ,MAAM,eAAY;EACxB,MAAM,aAAa,IAAI,gBAAe;EACtC,MAAM,QAAQ,iBAAiB,WAAW,MAAK,GAAI,mBAAmB;EACtE,IAAI;GACF,MAAM,WAAW,MAAM,MAAM,KAAK,QAAQ,WAAW,GAAG;IAAE,SAAS,EAAE,QAAQ,mBAAkB;IAAI,QAAQ,WAAW;GAAM,CAAE;GAC9H,IAAI,SAAS,IAAI;IACf,MAAM,OAAgB,MAAM,SAAS,KAAI;IACzC,IAAI,QAAQ,OAAO,SAAS,YAAY,CAAC,MAAM,QAAQ,IAAI,GAAG;KAC5D,KAAK,cAAc,IAAI,IAAsB;KAC7C,KAAK,iBAAiB;KACtB,IAAI,KAAK,UACP,KAAK,cAAc,IAAI,4BAA4B,IAAsB;IAE7E;GACF;EACF,QAAQ,CAER,UAAU;GACR,aAAa,KAAK;EACpB;EACA,OAAO,KAAK,cAAa;CAC3B;CAEQ,KAAK,OAA0B,MAAmB,KAAa,OAAsB,SAAgB;EAC3G,MAAM,UAAU,IAAI,QAAQ,KAAK,YAAY,iBAAiB,UAAU,MAAM,UAAU,OAAU;EAClG,IAAI,SAAS,CAAC,QAAQ,IAAI,eAAe,GACvC,QAAQ,IAAI,iBAAiB,UAAU,OAAO;EAEhD,MAAM,cAAkC,KAAK,gBAAgB,WAAW,CAAC,qBAAqB,GAAG,IAAI,SAAS;EAC9G,OAAO,MAAM,OAAO;GAAE,GAAG;GAAM;GAAa;EAAO,CAAE;CACvD;CAEQ,eAAY;EAClB,KAAK,UAAU,WAAW;GAAE,UAAU,KAAK,UAAS;GAAI,MAAM,KAAK,UAAS;EAAE,CAAE;CAClF;CAEQ,aAAU;EAChB,MAAM,SAAS,KAAK,SAAS,IAAI,QAAQ,IAAI;EAC7C,IAAI,QACF,OAAO,OAAO;EAEhB,MAAM,WAAW,KAAK,SAAS;EAC/B,OAAO,WAAW,GAAG,SAAS,WAAW,SAAS,SAAS,SAAS,SAAS;CAC/E;CAphBW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAW,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAX,OAAA,QAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;CAAW,CAAA;;AAAX,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAW,YAAA,CAAA,EADvB,MAAA,Q;;AAyhBD,SAAgB,UAAU,MAAiC,MAAY;CACrE,IAAI,CAAC,QAAQ,CAAC,MACZ,OAAO;CAET,OAAO,KAAK,SAAS,QAAS,MAAM,QAAQ,KAAK,KAAK,KAAK,KAAK,MAAM,SAAS,IAAI;AACrF;AAEA,SAAS,aAAa,OAAc;CAClC,OAAO,OAAO,UAAU,YAAY,QAAQ,QAAQ;AACtD;AAEA,eAAe,QAAQ,MAAkC,MAAY;CACnE,IAAI;EACF,MAAM,KAAI;CACZ,SAAS,OAAO;EACd,QAAQ,MAAM,0CAA0C,KAAI,YAAa,KAAK;CAChF;AACF;AAEA,SAAS,YAAe,SAAqB,IAAU;CACrD,IAAI;CACJ,MAAM,UAAU,IAAI,SAAe,YAAW;EAC5C,QAAQ,iBAAiB,QAAQ,IAAI,GAAG,EAAE;CAC5C,CAAC;CACD,OAAO,QAAQ,KAAK,CAAC,SAAS,OAAO,CAAC,CAAC,CAAC,cAAc,aAAa,KAAK,CAAC;AAC3E;AC/oBA,MAAa,4BAA4B,aAAkC,iBAAiB;AAG5F,MAAa,+BAA+B;AAkB5C,IAAa,uBAAb,MAAa,qBAAoB;CACd,MAAM,OAAO,eAAe;CAC5B,QAAQ,OAAO,QAAQ;CACvB,gBAAgB,OAAO,aAAa;CACpC,WAAW,iBAAiB,OAAO,WAAW,CAAC;CAC/C,cAAc,KAAK,WAAW,OAAO,KAAK,cAAc,IAAI,2BAA2B,IAAI;CAC3F,kBAAkB,OAAuB,KAAK,cAAc,KAAK,YAAY,aAAa,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,kBAAA,CAAA,+BAAA,CAAA,CAAA;CAC/F,uBAAuB,OAAuB,KAAK,cAAc,KAAK,YAAY,oBAAoB,QAAQ,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,uBAAA,CAAA,+BAAA,CAAA,CAAA;CACnH,6BAA6B,OAAuB,KAAK,cAAc,KAAK,YAAY,0BAA0B,OAAO,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,6BAAA,CAAA,+BAAA,CAAA,CAAA;CACvI,gBAAqD;CAGpD,aAAa,KAAK,gBAAgB,WAAU;CAE5C,kBAAkB,KAAK,qBAAqB,WAAU;CAEtD,wBAAwB,KAAK,2BAA2B,WAAU;CAG3E,mBAAgB;EAGd,IAAI,KAAK,YAAY,KAAK,IAAI,QAAO,CAAE,CAAC,SAAS;GAC/C,KAAK,IAAI,cAAa;GACtB,OAAO,QAAQ,QAAQ,EAAE,YAAY,MAAK,CAAE;EAC9C;EACA,IAAI,CAAC,KAAK,eACR,KAAK,gBAAgB,KAAK,WAAU,CAAE,CAAC,cAAa;GAClD,KAAK,gBAAgB;EACvB,CAAC;EAEH,OAAO,KAAK;CACd;CAMA,MAAM,UAAU,OAAyB;EACvC,MAAM,MAAM,KAAK,IAAI,SAAS,oBAAoB;EAClD,MAAM,WAAW,MAAM,SAAS,MAAM,QAAQ;EAE9C,MAAM,QAAQ,MAAM,OAAO,KAAI;EAC/B,MAAM,UAAkC;GAAE,QAAQ;GAAoB,gBAAgB;EAAkB;EACxG,IAAI,OACF,QAAQ,gCAAgC;EAE1C,MAAM,WAAW,MAAM,KAAK,MAAM,KAAK;GAAE,MAAM,KAAK,UAAU;IAAE,OAAO,MAAM;IAAO,MAAM,MAAM;IAAM;GAAQ,CAAE;GAAG;GAAS,QAAQ;EAAM,CAAE;EAC5I,IAAI,CAAC,SAAS,IAAI;GAChB,MAAM,QAAQ,MAAM,aAAa,QAAQ;GACzC,IAAI,SAAS,WAAW,KAEtB,MAAM,KAAK,iBAAgB;GAE7B,MAAM;EACR;EACA,KAAK,gBAAgB,IAAI,KAAK;CAChC;CAEQ,MAAM,aAAU;EACtB,IAAI;GACF,MAAM,WAAW,MAAM,KAAK,MAAM,KAAK,IAAI,SAAS,sBAAsB,GAAG,EAAE,SAAS,EAAE,QAAQ,mBAAkB,EAAE,CAAE;GACxH,IAAI,CAAC,SAAS,IACZ,MAAM,IAAI,MAAM,QAAQ,SAAS,QAAQ;GAE3C,MAAM,OAAS,MAAM,SAAS,KAAI,KAAO,CAAA;GACzC,MAAM,SAA8B;IAElC,iBAAiB,OAAO,KAAK,uBAAuB,YAAY,KAAK,qBAAqB;IAC1F,uBAAuB,KAAK,6BAA6B;IACzD,YAAY,KAAK,kBAAkB;GACpC;GACD,KAAK,gBAAgB,IAAI,OAAO,UAAU;GAC1C,KAAK,qBAAqB,IAAI,OAAO,mBAAmB,IAAI;GAC5D,KAAK,2BAA2B,IAAI,OAAO,yBAAyB,KAAK;GACzE,IAAI,KAAK,UACP,KAAK,cAAc,IAAI,2BAA2B,MAAM;GAE1D,OAAO;EACT,QAAQ;GAEN,KAAK,gBAAgB,IAAI,KAAK;GAC9B,OAAO,EAAE,YAAY,MAAK;EAC5B;CACF;CApFW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAoB,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAApB,OAAA,QAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;CAAoB,CAAA;;AAApB,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAoB,YAAA,CAAA,EADhC,MAAA,Q;;AAwFD,eAAe,aAAa,UAAkB;CAC5C,MAAM,OAAO,MAAM,SAAS,KAAI,CAAE,CAAC,YAAY,EAAE;CACjD,IAAI,OAAgB;CACpB,IAAI;EACF,OAAO,OAAO,KAAK,MAAM,IAAI,IAAI;CACnC,QAAQ,CAER;CACA,MAAM,cAAc,QAAQ,OAAO,SAAS,WAAY,KAA+B,UAAU;CACjG,MAAM,UAAU,OAAO,gBAAgB,YAAY,cAAc,cAAc,8BAA8B,SAAS,OAAM,GAAI,SAAS,aAAa,KAAI;CAC1J,OAAO,OAAO,OAAO,IAAI,MAAM,OAAO,GAAG;EAAE;EAAM,QAAQ,SAAS;EAAQ,YAAY,SAAS;CAAM,CAAE;AACzG;AClHA,MAAa,eAAmD,QAAQ,UAAW,OAAO,WAAW,CAAC,CAAC,gBAAe,IAAK,OAAO,UAAU,KAAK;AAGjJ,SAAgB,aAAa,aAAa,QAAM;CAC9C,aAAc,OAAO,WAAW,CAAC,CAAC,gBAAe,IAAK,OAAO,MAAM,CAAC,CAAC,SAAS,UAAU,IAAI;AAC9F;AAGA,SAAgB,YAAY,OAA0B,aAAa,KAAG;CACpE,MAAM,SAAS,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK;CACpD,QAAQ,QAAQ,UAAS;EACvB,MAAM,OAAO,OAAO,WAAW;EAC/B,IAAI,CAAC,KAAK,gBAAe,GACvB,OAAO,UAAU,KAAK;EAExB,MAAM,OAAO,KAAK,KAAI;EACtB,OAAO,OAAO,MAAM,SAAS,UAAU,MAAM,IAAI,CAAC,IAAI,OAAO,OAAO,MAAM,CAAC,CAAC,SAAS,UAAU;CACjG;AACF;AAOA,MAAa,qBAAyD,OAAO,QAAQ,UAAS;CAC5F,MAAM,UAAU,OAAO,eAAe;CACtC,IAAI,CAAC,QAAQ,YAAY,SACvB,OAAO;CAET,MAAM,QAAQ,OAAO,oBAAoB;CACzC,MAAM,SAAS,OAAO,MAAM;CAC5B,IAAI;EACF,IAAI,MAAM,WAAU,MAAO,MACzB,MAAM,MAAM,iBAAgB;EAE9B,MAAM,OAAO,SAAS,MAAM,GAAG;EAC/B,MAAM,YAAY,QAAQ,YAAY;EAEtC,IAAI,MAAM,WAAU,MAAO,QAAQ,MAAM,gBAAe,MAAO,SAAS,SAAS,WAC/E,OAAO,OAAO,SAAS,SAAS;EAElC,IAAI,MAAM,WAAU,MAAO,SAAS,SAAS,WAC3C,OAAO,OAAO,SAAS,QAAQ,SAAS;CAE5C,QAAQ,CAER;CACA,OAAO;AACT;AAEA,SAAS,UAAU,OAA0B;CAC3C,MAAM,UAAU,OAAO,eAAe;CACtC,OAAO,OAAO,MAAM,CAAC,CAAC,cAAc,CAAC,QAAQ,SAAS,GAAG,EAAE,aAAa,EAAE,UAAU,MAAM,IAAG,EAAE,CAAE;AACnG;AChDA,IAAa,wBAAb,MAAa,sBAAqB;CACf,OAAO,OAAO,WAAW;CAG1C,OAAO,KAAY;EACjB,OAAO,KAAK,KAAK,mBAAmB,GAAG;CACzC;CANW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAqB,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAArB,OAAA,QAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;CAAqB,CAAA;;AAArB,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAqB,YAAA,CAAA,EADjC,MAAA,Q;;AAkBD,MAAa,qBAAwC,SAA+B,SAAuD;CACzI,IAAI,iBAAiB,OAAO,WAAW,CAAC,GACtC,OAAO,KAAK,OAAO;CAErB,MAAM,MAAM,OAAO,eAAe;CAClC,IAAI,CAAC,eAAe,QAAQ,KAAK,IAAI,QAAO,CAAE,CAAC,SAAS,OAAO,QAAQ,CAAC,CAAC,UAAU,MAAM,GACvF,OAAO,KAAK,OAAO;CAErB,MAAM,OAAO,OAAO,WAAW;CAC/B,MAAM,UAAU,OAAO,qBAAqB;CAC5C,MAAM,MAAM,QAAQ;CACpB,MAAM,mBAAmB,QAAQ,QAAQ,IAAI,eAAe;CAC5D,MAAM,aAAa,UACjB,QAAQ,MAAM,SAAS,CAAC,mBAAmB;EAAE,YAAY,EAAE,eAAe,UAAU,QAAO;EAAI,iBAAiB;CAAI,IAAK,EAAE,iBAAiB,KAAI,CAAE;CACpJ,MAAM,oBAAoB,UAAqC;EAC7D,IAAI,eAAe,KAAK,GACtB,AAAK,QAAQ,OAAO,GAAG;EAEzB,OAAO,iBAAiB,KAAK;CAC/B;CACA,MAAM,QAAQ,cACZ,KAAK,UAAU,SAAS,CAAC,CAAC,CAAC,KACzB,YAAY,UAAkB;EAC5B,IAAI,CAAC,eAAe,KAAK,KAAK,kBAC5B,OAAO,iBAAiB,KAAK;EAE/B,OAAO,KAAK,KAAK,eAAe,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,WAAW,UAAW,QAAQ,KAAK,UAAU,KAAK,CAAC,CAAC,CAAC,KAAK,WAAW,gBAAgB,CAAC,IAAI,iBAAiB,KAAK,CAAE,CAAC;CAC3K,CAAC,CAAC;CAGN,OAAO,CAAC,oBAAoB,KAAK,gBAAe,IAAK,KAAK,KAAK,WAAU,CAAE,CAAC,CAAC,KAAK,UAAU,IAAI,CAAC,IAAI,KAAK,mBAAmB,OAAO,KAAK,SAAQ,CAAE;AACrJ;AAEA,SAAS,eAAe,OAAc;CACpC,OAAO,iBAAiB,qBAAqB,MAAM,WAAW;AAChE;ACpDA,SAAgB,cAAc,UAAyB,CAAA,GAAE;CACvD,OAAO,yBAAyB;EAC9B;GAAE,SAAS;GAAuB,UAAU;EAAO;EACnD;GACE,SAAS;GACT,kBAA0B;IACxB,MAAM,OAAO,OAAO,WAAW;IAC/B,QAAQ,OAAO,SAAS,KAAK,cAAc,OAAO,IAAI;GACxD;EACD;EACD,4BAA2B;GACzB,MAAM,OAAO,OAAO,WAAW;GAC/B,IAAI,iBAAiB,OAAO,WAAW,CAAC,GACtC,OAAO,KAAK,eAAc,CAAE,CAAC,WAAW,MAAS;GAEnD,AAAK,KAAK,eAAc;GACxB,AAAK,KAAK,gBAAe;EAE3B,CAAC;CACF,CAAA;AACH"}
1
+ {"version":3,"file":"lenne.tech-ng-base-auth-provide.mjs","names":[],"sources":["../../../projects/ng-base/auth/provide/src/lib/auth-options.ts","../../../projects/ng-base/auth/provide/src/lib/auth-state.ts","../../../projects/ng-base/auth/provide/src/lib/auth-urls.ts","../../../projects/ng-base/auth/provide/src/lib/session-loss.ts","../../../projects/ng-base/auth/provide/src/lib/auth-store.ts","../../../projects/ng-base/auth/provide/src/lib/system-setup.ts","../../../projects/ng-base/auth/provide/src/lib/auth-guards.ts","../../../projects/ng-base/auth/provide/src/lib/auth-interceptor.ts","../../../projects/ng-base/auth/provide/src/lib/provide-lt-auth.ts"],"sourcesContent":["import { inject, InjectionToken } from '@angular/core';\nimport { LT_RUNTIME_CONFIG, mergeLtFeatureOptions } from '@lenne.tech/ng-base';\n\n/** Cookie names (exact names win over the `NG_PUBLIC_COOKIE_PREFIX` derived ones). */\nexport interface LtAuthCookieNamesOptions {\n /** Auth-state cookie (default `lt-auth-state`, or `<prefix>-auth-state`). */\n state?: string;\n /** JWT cookie (default `lt-jwt-token`, or `<prefix>-jwt-token`). */\n token?: string;\n}\n\n/** Options of `provideLtAuth()`; runtime overrides via `NG_PUBLIC_LT_AUTH_*`. */\nexport interface LtAuthOptions {\n /** Better Auth base path, must equal nest-server `betterAuth.basePath` (default `/iam`). */\n basePath?: string;\n /** Cookie names. */\n cookieNames?: LtAuthCookieNamesOptions;\n /**\n * Register Better Auth's `adminClient()` plugin (default **`false`**). nest-server does not serve the admin plugin\n * today (routes 404), and its methods are not hashed. Enable only together with a backend that registers `admin()`.\n */\n enableAdmin?: boolean;\n /**\n * Offer passkeys (default `true`); `false` makes `passkeySupported()` false. The passkey flows call\n * `/iam/passkey/*` directly, so `@better-auth/passkey` is not needed (add `passkeyClient()` via `plugins` if wanted).\n */\n enablePasskey?: boolean;\n /** Register the two-factor plugin (default `true`). */\n enableTwoFactor?: boolean;\n /** 401 handling. */\n interceptor?: {\n /** Detect session loss on 401 and redirect to the login page (default `true`). */\n enabled?: boolean;\n /** Extra route prefixes on which a 401 never logs out (merged with the built-in auth pages). */\n publicPaths?: string[];\n };\n /** Login page (default `/auth/login`). */\n loginPath?: string;\n /** Extra Better Auth client plugins (same as `registerLtAuthPlugins()`). */\n plugins?: unknown[];\n /** First-admin setup flow. */\n systemSetup?: {\n /** Enable the setup redirect of `ltSystemSetupGuard` (default `false`). */\n enabled?: boolean;\n /** Setup page (default `/auth/setup`). */\n setupPath?: string;\n };\n /** Where a sign-in that needs a second factor redirects (default `/auth/2fa`). */\n twoFactorRedirectPath?: string;\n}\n\n/** Fully resolved auth options. */\nexport interface LtResolvedAuthOptions {\n basePath: string;\n cookieNames: Required<LtAuthCookieNamesOptions>;\n enableAdmin: boolean;\n enablePasskey: boolean;\n enableTwoFactor: boolean;\n interceptor: { enabled: boolean; publicPaths: string[] };\n loginPath: string;\n plugins: unknown[];\n systemSetup: { enabled: boolean; setupPath: string };\n twoFactorRedirectPath: string;\n}\n\n/** Default name of the auth-state cookie. */\nexport const LT_AUTH_STATE_COOKIE_DEFAULT = 'lt-auth-state';\n/** Default name of the JWT cookie. */\nexport const LT_JWT_TOKEN_COOKIE_DEFAULT = 'lt-jwt-token';\n\n/** Defaults of {@link LtAuthOptions}. */\nexport const LT_AUTH_DEFAULTS: LtResolvedAuthOptions = {\n basePath: '/iam',\n cookieNames: { state: LT_AUTH_STATE_COOKIE_DEFAULT, token: LT_JWT_TOKEN_COOKIE_DEFAULT },\n enableAdmin: false,\n enablePasskey: true,\n enableTwoFactor: true,\n interceptor: { enabled: true, publicPaths: [] },\n loginPath: '/auth/login',\n plugins: [],\n systemSetup: { enabled: false, setupPath: '/auth/setup' },\n twoFactorRedirectPath: '/auth/2fa',\n};\n\n/** Options passed to `provideLtAuth()` (raw). */\nexport const LT_AUTH_INPUT_OPTIONS = new InjectionToken<LtAuthOptions>('LT_AUTH_INPUT_OPTIONS', { factory: () => ({}) });\n\n/**\n * Resolved auth options: defaults ← `provideLtAuth(options)` ← runtime `NG_PUBLIC_LT_AUTH_*`; cookie names derived\n * from `NG_PUBLIC_COOKIE_PREFIX` unless set explicitly.\n */\nexport const LT_AUTH_OPTIONS = new InjectionToken<LtResolvedAuthOptions>('LT_AUTH_OPTIONS', {\n factory: () => {\n const runtime = inject(LT_RUNTIME_CONFIG, { optional: true });\n const merged = mergeLtFeatureOptions(LT_AUTH_DEFAULTS, inject(LT_AUTH_INPUT_OPTIONS), runtime?.lt.auth);\n return { ...merged, cookieNames: resolveLtAuthCookieNames(merged.cookieNames, runtime?.cookiePrefix) };\n },\n});\n\n/** Sanitise a cookie prefix to RFC 6265 token characters (`[A-Za-z0-9._-]`). */\nexport function resolveLtCookiePrefix(raw: unknown): string {\n return typeof raw === 'string' ? raw.trim().replace(/[^A-Za-z0-9._-]/g, '') : '';\n}\n\n/**\n * Cookie names: an explicit non-default name wins, else `<prefix>-auth-state` / `<prefix>-jwt-token`, else the defaults.\n * Mirror the prefix on the backend (`COOKIE_PREFIX`) so both sides agree.\n */\nexport function resolveLtAuthCookieNames(configured: LtAuthCookieNamesOptions | undefined, cookiePrefix?: unknown): Required<LtAuthCookieNamesOptions> {\n const prefix = resolveLtCookiePrefix(cookiePrefix);\n const state = configured?.state && configured.state !== LT_AUTH_STATE_COOKIE_DEFAULT ? configured.state : prefix ? `${prefix}-auth-state` : LT_AUTH_STATE_COOKIE_DEFAULT;\n const token = configured?.token && configured.token !== LT_JWT_TOKEN_COOKIE_DEFAULT ? configured.token : prefix ? `${prefix}-jwt-token` : LT_JWT_TOKEN_COOKIE_DEFAULT;\n return { state, token };\n}\n","import { DOCUMENT, isPlatformServer } from '@angular/common';\nimport { inject, PLATFORM_ID, Service } from '@angular/core';\nimport { LtCookieService } from '@lenne.tech/ng-base';\n\nimport { LT_AUTH_OPTIONS } from './auth-options';\nimport type { LtAuthMode, LtAuthState, LtUser } from './auth-types';\n\n/** Lifetime of the auth-state and JWT cookies (7 days). */\nexport const LT_AUTH_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;\n\n/** Better Auth session cookies cleared best-effort on sign-out / session loss (the real ones are httpOnly). */\nconst BETTER_AUTH_COOKIES = ['better-auth.session_token', 'better-auth.session', '__Secure-better-auth.session_token', 'session_token', 'session'];\nconst BETTER_AUTH_COOKIE_PATHS = ['/', '/api', '/api/iam', '/iam'];\n\n/**\n * Resolve the auth state from a raw cookie string, tolerating several cookies of the same name.\n *\n * A host-only cookie and a domain-scoped twin can coexist and disagree; the one carrying a `user` wins. Preferring the\n * user-less twin caused random logouts in the Nuxt stack. `lt-auth-state` is a UI cache, the session itself is the\n * httpOnly `iam.session_token`, so a forged cookie yields at most a UI that looks signed in while every API call 401s.\n */\nexport function resolveLtAuthState(cookieString: string, stateCookieName: string): LtAuthState | null {\n const prefix = `${stateCookieName}=`;\n let fallback: LtAuthState | null = null;\n for (const entry of (cookieString || '').split(/;\\s*/)) {\n if (!entry.startsWith(prefix)) {\n continue;\n }\n try {\n const state = JSON.parse(decodeURIComponent(entry.slice(prefix.length))) as LtAuthState | null;\n if (state?.user) {\n return state;\n }\n if (state) {\n fallback = state;\n }\n } catch {\n // malformed entry\n }\n }\n return fallback;\n}\n\n/**\n * Read/write access to the auth cookies (`lt-auth-state`, `lt-jwt-token`) with the duplicate-tolerant read and the\n * SSR no-write rule. The server reads the request cookies and never writes.\n */\n@Service()\nexport class LtAuthStateService {\n private readonly cookies = inject(LtCookieService);\n private readonly options = inject(LT_AUTH_OPTIONS);\n private readonly document = inject(DOCUMENT);\n private readonly isServer = isPlatformServer(inject(PLATFORM_ID));\n\n /** Cookie names in use. */\n get cookieNames(): { state: string; token: string } {\n return this.options.cookieNames;\n }\n\n /** Current auth state from the cookies (`null` when none). */\n readState(): LtAuthState | null {\n return resolveLtAuthState(this.cookies.raw(), this.cookieNames.state);\n }\n\n /** Write the auth state (browser only; never writes a user-less state during SSR). */\n writeState(state: LtAuthState): void {\n if (this.isServer) {\n return;\n }\n const encoded = JSON.stringify(state);\n if (encodeURIComponent(encoded).length > 3500 && typeof ngDevMode !== 'undefined' && ngDevMode) {\n console.warn(`[@lenne.tech/ng-base] The auth-state cookie is ${encodeURIComponent(encoded).length} bytes; browsers drop cookies over 4 KB.`);\n }\n this.cookies.set(this.cookieNames.state, encoded, { maxAge: LT_AUTH_COOKIE_MAX_AGE });\n }\n\n /** Auth mode from the state cookie; `cookie` when unknown. */\n getAuthMode(): LtAuthMode {\n return this.readState()?.authMode === 'jwt' ? 'jwt' : 'cookie';\n }\n\n /** Merge `authMode` into the state cookie (keeps the user). */\n setAuthMode(mode: LtAuthMode): void {\n const current = this.readState();\n this.writeState({ authMode: mode, user: current?.user ?? null });\n }\n\n /**\n * First JWT of the token cookie(s), or `null`. Browser only. A value that is not JWT-shaped (e.g. an opaque Better Auth\n * session token an older version stored) is never returned and is deleted. A JSON-quoted JWT (`\"eyJ…\"`, written by\n * 22.0.0) is returned and rewritten raw, the form nest-server reads from the cookie.\n */\n getJwtToken(): string | null {\n if (this.isServer) {\n return null;\n }\n let invalid = false;\n for (const raw of this.cookies.getAll(this.cookieNames.token)) {\n const token = parseToken(raw);\n if (isLtJwtShaped(token)) {\n if (raw !== token) {\n this.deleteEverywhere(this.cookieNames.token);\n this.cookies.set(this.cookieNames.token, token, { maxAge: LT_AUTH_COOKIE_MAX_AGE });\n }\n return token;\n }\n invalid ||= !!token;\n }\n if (invalid) {\n this.deleteEverywhere(this.cookieNames.token);\n }\n return null;\n }\n\n /**\n * Store or clear the JWT (7 days). Browser only. Only JWT-shaped values are accepted: the token cookie is readable by\n * scripts, so an opaque session token (the body of `two-factor/*` or `passkey/*` answers) must never land there.\n * Returns whether the value was stored.\n */\n setJwtToken(token: string | null): boolean {\n if (this.isServer) {\n return false;\n }\n if (!token) {\n this.deleteEverywhere(this.cookieNames.token);\n return false;\n }\n if (!isLtJwtShaped(token)) {\n this.deleteEverywhere(this.cookieNames.token);\n console.warn('[@lenne.tech/ng-base] Refused to store a token that is not a JWT as JWT.');\n return false;\n }\n // Raw, not JSON-quoted: nest-server reads the cookie as the JWT itself (`lt-jwt-token`, JWT cookie strategy), as\n // nuxt-extensions writes it.\n this.cookies.set(this.cookieNames.token, token, { maxAge: LT_AUTH_COOKIE_MAX_AGE });\n return true;\n }\n\n /**\n * Delete a stored token that is not JWT-shaped and fall back to cookie mode (run at startup in the browser). Returns\n * whether something was purged.\n */\n purgeInvalidJwt(): boolean {\n if (this.isServer) {\n return false;\n }\n const stored = this.cookies.getAll(this.cookieNames.token).map(parseToken);\n if (!stored.some((token) => token && !isLtJwtShaped(token))) {\n return false;\n }\n this.deleteEverywhere(this.cookieNames.token);\n const state = this.readState();\n if (state?.authMode === 'jwt') {\n this.writeState({ authMode: 'cookie', user: state.user });\n }\n return true;\n }\n\n /** Whether any state cookie carries a user. */\n isAuthenticated(): boolean {\n return !!this.readState()?.user;\n }\n\n /** The user from the state cookie. */\n readUser(): LtUser | null {\n return this.readState()?.user ?? null;\n }\n\n /**\n * Delete the auth-state and JWT cookies on the host-only and the `domain=<host>` slot, and best-effort the Better Auth\n * cookies on the usual paths. Browser only.\n */\n clearAuthCookies(): void {\n if (this.isServer) {\n return;\n }\n this.deleteEverywhere(this.cookieNames.state);\n this.deleteEverywhere(this.cookieNames.token);\n for (const name of BETTER_AUTH_COOKIES) {\n for (const path of BETTER_AUTH_COOKIE_PATHS) {\n this.cookies.delete(name, { path });\n }\n }\n }\n\n private deleteEverywhere(name: string): void {\n const host = this.document.location?.hostname;\n this.cookies.delete(name, { path: '/' });\n if (host) {\n this.cookies.delete(name, { domain: host, path: '/' });\n }\n }\n}\n\n/** Whether `value` looks like a JWT (`eyJ…`, three segments), the same test nest-server applies (`isJwtShaped`). */\nexport function isLtJwtShaped(value: unknown): value is string {\n return typeof value === 'string' && value.startsWith('eyJ') && value.split('.').length === 3 && value.split('.').every((part) => part.length > 0);\n}\n\n/**\n * Whether the JWT expires within `marginMs` (by its `exp` claim, read without verification: only for timing the\n * refresh, never for trust). A token without a readable `exp` counts as not expiring; the server decides.\n */\nexport function ltJwtExpiresWithin(token: string, marginMs: number, now: number = Date.now()): boolean {\n const payload = token.split('.')[1];\n if (!payload) {\n return false;\n }\n try {\n const base64 = payload.replace(/-/g, '+').replace(/_/g, '/');\n const claims = JSON.parse(atob(base64.padEnd(base64.length + ((4 - (base64.length % 4)) % 4), '='))) as { exp?: unknown };\n return typeof claims.exp === 'number' && claims.exp * 1000 - now <= marginMs;\n } catch {\n return false;\n }\n}\n\nfunction parseToken(raw: string): string | null {\n if (!raw) {\n return null;\n }\n try {\n const parsed: unknown = JSON.parse(raw);\n return typeof parsed === 'string' && parsed ? parsed : null;\n } catch {\n return raw;\n }\n}\n","/** Built-in route prefixes on which a 401 never logs out (auth pages). Extend via `provideLtAuth({ interceptor: { publicPaths } })`. */\nexport const LT_AUTH_PUBLIC_PATHS: readonly string[] = [\n '/auth/login',\n '/auth/register',\n '/auth/forgot-password',\n '/auth/reset-password',\n '/auth/2fa',\n '/auth/setup',\n '/auth/verify-email',\n];\n\n/** API path fragments whose 401 is part of a normal auth flow (never treated as session loss). */\nexport const LT_AUTH_ENDPOINT_FRAGMENTS: readonly string[] = [\n '/sign-in',\n '/sign-up',\n '/sign-out',\n '/forgot-password',\n '/reset-password',\n '/verify-email',\n '/get-session',\n '/list-sessions',\n '/revoke-session',\n '/revoke-other-sessions',\n '/token',\n '/passkey/',\n '/list-user-passkeys',\n '/generate-register-options',\n '/verify-registration',\n '/generate-authenticate-options',\n '/verify-authentication',\n '/two-factor/',\n];\n\n/**\n * Paths that need the session cookie even in JWT mode: passkey and 2FA challenges are bound to cookies, and\n * `/get-session` resolves the session from the cookie only (a bearer-only request gets `200 null`, which the session\n * probe would read as \"dead\" for every user in JWT mode).\n */\nexport const LT_AUTH_COOKIE_PATHS: readonly string[] = ['/passkey/', '/two-factor/', '/2fa/', '/get-session'];\n\n/** Whether a 401 from `url` belongs to an auth flow (wrong password, wrong code, probe) rather than a lost session. */\nexport function isLtAuthEndpoint(url: string): boolean {\n return LT_AUTH_ENDPOINT_FRAGMENTS.some((fragment) => url.includes(fragment));\n}\n\n/** Whether `url` needs the session cookie even in JWT mode (see {@link LT_AUTH_COOKIE_PATHS}). */\nexport function ltUrlRequiresCookies(url: string): boolean {\n return LT_AUTH_COOKIE_PATHS.some((path) => url.includes(path));\n}\n\n/** URL string of a fetch input. */\nexport function ltRequestUrl(input: RequestInfo | URL): string {\n if (typeof input === 'string') {\n return input;\n }\n return input instanceof URL ? input.href : input.url;\n}\n\n/** Path of a URL or route (`/a/b?x=1#y` → `/a/b`). */\nexport function ltPathOf(url: string): string {\n return url.split(/[?#]/)[0] || '/';\n}\n\n/**\n * Whether `url` goes to the API, so it may carry credentials and the Bearer token:\n *\n * - it starts with the API base URL at a path boundary (`https://api.example.com` matches `https://api.example.com/x`,\n * never `https://api.example.com.evil.test`);\n * - or it is same-origin and starts with `/api` (dev proxy);\n * - or no API URL is configured and it is same-origin (same-origin reverse proxy).\n *\n * Absolute URLs on `origin` are compared by their path. Foreign hosts never get the token.\n */\nexport function isLtApiRequest(url: string, baseUrl: string, origin?: string | null): boolean {\n if (baseUrl && hasPrefix(url, baseUrl)) {\n return true;\n }\n let path = url;\n if (origin && origin !== 'null' && hasPrefix(url, origin)) {\n path = url.slice(origin.length) || '/';\n }\n if (!path.startsWith('/') || path.startsWith('//')) {\n return false;\n }\n if (hasPrefix(path, '/api')) {\n return true;\n }\n if (!baseUrl) {\n return true;\n }\n return baseUrl.startsWith('/') && hasPrefix(path, baseUrl);\n}\n\nfunction hasPrefix(value: string, prefix: string): boolean {\n if (!value.startsWith(prefix)) {\n return false;\n }\n const next = value.charAt(prefix.length);\n return next === '' || next === '/' || next === '?' || next === '#' || prefix.endsWith('/');\n}\n","import { isLtAuthEndpoint, ltPathOf } from './auth-urls';\n\n/** How long a positive (\"session is alive\") verdict is reused. Only `true` is cached, never `false` or \"no verdict\". */\nexport const LT_SESSION_ALIVE_TTL_MS = 1000;\n/** How long the guard is held after a logout, so a burst of parallel 401s redirects once. */\nexport const LT_SESSION_LOSS_GUARD_MS = 1000;\n\n/** What the detector needs from the auth service. */\nexport interface LtSessionLossHost {\n clearUser(): void;\n /** Current router URL (full path incl. query), `null` when unknown. */\n currentUrl(): string | null;\n isAuthenticated(): boolean;\n isJwtMode(): boolean;\n navigate(url: string): void;\n /** `GET <auth>/get-session` with the session cookie. */\n probeSession(): Promise<Response>;\n switchToJwtMode(): Promise<boolean>;\n}\n\n/** Configuration of the detector. */\nexport interface LtSessionLossConfig {\n enabled: boolean;\n loginPath: string;\n publicPaths: readonly string[];\n}\n\n/** The detector: `handle(url)` resolves to `true` when the session was found dead and the user was redirected. */\nexport interface LtSessionLossDetector {\n handle(url?: string): Promise<boolean>;\n}\n\n/**\n * Session-loss detection on 401 (port of the nuxt-extensions auth interceptor).\n *\n * A 401 from a domain endpoint is no proof of an expired session (backends mislabel permission errors as 401), so the\n * session endpoint is probed first: only a confirmed dead session (`401`/`403` or an empty body) clears the state and\n * redirects to `loginPath?redirect=<current url>`. A probe without verdict (network error, 429, 5xx) never logs out.\n * The guard is claimed as late as possible and held only after a logout, so a 401 that was ignored (auth endpoint,\n * public page, user not hydrated yet) never swallows the ones right behind it.\n */\nexport function createLtSessionLossDetector(host: LtSessionLossHost, config: LtSessionLossConfig): LtSessionLossDetector {\n let handling = false;\n let aliveUntil = 0;\n\n const isPublicRoute = (): boolean => {\n const current = host.currentUrl();\n if (!current) {\n return false;\n }\n const path = ltPathOf(current);\n return config.publicPaths.some((publicPath) => path.startsWith(publicPath));\n };\n\n const probe = async (): Promise<boolean | null> => {\n if (Date.now() < aliveUntil) {\n return true;\n }\n let verdict: boolean | null;\n try {\n const response = await host.probeSession();\n if (response.status === 401 || response.status === 403) {\n return false;\n }\n if (!response.ok) {\n return null;\n }\n const data = (await response.json().catch(() => null)) as { session?: unknown; user?: unknown } | null;\n verdict = Boolean(data && (data.user || data.session));\n // In JWT mode the 401 came from the bearer while the verdict came from the cookie: the bearer is stale.\n if (verdict && host.isJwtMode()) {\n await host.switchToJwtMode().catch(() => false);\n }\n } catch {\n return null;\n }\n if (verdict) {\n aliveUntil = Date.now() + LT_SESSION_ALIVE_TTL_MS;\n }\n return verdict;\n };\n\n return {\n async handle(url?: string): Promise<boolean> {\n if (!config.enabled || handling || (url && isLtAuthEndpoint(url)) || isPublicRoute() || !host.isAuthenticated()) {\n return false;\n }\n handling = true;\n let loggedOut = false;\n try {\n const alive = await probe();\n if (alive !== false) {\n return false;\n }\n loggedOut = true;\n host.clearUser();\n const current = host.currentUrl();\n const redirect = current && ltPathOf(current) !== config.loginPath ? `?redirect=${encodeURIComponent(current)}` : '';\n host.navigate(`${config.loginPath}${redirect}`);\n return true;\n } catch (error) {\n console.error('[@lenne.tech/ng-base] Handling a 401 failed:', error);\n return loggedOut;\n } finally {\n if (loggedOut) {\n setTimeout(() => {\n handling = false;\n }, LT_SESSION_LOSS_GUARD_MS);\n } else {\n handling = false;\n }\n }\n },\n };\n}\n","import { DOCUMENT, isPlatformServer } from '@angular/common';\nimport { computed, inject, InjectionToken, Injector, makeStateKey, PLATFORM_ID, Service, signal, TransferState, type Signal } from '@angular/core';\nimport { Router } from '@angular/router';\nimport { isLtAbsoluteUrl, LT_RUNTIME_CONFIG, ltCredentialsFetch, LtApiUrlService, warnLtOnce } from '@lenne.tech/ng-base';\n\nimport { LT_AUTH_OPTIONS } from './auth-options';\nimport { isLtJwtShaped, LtAuthStateService, ltJwtExpiresWithin } from './auth-state';\nimport type { LtAuthCallOptions, LtAuthFeatures, LtAuthMode, LtUser, LtUserInput } from './auth-types';\nimport { isLtApiRequest, isLtAuthEndpoint, LT_AUTH_PUBLIC_PATHS, ltRequestUrl, ltUrlRequiresCookies } from './auth-urls';\nimport { createLtSessionLossDetector } from './session-loss';\n\n/**\n * Hooks around `signOut()`. `provideLtSync()` registers one that flushes the offline outbox before the session ends and\n * deletes the IndexedDB afterwards; projects can add their own (multi provider).\n */\nexport interface LtAuthSignOutHook {\n /** Runs before `POST /iam/sign-out` with the id of the signed-in user (errors are logged, never block sign-out). */\n beforeSignOut?(userId: string | null): Promise<void> | void;\n /** Runs after the local state was cleared. */\n afterSignOut?(): Promise<void> | void;\n}\n\n/** Sign-out hooks (multi). */\nexport const LT_AUTH_SIGN_OUT_HOOKS = new InjectionToken<LtAuthSignOutHook[]>('LT_AUTH_SIGN_OUT_HOOKS', { factory: () => [] });\n\n/** TransferState key of the feature flags fetched during SSR. */\nexport const LT_AUTH_FEATURES_STATE_KEY = makeStateKey<LtAuthFeatures>('lt-auth-features');\n\n/** Upper bound for `validateSession()` waiting on `GET /iam/get-session`. */\nexport const LT_VALIDATE_SESSION_TIMEOUT_MS = 10_000;\n\n/**\n * A JWT that expires within this margin is refreshed before it is sent (nest-server's JWTs live 15 minutes; with\n * `cookies: false` there is no session cookie to mint a new one once it has expired).\n */\nexport const LT_JWT_REFRESH_MARGIN_MS = 60_000;\n\n/** Upper bound for `GET /iam/features` (keeps a slow API from holding the SSR response). */\nconst FEATURES_TIMEOUT_MS = 5000;\n\n/**\n * Authorization keys the session owns. A key the session omits is dropped from the merged user (fail-closed), so a\n * backend downgrade (role removed, ban) is never masked by a stale cached value.\n */\nconst AUTHZ_KEYS = ['banExpires', 'banReason', 'banned', 'emailVerified', 'role', 'roles', 'twoFactorEnabled'] as const satisfies readonly (keyof LtUser)[];\n\n/** What a sign-in / sign-up answer says (nest-server's own shape and Better Auth's native one). */\nexport interface LtAuthAnswer {\n /** Sign-up with email verification: nest-server revoked the session, nobody is signed in yet. */\n emailVerificationRequired: boolean;\n /** A JWT the server put into the body on purpose (`cookies: false` or `exposeTokenInBody` with the JWT plugin). */\n jwt: string | null;\n /** The answer proves a session (`success: true`, a token or a session object) and reports no failure. */\n session: boolean;\n /** A second factor is required (`requiresTwoFactor` from nest-server, `twoFactorRedirect` from Better Auth). */\n twoFactor: boolean;\n user: LtUserInput | null;\n}\n\n/**\n * Read a sign-in / sign-up answer, either the Better Auth client envelope (`{ data, error }`) or a raw body.\n *\n * nest-server answers `{ requiresTwoFactor, success, user }` (the token is removed from the body while cookies are on),\n * `{ requiresTwoFactor: true, success: false }` when 2FA is required and `{ emailVerificationRequired: true, user }`\n * after a sign-up whose session it revoked. Better Auth's native answers carry `twoFactorRedirect` or\n * `{ token: <opaque session token>, user }`. Only a JWT-shaped body token counts as JWT: an opaque session token never\n * becomes a bearer kept in a script-readable cookie.\n */\nexport function readLtAuthAnswer(result: unknown): LtAuthAnswer {\n const outer = (result && typeof result === 'object' ? result : {}) as Record<string, unknown>;\n const data = (outer['data'] && typeof outer['data'] === 'object' ? outer['data'] : {}) as Record<string, unknown>;\n const pick = (key: string): unknown => data[key] ?? outer[key];\n const token = pick('token');\n const user = pick('user');\n const sessionObject = pick('session');\n const failed = !!outer['error'] || pick('success') === false;\n const proven = pick('success') === true || (typeof token === 'string' && !!token) || (!!sessionObject && typeof sessionObject === 'object');\n return {\n emailVerificationRequired: pick('emailVerificationRequired') === true,\n jwt: isLtJwtShaped(token) ? token : null,\n session: proven && !failed,\n twoFactor: pick('requiresTwoFactor') === true || pick('twoFactorRedirect') === true,\n user: user && typeof user === 'object' ? (user as LtUserInput) : null,\n };\n}\n\n/**\n * The light half of authentication: session state, transport and session validation, without Better Auth.\n *\n * Guards, `ltAuthInterceptor`, `provideLtAuth()` (and through it `LT_FETCH`) and offline sync use this store, so an\n * app's initial bundle carries no passkey, 2FA or Better Auth code. `LtAuthService` (`@lenne.tech/ng-base/auth`)\n * exposes the same state and methods and adds the Better Auth flows; both read and write the same signals.\n *\n * State is signal-based and backed by the client-readable `lt-auth-state` cookie, so SSR renders the signed-in shell\n * from the request cookie (UI gating only; the session itself is the httpOnly `iam.session_token`). Transport: session\n * cookie first; when cookies do not work (401 while the state says signed in) the store switches to JWT mode\n * (`GET /iam/token`, Bearer header) and refreshes a stale JWT once per burst. nest-server with `cookies: false` answers\n * every sign-in with a JWT, which puts the store into JWT mode directly; session checks never change the mode, and a\n * JWT close to its expiry is refreshed with itself as bearer before it is sent.\n */\n@Service()\nexport class LtAuthStore {\n private readonly api = inject(LtApiUrlService);\n private readonly authState = inject(LtAuthStateService);\n private readonly options = inject(LT_AUTH_OPTIONS);\n private readonly runtimeConfig = inject(LT_RUNTIME_CONFIG, { optional: true });\n private readonly transferState = inject(TransferState);\n private readonly injector = inject(Injector);\n private readonly document = inject(DOCUMENT);\n private readonly signOutHooks = inject(LT_AUTH_SIGN_OUT_HOOKS);\n private readonly isServer = isPlatformServer(inject(PLATFORM_ID));\n\n // ── state ──────────────────────────────────────────────────────────────────────────────────────────────────────────\n /** A non-JWT value in the JWT cookie (opaque session token from an older version) is deleted before anything reads it. */\n private readonly purgedInvalidJwt = this.authState.purgeInvalidJwt();\n private readonly initialState = this.authState.readState();\n private readonly transferredFeatures = this.isServer ? null : this.transferState.get(LT_AUTH_FEATURES_STATE_KEY, null);\n private readonly userState = signal<LtUser | null>(this.initialState?.user ?? null);\n private readonly modeState = signal<LtAuthMode>(this.initialState?.authMode === 'jwt' ? 'jwt' : 'cookie');\n private readonly loadingState = signal(false);\n private readonly featuresState = signal<LtAuthFeatures>(this.transferredFeatures ?? {});\n private featuresLoaded = this.transferredFeatures !== null;\n private featuresRequest: Promise<LtAuthFeatures> | null = null;\n /** In-flight `GET /token` shared by every 401 of a burst (cookie → JWT switch and stale-JWT refresh alike). */\n private jwtMint: Promise<string | null> | null = null;\n private readonly sessionLoss = createLtSessionLossDetector(\n {\n clearUser: () => this.clearUser(),\n currentUrl: () => this.currentUrl(),\n isAuthenticated: () => this.isAuthenticated(),\n isJwtMode: () => this.isJwtMode(),\n navigate: (url) => this.navigate(url),\n probeSession: () => this.fetchWithAuth(this.authUrl('/get-session'), { method: 'GET' }),\n switchToJwtMode: () => this.switchToJwtMode(),\n },\n {\n enabled: this.options.interceptor.enabled,\n loginPath: this.options.loginPath,\n publicPaths: [\n ...new Set([\n ...LT_AUTH_PUBLIC_PATHS,\n ...this.options.interceptor.publicPaths,\n this.options.loginPath,\n this.options.twoFactorRedirectPath,\n this.options.systemSetup.setupPath,\n ]),\n ],\n },\n );\n\n /** Signed-in user (from the auth-state cookie, refreshed by `validateSession()` and every sign-in). */\n readonly user: Signal<LtUser | null> = this.userState.asReadonly();\n /** Whether a user is signed in (UI gating; the backend decides). */\n readonly isAuthenticated = computed(() => !!this.userState());\n /** Current transport. */\n readonly authMode: Signal<LtAuthMode> = this.modeState.asReadonly();\n /** Whether the JWT fallback is active. */\n readonly isJwtMode = computed(() => this.modeState() === 'jwt');\n /** Whether a sign-in/up/out or passkey flow is running. */\n readonly isLoading: Signal<boolean> = this.loadingState.asReadonly();\n /** Whether the user has the `admin` role (UX gate only). */\n readonly isAdmin = computed(() => hasLtRole(this.userState(), 'admin'));\n /** Whether two-factor authentication is enabled for the user. */\n readonly is2FAEnabled = computed(() => !!this.userState()?.twoFactorEnabled);\n /** nest-server feature flags (`GET /iam/features`); fetched on the server and transferred to the browser. */\n readonly features: Signal<LtAuthFeatures> = this.featuresState.asReadonly();\n /** Whether passkeys can be used: enabled in the options, backend feature on and WebAuthn available in this browser. */\n readonly passkeySupported = computed(() => this.options.enablePasskey && !!this.featuresState()['passkey'] && typeof globalThis.PublicKeyCredential !== 'undefined');\n\n /** The current JWT (JWT mode only), or `null`. */\n jwtToken(): string | null {\n return this.isJwtMode() ? this.authState.getJwtToken() : null;\n }\n\n // ── roles ──────────────────────────────────────────────────────────────────────────────────────────────────────────\n /** `user.role === role` or `user.roles` contains it (case-sensitive). */\n hasRole(role: string): boolean {\n return hasLtRole(this.userState(), role);\n }\n\n /** Whether the user has any of the roles. */\n hasAnyRole(...roles: string[]): boolean {\n return roles.some((role) => hasLtRole(this.userState(), role));\n }\n\n // ── features ───────────────────────────────────────────────────────────────────────────────────────────────────────\n /**\n * Fetch the feature flags (`GET /iam/features`, anonymous). One request in flight at a time; errors keep the last\n * known flags. On the server the result is put into `TransferState` for the browser. SSR without API URL: no fetch.\n */\n fetchFeatures(): Promise<LtAuthFeatures> {\n if (this.isServer && this.api.resolve().missing) {\n return Promise.resolve(this.featuresState());\n }\n if (!this.featuresRequest) {\n this.featuresRequest = this.loadFeatures().finally(() => {\n this.featuresRequest = null;\n });\n }\n return this.featuresRequest;\n }\n\n /** The feature flags, fetched at most once (transferred from SSR, or fetched on first call). */\n ensureFeatures(): Promise<LtAuthFeatures> {\n return this.featuresLoaded ? Promise.resolve(this.featuresState()) : this.fetchFeatures();\n }\n\n // ── session ────────────────────────────────────────────────────────────────────────────────────────────────────────\n /** Store the user in state and cookie (browser always; SSR never writes). `image: null` becomes `undefined`. */\n setUser(user: LtUserInput | null, mode: LtAuthMode = 'cookie'): void {\n const normalised: LtUser | null = user ? ({ ...user, image: user.image ?? undefined } as LtUser) : null;\n this.userState.set(normalised);\n this.modeState.set(mode);\n this.authState.writeState({ authMode: mode, user: normalised });\n }\n\n /** Clear local auth state and cookies (browser). Does not delete offline data (use `signOut()` for that). */\n clearUser(): void {\n this.userState.set(null);\n this.modeState.set('cookie');\n this.authState.clearAuthCookies();\n }\n\n /**\n * Sign out: sign-out hooks (offline sync flush), `POST /iam/sign-out`, local state cleared, after-hooks (sync wipe).\n * The request is a plain {@link fetchWithAuth} call, so signing out never loads Better Auth. The local state is\n * cleared even when the request fails (offline device): the failure is logged, not thrown.\n */\n async signOut(options?: LtAuthCallOptions): Promise<void> {\n this.loadingState.set(true);\n try {\n const userId = this.userState()?.id;\n for (const hook of this.signOutHooks) {\n await runHook(() => hook.beforeSignOut?.(userId ? String(userId) : null), 'beforeSignOut');\n }\n try {\n await this.fetchWithAuth(this.authUrl('/sign-out'), {\n body: '{}',\n credentials: 'include',\n headers: { ...options?.headers, 'content-type': 'application/json' },\n method: 'POST',\n signal: options?.signal,\n });\n } catch (error) {\n console.error('[@lenne.tech/ng-base] The sign-out request failed; the local session is cleared anyway:', error);\n }\n this.clearUser();\n for (const hook of this.signOutHooks) {\n await runHook(() => hook.afterSignOut?.(), 'afterSignOut');\n }\n } finally {\n this.loadingState.set(false);\n }\n }\n\n /**\n * Validate the session (app start / hard reload). Server: answers from the cookie, no backend call. Browser:\n * `GET /iam/get-session` (≤ 10 s, plain {@link fetchWithAuth}); a user is merged with the cached one\n * (nest-server-only fields kept, authz keys fail-closed); no user but a cached one → `true` (e.g. right after 2FA);\n * else `false`.\n */\n async validateSession(): Promise<boolean> {\n if (this.isServer) {\n return !!this.cachedUser();\n }\n try {\n const session = await withTimeout(this.fetchSession(), LT_VALIDATE_SESSION_TIMEOUT_MS);\n const sessionUser = session?.user;\n if (sessionUser) {\n // The answer proves the session, not the transport: keep the mode (a bearer-proven session stays in JWT mode).\n this.setUser(this.mergeSessionUser(sessionUser), this.modeState());\n if (!this.isJwtMode()) {\n this.prefetchJwt();\n }\n return true;\n }\n const cached = this.cachedUser();\n if (cached) {\n if (!this.userState()) {\n this.userState.set(cached);\n }\n this.prefetchJwt();\n return true;\n }\n return false;\n } catch {\n return !!this.cachedUser();\n }\n }\n\n /**\n * The auth-aware fetch (also provided as `LT_FETCH` by `provideLtAuth()` and used by the Better Auth client).\n *\n * For API URLs (see {@link isLtApiRequest}): cookie mode sends `credentials: 'include'`; JWT mode adds\n * `Authorization: Bearer` and omits cookies except for passkey/2FA/`get-session` (an explicit `init.credentials`\n * wins). On a 401 while signed in: cookie mode → switch to JWT and retry once; JWT mode → one shared refresh per\n * burst and retry once. A 401 that survives is handed to the session-loss detection. Auth-flow endpoints\n * (sign-in, 2FA, …) are never retried: their 401 is the flow's verdict. Other URLs are fetched unchanged (no token).\n */\n async fetchWithAuth(input: RequestInfo | URL, init: RequestInit = {}): Promise<Response> {\n const url = ltRequestUrl(input);\n if (!isLtApiRequest(url, this.api.resolve().baseUrl, this.document.location?.origin)) {\n return ltCredentialsFetch(input, init);\n }\n const ownAuthorization = new Headers(init.headers ?? (input instanceof Request ? input.headers : undefined)).has('Authorization');\n const sentToken = !ownAuthorization && this.isJwtMode() ? await this.currentJwt() : null;\n const retryInput = input instanceof Request ? input.clone() : input;\n const response = await this.send(input, init, url, sentToken, this.isJwtMode());\n if (response.status !== 401) {\n return response;\n }\n const freshToken = ownAuthorization ? null : await this.recoverFrom401(url, sentToken);\n const final = freshToken ? await this.send(retryInput, init, url, freshToken, true) : response;\n if (final.status === 401) {\n void this.handleUnauthorized(url);\n }\n return final;\n }\n\n /**\n * Token to retry a request with after a 401, or `null` when no retry makes sense (server, auth-flow endpoint, not\n * signed in, cookie-bound path in JWT mode, no token obtainable). `sentToken` is the bearer the request carried\n * (`null` in cookie mode). Used by {@link fetchWithAuth} and `ltAuthInterceptor`.\n */\n async recoverFrom401(url: string, sentToken: string | null): Promise<string | null> {\n if (this.isServer || isLtAuthEndpoint(url) || !this.isAuthenticated()) {\n return null;\n }\n if (sentToken && ltUrlRequiresCookies(url)) {\n return null;\n }\n const current = this.jwtToken();\n if (current && current !== sentToken) {\n // A parallel request already switched or refreshed.\n return current;\n }\n return this.mintJwt();\n }\n\n /** Session-loss handling for a 401 from `url` (see `LtUnauthorizedHandler`). */\n handleUnauthorized(url?: string): Promise<boolean> {\n if (this.isServer) {\n return Promise.resolve(false);\n }\n return this.sessionLoss.handle(url);\n }\n\n /** `GET /iam/token` → store the JWT and switch to JWT mode. One request per burst. Browser only. */\n async switchToJwtMode(): Promise<boolean> {\n return !!(await this.mintJwt());\n }\n\n /** Re-mint the JWT (JWT mode only). */\n async refreshJwtToken(): Promise<boolean> {\n if (!this.isJwtMode() || !this.authState.getJwtToken()) {\n return false;\n }\n return !!(await this.mintJwt());\n }\n\n /**\n * Build an absolute app URL from `NG_PUBLIC_SITE_URL` (or the browser origin), for `redirectTo` / `callbackURL`.\n * Never derived from the request: on the server without a site URL it throws. Absolute input is returned unchanged.\n */\n absoluteAppUrl(path: string): string {\n if (isLtAbsoluteUrl(path)) {\n return path;\n }\n const origin = this.isServer ? '' : this.document.location?.origin;\n const base = (this.runtimeConfig?.siteUrl || (origin && origin !== 'null' ? origin : '')).replace(/\\/+$/, '');\n if (!base) {\n throw new Error('[@lenne.tech/ng-base] absoluteAppUrl: no site URL known. Set NG_PUBLIC_SITE_URL.');\n }\n return `${base}${path.startsWith('/') ? path : `/${path}`}`;\n }\n\n // ── plumbing for LtAuthService (@lenne.tech/ng-base/auth) ──────────────────────────────────────────────────────────\n /** Full URL of a Better Auth route (`/get-session` → `<api>/iam/get-session`). */\n authUrl(path: string): string {\n const basePath = (this.options.basePath || '/iam').replace(/\\/+$/, '');\n return this.api.buildUrl(`${basePath.startsWith('/') ? basePath : `/${basePath}`}${path}`);\n }\n\n /** Run `task` with `isLoading()` set. */\n async withLoading<T>(task: () => Promise<T>): Promise<T> {\n this.loadingState.set(true);\n try {\n return await task();\n } finally {\n this.loadingState.set(false);\n }\n }\n\n /**\n * Apply a sign-in / sign-up answer (see {@link readLtAuthAnswer}). No user is set when a second factor or an email\n * verification is pending, or when the answer proves no session. A JWT-shaped body token → JWT mode; otherwise\n * cookie mode plus a background JWT from `GET /iam/token` (cookie stays primary). `true` when a user was set.\n */\n applyAuthResult(result: unknown): boolean {\n const answer = readLtAuthAnswer(result);\n if (answer.twoFactor || answer.emailVerificationRequired || !answer.session || !answer.user) {\n return false;\n }\n if (answer.jwt) {\n this.authState.setJwtToken(answer.jwt);\n this.setUser(answer.user, 'jwt');\n return true;\n }\n this.setUser(answer.user, 'cookie');\n this.prefetchJwt();\n return true;\n }\n\n /**\n * Take a session another flow already proved (2FA verification, passkey sign-in), as after a password sign-in:\n * a JWT-shaped `token` in the answer (nest-server with `cookies: false` or `exposeTokenInBody`) → JWT mode; otherwise\n * cookie mode plus a background JWT. Any other token is Better Auth's opaque session token and is never kept. An\n * answer without user loads it from `GET /iam/get-session` (with the bearer in JWT mode). Resolves to the user, or\n * `undefined` when no session could be loaded (local state then stays signed out).\n */\n async adoptVerifiedSession(result: unknown): Promise<LtUser | undefined> {\n const { jwt, user } = readLtAuthAnswer(result);\n const mode: LtAuthMode = jwt && this.authState.setJwtToken(jwt) ? 'jwt' : 'cookie';\n if (user) {\n this.setUser(user, mode);\n if (mode === 'cookie') {\n this.prefetchJwt();\n }\n return this.userState() ?? undefined;\n }\n this.modeState.set(mode);\n const loaded = await this.loadSessionUser(mode);\n if (!loaded) {\n this.clearUser();\n }\n return loaded;\n }\n\n /**\n * Load the user via `GET /iam/get-session`, merge it with the cached one and store it, in `mode` (default: the\n * current mode; the session answer proves the session, not the transport).\n */\n async loadSessionUser(mode: LtAuthMode = this.modeState()): Promise<LtUser | undefined> {\n try {\n const data = await this.fetchSession();\n if (!data?.user) {\n return undefined;\n }\n this.setUser(this.mergeSessionUser(data.user), mode);\n if (mode === 'cookie') {\n this.prefetchJwt();\n }\n return this.userState() ?? undefined;\n } catch {\n return undefined;\n }\n }\n\n /**\n * The JWT to send (JWT mode only, else `null`), refreshed first when it expires within\n * {@link LT_JWT_REFRESH_MARGIN_MS} (one refresh per burst; the old token is kept when the refresh fails).\n */\n async currentJwt(): Promise<string | null> {\n const token = this.jwtToken();\n return this.jwtNeedsRefresh() ? ((await this.mintJwt()) ?? token) : token;\n }\n\n /** Whether the JWT of JWT mode expires within {@link LT_JWT_REFRESH_MARGIN_MS} (browser only). */\n jwtNeedsRefresh(): boolean {\n const token = this.jwtToken();\n return !!token && !this.isServer && ltJwtExpiresWithin(token, LT_JWT_REFRESH_MARGIN_MS);\n }\n\n /**\n * Navigate to `path` and keep the `redirect` query parameter of the current URL (login → 2FA must not lose where the\n * user wanted to go). A `redirect` already in `path` wins; only app-relative targets (`/…`, not `//…`) are kept.\n */\n navigateKeepingRedirect(path: string): void {\n const router = this.injector.get(Router, null);\n const current = router ? router.parseUrl(router.url).queryParams['redirect'] : new URLSearchParams(this.document.location?.search ?? '').get('redirect');\n const redirect = typeof current === 'string' && current.startsWith('/') && !current.startsWith('//') ? current : null;\n if (!redirect || /[?&]redirect=/.test(path)) {\n this.navigate(path);\n return;\n }\n this.navigate(`${path}${path.includes('?') ? '&' : '?'}redirect=${encodeURIComponent(redirect)}`);\n }\n\n /** Navigate within the app (Router when available, else a full page load in the browser). */\n navigate(url: string): void {\n const router = this.injector.get(Router, null);\n if (router) {\n void router.navigateByUrl(url).catch((error: unknown) => console.error('[@lenne.tech/ng-base] Navigation failed:', error));\n return;\n }\n if (!this.isServer) {\n this.document.location.assign(url);\n }\n }\n\n // ── internals ──────────────────────────────────────────────────────────────────────────────────────────────────────\n /**\n * Merge a session user onto the cached user of the SAME identity: nest-server-only fields survive, authorization\n * keys always reflect the session. A different / absent / id-less identity yields the session user verbatim.\n */\n private mergeSessionUser(sessionUser: LtUser): LtUser {\n const cached = this.cachedUser();\n if (!cached?.id || !sessionUser?.id || cached.id !== sessionUser.id) {\n return sessionUser;\n }\n const merged: Record<string, unknown> = { ...cached, ...sessionUser };\n for (const key of AUTHZ_KEYS) {\n if (!(key in sessionUser)) {\n delete merged[key];\n }\n }\n return merged as LtUser;\n }\n\n /** Cached user: the duplicate-tolerant cookie view, else the in-memory state. */\n private cachedUser(): LtUser | null {\n return this.authState.readUser() ?? this.userState();\n }\n\n /** `GET /iam/get-session` with the session cookie (bearer too in JWT mode); `null` without a session. */\n private async fetchSession(): Promise<{ session?: unknown; user?: LtUser } | null> {\n const response = await this.fetchWithAuth(this.authUrl('/get-session'), { method: 'GET' });\n if (!response.ok) {\n return null;\n }\n return ((await response.json().catch(() => null)) as { session?: unknown; user?: LtUser } | null) ?? null;\n }\n\n /** Mint a JWT and switch to JWT mode; one `GET /token` per burst. */\n private mintJwt(): Promise<string | null> {\n if (this.isServer) {\n return Promise.resolve(null);\n }\n if (!this.jwtMint) {\n this.jwtMint = this.requestJwt()\n .then((token) => {\n if (token && this.authState.setJwtToken(token)) {\n this.modeState.set('jwt');\n this.persistState();\n return token;\n }\n return null;\n })\n .finally(() => {\n this.jwtMint = null;\n });\n }\n return this.jwtMint;\n }\n\n /** Fetch a JWT in the background so it is ready as fallback; keeps the current mode (cookie stays primary). */\n private prefetchJwt(): void {\n if (this.isServer) {\n return;\n }\n void this.requestJwt().then((token) => {\n if (token && this.userState()) {\n this.authState.setJwtToken(token);\n }\n });\n }\n\n /**\n * `GET <auth>/token` with the session cookie and, in JWT mode, the current JWT as bearer while it is still valid\n * (with `cookies: false` the bearer is the only proof of the session); the token or `null`.\n */\n private async requestJwt(): Promise<string | null> {\n const current = this.jwtToken();\n const bearer = current && !ltJwtExpiresWithin(current, 0) ? current : null;\n try {\n const response = await fetch(this.authUrl('/token'), {\n credentials: 'include',\n headers: bearer ? { Authorization: `Bearer ${bearer}` } : undefined,\n method: 'GET',\n });\n if (!response.ok) {\n return null;\n }\n const data = (await response.json()) as { token?: unknown } | null;\n return stringOrNull(data?.token);\n } catch {\n return null;\n }\n }\n\n private async loadFeatures(): Promise<LtAuthFeatures> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), FEATURES_TIMEOUT_MS);\n try {\n const response = await fetch(this.authUrl('/features'), { headers: { accept: 'application/json' }, signal: controller.signal });\n if (response.ok) {\n const body: unknown = await response.json();\n if (body && typeof body === 'object' && !Array.isArray(body)) {\n this.featuresState.set(body as LtAuthFeatures);\n this.featuresLoaded = true;\n if (this.options.enableAdmin && (body as LtAuthFeatures)['admin'] !== true) {\n warnLtOnce(\n 'admin-plugin-unserved',\n '[@lenne.tech/ng-base] provideLtAuth({ enableAdmin: true }) is set, but the API reports no admin plugin (no `admin` in ' +\n '/iam/features): admin.* calls answer 404, and ng-base does not hash the passwords of admin.createUser / ' +\n 'admin.setUserPassword. Switch enableAdmin off unless the backend serves the Better Auth admin routes.',\n );\n }\n if (this.isServer) {\n this.transferState.set(LT_AUTH_FEATURES_STATE_KEY, body as LtAuthFeatures);\n }\n }\n }\n } catch {\n // keep the last known flags\n } finally {\n clearTimeout(timer);\n }\n return this.featuresState();\n }\n\n private send(input: RequestInfo | URL, init: RequestInit, url: string, token: string | null, jwtMode: boolean): Promise<Response> {\n const headers = new Headers(init.headers ?? (input instanceof Request ? input.headers : undefined));\n if (token && !headers.has('Authorization')) {\n headers.set('Authorization', `Bearer ${token}`);\n }\n const credentials: RequestCredentials = init.credentials ?? (jwtMode && !ltUrlRequiresCookies(url) ? 'omit' : 'include');\n return fetch(input, { ...init, credentials, headers });\n }\n\n private persistState(): void {\n this.authState.writeState({ authMode: this.modeState(), user: this.userState() });\n }\n\n private currentUrl(): string | null {\n const router = this.injector.get(Router, null);\n if (router) {\n return router.url;\n }\n const location = this.document.location;\n return location ? `${location.pathname}${location.search}${location.hash}` : null;\n }\n}\n\n/** `user.role === role` or `user.roles` (array) contains it; malformed shapes → `false`. */\nexport function hasLtRole(user: LtUser | null | undefined, role: string): boolean {\n if (!user || !role) {\n return false;\n }\n return user.role === role || (Array.isArray(user.roles) && user.roles.includes(role));\n}\n\nfunction stringOrNull(value: unknown): string | null {\n return typeof value === 'string' && value ? value : null;\n}\n\nasync function runHook(hook: () => Promise<void> | void, name: string): Promise<void> {\n try {\n await hook();\n } catch (error) {\n console.error(`[@lenne.tech/ng-base] A sign-out hook (${name}) failed:`, error);\n }\n}\n\nfunction withTimeout<T>(promise: Promise<T>, ms: number): Promise<T | null> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timeout = new Promise<null>((resolve) => {\n timer = setTimeout(() => resolve(null), ms);\n });\n return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));\n}\n","import { isPlatformServer } from '@angular/common';\nimport { inject, makeStateKey, PLATFORM_ID, Service, signal, TransferState } from '@angular/core';\nimport { LT_FETCH, LtApiUrlService, ltSha256 } from '@lenne.tech/ng-base';\n\nimport type { LtSystemSetupInput, LtSystemSetupStatus } from './auth-types';\n\n/** TransferState key of the setup status read during SSR. */\nexport const LT_SYSTEM_SETUP_STATE_KEY = makeStateKey<LtSystemSetupStatus>('lt-system-setup');\n\n/** Header that carries the setup token. Sent unhashed and never in the body (the server compares it as given). */\nexport const LT_SYSTEM_SETUP_TOKEN_HEADER = 'X-System-Setup-Token';\n\n/**\n * Error thrown by `initSetup()` for a non-2xx answer. `data` is the response body, so\n * `LtErrorTranslationService.translateError(error)` reads its `#LTNS_xxxx` code (`LTNS_0053` HTTP setup disabled,\n * `LTNS_0054` wrong token).\n */\nexport interface LtSystemSetupError extends Error {\n data: unknown;\n status: number;\n statusCode: number;\n}\n\n/**\n * First-admin setup of nest-server (`/system-setup/*`), the counterpart of `useSystemSetup()`.\n * Status is fetched once and transferred SSR → browser.\n */\n@Service()\nexport class LtSystemSetupService {\n private readonly api = inject(LtApiUrlService);\n private readonly fetch = inject(LT_FETCH);\n private readonly transferState = inject(TransferState);\n private readonly isServer = isPlatformServer(inject(PLATFORM_ID));\n private readonly transferred = this.isServer ? null : this.transferState.get(LT_SYSTEM_SETUP_STATE_KEY, null);\n private readonly needsSetupState = signal<boolean | null>(this.transferred ? this.transferred.needsSetup : null);\n private readonly httpInitEnabledState = signal<boolean | null>(this.transferred ? this.transferred.httpInitEnabled !== false : null);\n private readonly httpInitTokenRequiredState = signal<boolean | null>(this.transferred ? this.transferred.httpInitTokenRequired === true : null);\n private statusRequest: Promise<LtSystemSetupStatus> | null = null;\n\n /** `true` when no admin exists yet; `null` until checked. */\n readonly needsSetup = this.needsSetupState.asReadonly();\n /** Whether HTTP init is open (nest-server ≥ 12.4; `true` for older APIs); `null` until checked. */\n readonly httpInitEnabled = this.httpInitEnabledState.asReadonly();\n /** Whether HTTP init requires `X-System-Setup-Token`; `null` until checked. */\n readonly httpInitTokenRequired = this.httpInitTokenRequiredState.asReadonly();\n\n /** `GET /system-setup/status`. Errors/404 → `needsSetup = false`. SSR without API URL → `false` without fetching. */\n checkSetupStatus(): Promise<LtSystemSetupStatus> {\n // Without an API URL the path stays relative; on the server it would never reach the backend. Leave the status\n // undecided, the browser checks once it can resolve the URL.\n if (this.isServer && this.api.resolve().missing) {\n this.api.reportMissing();\n return Promise.resolve({ needsSetup: false });\n }\n if (!this.statusRequest) {\n this.statusRequest = this.loadStatus().finally(() => {\n this.statusRequest = null;\n });\n }\n return this.statusRequest;\n }\n\n /**\n * `POST /system-setup/init` with `{ email, password: sha256, name }`; the token (trimmed, unhashed) goes into\n * `X-System-Setup-Token`. On 403 the status is re-read and the error rethrown (`LTNS_0053` disabled, `LTNS_0054` wrong token).\n */\n async initSetup(input: LtSystemSetupInput): Promise<void> {\n const url = this.api.buildUrl('/system-setup/init');\n const password = await ltSha256(input.password);\n // A header value carries no surrounding whitespace; a pasted line break would make fetch throw.\n const token = input.token?.trim();\n const headers: Record<string, string> = { accept: 'application/json', 'content-type': 'application/json' };\n if (token) {\n headers[LT_SYSTEM_SETUP_TOKEN_HEADER] = token;\n }\n const response = await this.fetch(url, { body: JSON.stringify({ email: input.email, name: input.name, password }), headers, method: 'POST' });\n if (!response.ok) {\n const error = await toSetupError(response);\n if (response.status === 403) {\n // The API may have been restarted with another configuration since the status was read.\n await this.checkSetupStatus();\n }\n throw error;\n }\n this.needsSetupState.set(false);\n }\n\n private async loadStatus(): Promise<LtSystemSetupStatus> {\n try {\n const response = await this.fetch(this.api.buildUrl('/system-setup/status'), { headers: { accept: 'application/json' } });\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`);\n }\n const data = ((await response.json()) ?? {}) as Record<string, unknown>;\n const status: LtSystemSetupStatus = {\n // Missing or malformed reads as released: an API before 12.4.0 accepts the HTTP setup, its 403 is the boundary.\n httpInitEnabled: typeof data['httpInitEnabled'] === 'boolean' ? data['httpInitEnabled'] : true,\n httpInitTokenRequired: data['httpInitTokenRequired'] === true,\n needsSetup: data['needsSetup'] === true,\n };\n this.needsSetupState.set(status.needsSetup);\n this.httpInitEnabledState.set(status.httpInitEnabled ?? true);\n this.httpInitTokenRequiredState.set(status.httpInitTokenRequired ?? false);\n if (this.isServer) {\n this.transferState.set(LT_SYSTEM_SETUP_STATE_KEY, status);\n }\n return status;\n } catch {\n // Error or 404: the endpoint does not exist (older API) → no setup flow.\n this.needsSetupState.set(false);\n return { needsSetup: false };\n }\n }\n}\n\nasync function toSetupError(response: Response): Promise<LtSystemSetupError> {\n const text = await response.text().catch(() => '');\n let data: unknown = text;\n try {\n data = text ? JSON.parse(text) : null;\n } catch {\n // keep the text\n }\n const bodyMessage = data && typeof data === 'object' ? (data as { message?: unknown }).message : data;\n const message = typeof bodyMessage === 'string' && bodyMessage ? bodyMessage : `[POST] /system-setup/init: ${response.status} ${response.statusText}`.trim();\n return Object.assign(new Error(message), { data, status: response.status, statusCode: response.status }) as LtSystemSetupError;\n}\n","import { inject } from '@angular/core';\nimport { Router, type CanActivateChildFn, type CanActivateFn, type RouterStateSnapshot, type UrlTree } from '@angular/router';\n\nimport { LT_AUTH_OPTIONS } from './auth-options';\nimport { hasLtRole, LtAuthStore } from './auth-store';\nimport { ltPathOf } from './auth-urls';\nimport { LtSystemSetupService } from './system-setup';\n\n/**\n * Requires a signed-in user (auth-state cookie; on the server the request cookie). Otherwise redirects to\n * `loginPath?redirect=<url>`. UI gating only: the backend authorises every request itself.\n */\nexport const ltAuthGuard: CanActivateFn & CanActivateChildFn = (_route, state) => (inject(LtAuthStore).isAuthenticated() ? true : loginTree(state));\n\n/** Only for signed-out users (login, register…); a signed-in user is redirected to `redirectTo` (default `/app`). */\nexport function ltGuestGuard(redirectTo = '/app'): CanActivateFn & CanActivateChildFn {\n return () => (inject(LtAuthStore).isAuthenticated() ? inject(Router).parseUrl(redirectTo) : true);\n}\n\n/** Requires one of the roles (`role` or `roles`); signed-out → login, missing role → `redirectTo` (default `/`). */\nexport function ltRoleGuard(roles: string | string[], redirectTo = '/'): CanActivateFn & CanActivateChildFn {\n const wanted = Array.isArray(roles) ? roles : [roles];\n return (_route, state) => {\n const auth = inject(LtAuthStore);\n if (!auth.isAuthenticated()) {\n return loginTree(state);\n }\n const user = auth.user();\n return wanted.some((role) => hasLtRole(user, role)) ? true : inject(Router).parseUrl(redirectTo);\n };\n}\n\n/**\n * First-admin setup redirect (enable with `provideLtAuth({ systemSetup: { enabled: true } })`), put it on the root\n * route as `canActivateChild`. `needsSetup && httpInitEnabled !== false` → setup page; setup done but on the setup\n * page → login page; errors never redirect.\n */\nexport const ltSystemSetupGuard: CanActivateFn & CanActivateChildFn = async (_route, state) => {\n const options = inject(LT_AUTH_OPTIONS);\n if (!options.systemSetup.enabled) {\n return true;\n }\n const setup = inject(LtSystemSetupService);\n const router = inject(Router);\n try {\n if (setup.needsSetup() === null) {\n await setup.checkSetupStatus();\n }\n const path = ltPathOf(state.url);\n const setupPath = options.systemSetup.setupPath;\n // Only an explicit \"HTTP init not released\" suppresses the redirect (the page shows a notice instead).\n if (setup.needsSetup() === true && setup.httpInitEnabled() !== false && path !== setupPath) {\n return router.parseUrl(setupPath);\n }\n if (setup.needsSetup() === false && path === setupPath) {\n return router.parseUrl(options.loginPath);\n }\n } catch {\n // errors never redirect\n }\n return true;\n};\n\nfunction loginTree(state: RouterStateSnapshot): UrlTree {\n const options = inject(LT_AUTH_OPTIONS);\n return inject(Router).createUrlTree([options.loginPath], { queryParams: { redirect: state.url } });\n}\n","import { DOCUMENT, isPlatformServer } from '@angular/common';\nimport { HttpErrorResponse, type HttpEvent, type HttpHandlerFn, type HttpInterceptorFn, type HttpRequest } from '@angular/common/http';\nimport { inject, PLATFORM_ID, Service } from '@angular/core';\nimport { LtApiUrlService } from '@lenne.tech/ng-base';\nimport { catchError, from, type Observable, switchMap, throwError } from 'rxjs';\n\nimport { LtAuthStore } from './auth-store';\nimport { isLtApiRequest } from './auth-urls';\n\n/**\n * Session-loss detection shared by `ltAuthInterceptor` (HttpClient) and the auth-aware `LT_FETCH` (fetch, SSE, sync).\n *\n * On a 401 outside the auth endpoints and public pages while the state says signed in, it probes\n * `GET /iam/get-session` (positive verdict cached 1 s): dead → `clearUser()` and a redirect to\n * `loginPath?redirect=<current url>` (guard held 1 s), alive → nothing (in JWT mode the JWT is re-minted).\n * A probe without verdict (network error, 429, 5xx) never logs out. Server: no-op.\n */\n@Service()\nexport class LtUnauthorizedHandler {\n private readonly auth = inject(LtAuthStore);\n\n /** Handle a 401 for `url`. Resolves to `true` when the session was found dead and the user was redirected. */\n handle(url?: string): Promise<boolean> {\n return this.auth.handleUnauthorized(url);\n }\n}\n\n/**\n * `HttpClient` interceptor for API requests (URLs under the configured API base):\n * sends credentials, adds `Authorization: Bearer` in JWT mode (refreshed first when it is about to expire), retries once after a cookie → JWT switch or a JWT\n * refresh, and hands an unrecoverable 401 to {@link LtUnauthorizedHandler}. A request that brings its own\n * `Authorization` header keeps it and is not retried. Other URLs and server-side requests pass through unchanged.\n *\n * @example provideHttpClient(withInterceptors([ltAuthInterceptor]))\n */\nexport const ltAuthInterceptor: HttpInterceptorFn = (request: HttpRequest<unknown>, next: HttpHandlerFn): Observable<HttpEvent<unknown>> => {\n if (isPlatformServer(inject(PLATFORM_ID))) {\n return next(request);\n }\n const api = inject(LtApiUrlService);\n if (!isLtApiRequest(request.url, api.resolve().baseUrl, inject(DOCUMENT).location?.origin)) {\n return next(request);\n }\n const auth = inject(LtAuthStore);\n const handler = inject(LtUnauthorizedHandler);\n const url = request.urlWithParams;\n const ownAuthorization = request.headers.has('Authorization');\n const authorize = (token: string | null): HttpRequest<unknown> =>\n request.clone(token && !ownAuthorization ? { setHeaders: { Authorization: `Bearer ${token}` }, withCredentials: true } : { withCredentials: true });\n const reportAndRethrow = (error: unknown): Observable<never> => {\n if (isUnauthorized(error)) {\n void handler.handle(url);\n }\n return throwError(() => error);\n };\n const send = (sentToken: string | null): Observable<HttpEvent<unknown>> =>\n next(authorize(sentToken)).pipe(\n catchError((error: unknown) => {\n if (!isUnauthorized(error) || ownAuthorization) {\n return reportAndRethrow(error);\n }\n return from(auth.recoverFrom401(url, sentToken)).pipe(switchMap((token) => (token ? next(authorize(token)).pipe(catchError(reportAndRethrow)) : reportAndRethrow(error))));\n }),\n );\n // In JWT mode a token close to its expiry is refreshed before the request goes out; otherwise it goes out at once.\n return !ownAuthorization && auth.jwtNeedsRefresh() ? from(auth.currentJwt()).pipe(switchMap(send)) : send(ownAuthorization ? null : auth.jwtToken());\n};\n\nfunction isUnauthorized(error: unknown): boolean {\n return error instanceof HttpErrorResponse && error.status === 401;\n}\n","import { isPlatformServer } from '@angular/common';\nimport { inject, makeEnvironmentProviders, PLATFORM_ID, provideAppInitializer, type EnvironmentProviders } from '@angular/core';\nimport { LT_FETCH, type LtFetch } from '@lenne.tech/ng-base';\n\nimport { LT_AUTH_INPUT_OPTIONS, type LtAuthOptions } from './auth-options';\nimport { LtAuthStore } from './auth-store';\n\n/**\n * Auth providers: options, the auth-aware `LT_FETCH` (Bearer in JWT mode, JWT switch/refresh, session-loss handling)\n * used by error translation, AI, sync and the Better Auth client, session validation and feature flags at startup\n * (features fetched on the server and transferred), and the user restored from the auth-state cookie.\n *\n * Startup: the server fetches the feature flags (bounded, anonymous) before rendering and transfers them; the browser\n * takes them from `TransferState` (fetching only when nothing was transferred) and validates the session in the\n * background, so the first render is never held up by the backend.\n *\n * Add `ltAuthInterceptor` to `provideHttpClient(withInterceptors([...]))` for `HttpClient` calls (generated API client).\n */\nexport function provideLtAuth(options: LtAuthOptions = {}): EnvironmentProviders {\n return makeEnvironmentProviders([\n { provide: LT_AUTH_INPUT_OPTIONS, useValue: options },\n {\n provide: LT_FETCH,\n useFactory: (): LtFetch => {\n const auth = inject(LtAuthStore);\n return (input, init) => auth.fetchWithAuth(input, init);\n },\n },\n provideAppInitializer(() => {\n const auth = inject(LtAuthStore);\n if (isPlatformServer(inject(PLATFORM_ID))) {\n return auth.ensureFeatures().then(() => undefined);\n }\n void auth.ensureFeatures();\n void auth.validateSession();\n return undefined;\n }),\n ]);\n}\n"],"mappings":"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"}