@syndew/oluso-auth 0.0.0-stage → 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,776 @@
1
+ import { Observable } from 'rxjs';
2
+ import * as _angular_core from '@angular/core';
3
+ import { InjectionToken } from '@angular/core';
4
+ import { CanActivateFn } from '@angular/router';
5
+ import { HttpInterceptorFn, HttpErrorResponse, HttpRequest, HttpClient } from '@angular/common/http';
6
+
7
+ interface AuthUser {
8
+ id: string;
9
+ email: string;
10
+ name?: string;
11
+ userName?: string;
12
+ displayName?: string;
13
+ firstName?: string;
14
+ lastName?: string;
15
+ picture?: string;
16
+ emailVerified?: boolean;
17
+ roles?: string[];
18
+ permissions?: string[];
19
+ metadata?: Record<string, unknown>;
20
+ organizationIds?: string[];
21
+ organizationRoles?: Record<string, string>;
22
+ isOrgAdmin?: boolean;
23
+ }
24
+
25
+ interface OrganizationContext {
26
+ id: string;
27
+ name: string;
28
+ slug?: string;
29
+ type?: 'personal' | 'team' | 'enterprise';
30
+ role?: string;
31
+ permissions?: string[];
32
+ }
33
+
34
+ interface AuthState {
35
+ user: AuthUser | null;
36
+ accessToken: string | null;
37
+ refreshToken?: string | null;
38
+ idToken?: string | null;
39
+ isAuthenticated: boolean;
40
+ isLoading: boolean;
41
+ error: string | null;
42
+ currentTenantId: string | null;
43
+ currentOrganization: OrganizationContext | null;
44
+ isSuperAdmin: boolean;
45
+ }
46
+
47
+ interface LoginRequest {
48
+ username: string;
49
+ password: string;
50
+ }
51
+
52
+ interface LoginResponse {
53
+ user: AuthUser;
54
+ accessToken: string;
55
+ refreshToken?: string;
56
+ idToken?: string;
57
+ expiresIn?: number;
58
+ }
59
+
60
+ interface OidcConfig {
61
+ authority: string;
62
+ clientId: string;
63
+ redirectUri: string;
64
+ postLogoutRedirectUri?: string;
65
+ scope?: string;
66
+ responseType?: string;
67
+ silentRedirectUri?: string;
68
+ automaticSilentRenew?: boolean;
69
+ loadUserInfo?: boolean;
70
+ }
71
+
72
+ interface TokenPayload {
73
+ sub: string;
74
+ iss: string;
75
+ aud: string | string[];
76
+ exp: number;
77
+ iat: number;
78
+ email?: string;
79
+ name?: string;
80
+ roles?: string[];
81
+ permissions?: string[];
82
+ tenant_id?: string;
83
+ [key: string]: unknown;
84
+ }
85
+
86
+ interface AuthRefreshResult {
87
+ isAuthenticated: boolean;
88
+ accessToken: string | null;
89
+ }
90
+ /**
91
+ * Implemented by the host app to bridge auth concerns to whatever auth library
92
+ * (angular-auth-oidc-client, MSAL, etc.) is in use. Keeps libs/auth free of
93
+ * library-specific dependencies — swapping providers is a single-file change.
94
+ */
95
+ declare abstract class AuthProvider {
96
+ abstract readonly isAuthenticated$: Observable<boolean>;
97
+ abstract getAccessToken(): Observable<string | null>;
98
+ abstract forceRefreshSession(): Observable<AuthRefreshResult>;
99
+ /**
100
+ * Refreshes the session into another tenant: the library's new tokens (access and refresh) are for
101
+ * `tenantId`, and later refreshes stay there. Errors when the server refuses the tenant (the user has no
102
+ * access to it); the session stays signed in, where it was.
103
+ */
104
+ abstract switchTenant(tenantId: string): Observable<AuthRefreshResult>;
105
+ abstract authorize(): void;
106
+ }
107
+ declare class NoOpAuthProvider extends AuthProvider {
108
+ readonly isAuthenticated$: Observable<boolean>;
109
+ getAccessToken(): Observable<string | null>;
110
+ forceRefreshSession(): Observable<AuthRefreshResult>;
111
+ switchTenant(): Observable<AuthRefreshResult>;
112
+ authorize(): void;
113
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<NoOpAuthProvider, never>;
114
+ static ɵprov: _angular_core.ɵɵInjectableDeclaration<NoOpAuthProvider>;
115
+ }
116
+
117
+ /**
118
+ * Serialises token refreshes against the auth library's own background renew.
119
+ *
120
+ * With silent renew on, the library refreshes on a timer. If an API call fails
121
+ * with 401 while that renew is still in flight, a second refresh request is
122
+ * declined by the library, which reports "no session" even though the renew is
123
+ * about to succeed. Left alone, the interceptor would treat that as a signed-out
124
+ * user and redirect to login. Instead, wait for the running renew to settle and
125
+ * answer with the token it produced.
126
+ *
127
+ * Library-agnostic: the adapter supplies the "renew started" and "renew settled"
128
+ * streams and the single-shot refresh call.
129
+ */
130
+ declare abstract class RenewAwareAuthProvider extends AuthProvider {
131
+ /** How long to wait for a running renew before giving up; matches the library's own timeout. */
132
+ protected renewWaitMs: number;
133
+ private renewInFlight;
134
+ private readonly settled$;
135
+ /** One refresh attempt through the library. Reports unauthenticated when the library declines. */
136
+ protected abstract refreshOnce(): Observable<AuthRefreshResult>;
137
+ /**
138
+ * Adapters call this once, in their constructor, with the library's events:
139
+ * `started$` whenever a background renew begins, `settled$` with true/false
140
+ * whenever a renew (or any authentication result) completes.
141
+ */
142
+ protected trackRenew(started$: Observable<unknown>, settled$: Observable<boolean>): void;
143
+ forceRefreshSession(): Observable<AuthRefreshResult>;
144
+ /**
145
+ * A refresh that switches tenant: {@link requestTenantSwitch} adds the token endpoint's `tenant` parameter to
146
+ * that one refresh request, outside the library, so the library neither keeps it for later refreshes nor sees
147
+ * a refusal (which it would answer by signing the user out). A renew already running is let finish first, so
148
+ * the switch redeems the refresh token it leaves behind. Errors unless the new token is for `tenantId`; the
149
+ * session is left where it was.
150
+ */
151
+ switchTenant(tenantId: string): Observable<AuthRefreshResult>;
152
+ private awaitRenew;
153
+ }
154
+ /** The tenant an access token is for (its `tenant_id` claim), or null when it names none or is not a JWT. */
155
+ declare function tenantOfToken(accessToken: string): string | null;
156
+
157
+ /**
158
+ * Tells the app's other tabs when a refresh token has been spent, and hands them what it was exchanged for.
159
+ *
160
+ * Refresh tokens are single use. Tabs can hold the same one: a duplicated tab starts with a copy of its
161
+ * opener's session, and remembered sign-ins share one store across every tab. When one tab redeems it, the
162
+ * others must take the rotated tokens rather than present the spent one, which the server refuses.
163
+ *
164
+ * The tokens travel encrypted under a key derived from the spent refresh token, so only a tab that already
165
+ * held that token can read them; any other tab of the app (another user's session-only sign-in, say) learns
166
+ * nothing. An answer is kept for {@link ROTATION_ANSWER_TTL_MS}, well inside the 120 s the OIDC client
167
+ * allows between an ID token being issued and accepted.
168
+ */
169
+ /** The token entries of the OIDC client's stored state, as written after a refresh. */
170
+ type RotatedTokens = Record<string, unknown>;
171
+ declare class TokenRotationChannel {
172
+ private readonly answers;
173
+ private readonly waiters;
174
+ private readonly publications;
175
+ private readonly listeners;
176
+ private readonly channel;
177
+ constructor(channelName?: string);
178
+ /**
179
+ * Records that `spentRefreshToken` was exchanged for `tokens` and tells the other tabs. Resolves once the
180
+ * other tabs have been told.
181
+ */
182
+ publish(spentRefreshToken: string, tokens: RotatedTokens): Promise<void>;
183
+ /** Resolves once this tab has published the rotation of `refreshToken`, or after `timeoutMs`. */
184
+ awaitPublished(refreshToken: string, timeoutMs: number): Promise<void>;
185
+ /** What `refreshToken` was exchanged for, in this tab or another, while that answer is fresh; else null. */
186
+ answerFor(refreshToken: string): Promise<RotatedTokens | null>;
187
+ /** {@link answerFor}, waiting up to `timeoutMs` for another tab's rotation of `refreshToken` to arrive. */
188
+ waitForAnswer(refreshToken: string, timeoutMs: number): Promise<RotatedTokens | null>;
189
+ /** Calls `listener` with the id of every rotation that arrives from another tab. Returns the unsubscribe. */
190
+ onRotation(listener: (id: string) => void): () => void;
191
+ /** Whether `refreshToken` is the token a rotation with `id` spent. */
192
+ isSpentBy(refreshToken: string, id: string): Promise<boolean>;
193
+ private remember;
194
+ private publication;
195
+ }
196
+
197
+ /**
198
+ * Token storage that follows the user's "Remember me" choice at sign-in.
199
+ *
200
+ * The OIDC client hands this one JSON blob per configuration. Until sign-in
201
+ * completes the blob only holds the pending state (nonce, code verifier) and
202
+ * stays in session storage, which the callback in the same tab can read. Once
203
+ * the blob contains a token response, the ID token says whether the sign-in was
204
+ * remembered: remembered sessions move to local storage, so they survive a
205
+ * browser restart and are shared across tabs; session-only sign-ins stay per
206
+ * tab and vanish when it closes. Sign-out removes both copies.
207
+ *
208
+ * Refresh tokens are single use, and tabs can hold the same one (a duplicated
209
+ * tab starts with a copy of its opener's session; remembered sessions share
210
+ * one store). When a write replaces the refresh token, the spent token and the
211
+ * new tokens go to the app's other tabs; a tab still holding the spent token
212
+ * takes the new ones, so it never presents a token the server has retired.
213
+ *
214
+ * Kept free of the OIDC library: it matches the storage contract by shape and
215
+ * is registered by the app's auth provider.
216
+ */
217
+ declare class RememberMeSecurityStorage {
218
+ private readonly rotations;
219
+ private readonly pendingRotations;
220
+ constructor(rotations?: TokenRotationChannel | null);
221
+ read(key: string): string | null;
222
+ write(key: string, value: string): void;
223
+ private publishRotation;
224
+ /** Takes the tokens another tab rotated to, for every stored session still holding the token it spent. */
225
+ private adoptRotation;
226
+ private authKeys;
227
+ private store;
228
+ remove(key: string): void;
229
+ clear(): void;
230
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<RememberMeSecurityStorage, [{ optional: true; }]>;
231
+ static ɵprov: _angular_core.ɵɵInjectableDeclaration<RememberMeSecurityStorage>;
232
+ }
233
+
234
+ /** The parts of the app's OIDC client the startup check needs; the library's security service fits as is. */
235
+ interface StartupAuthClient<R extends {
236
+ isAuthenticated: boolean;
237
+ }> {
238
+ /** Validates the stored tokens (or completes a sign-in callback) without calling the server. */
239
+ checkAuth(): Observable<R>;
240
+ /** {@link checkAuth}, then, when that finds no valid session, a refresh with the stored refresh token. */
241
+ checkAuthIncludingServer(): Observable<R>;
242
+ getRefreshToken(): Observable<string | null | undefined>;
243
+ }
244
+ /**
245
+ * The app's startup auth check. A stored access token that has expired (or is inside the renew window) makes the
246
+ * plain check report "signed out", and the app would send the user through sign-in again although the refresh
247
+ * token is good for days. When a refresh token is stored this renews with it instead, which also restarts the
248
+ * library's background renew. A sign-in callback is completed by the plain check alone, and a refresh the server
249
+ * refuses (refresh token expired or revoked) leaves the user signed out, as before.
250
+ *
251
+ * The refresh goes through the app's HttpClient, so the token-refresh interceptor still coordinates it with the
252
+ * app's other tabs.
253
+ */
254
+ declare function checkAuthRenewingSession<R extends {
255
+ isAuthenticated: boolean;
256
+ }>(client: StartupAuthClient<R>, url?: string): Observable<R>;
257
+
258
+ /**
259
+ * Auth Store
260
+ *
261
+ * Signal-based authentication state management.
262
+ * This mirrors the Zustand authSlice from the React codebase.
263
+ *
264
+ * Features:
265
+ * - Persistent state via localStorage
266
+ * - Tenant/organization context
267
+ * - Super admin support
268
+ */
269
+ declare class AuthStore {
270
+ private readonly authProvider;
271
+ private readonly _user;
272
+ private readonly _accessToken;
273
+ private readonly _refreshToken;
274
+ private readonly _idToken;
275
+ private readonly _isLoading;
276
+ private readonly _error;
277
+ private readonly _currentTenantId;
278
+ /** True once the user has picked a tenant in the UI; only then do requests carry X-Tenant-ID. */
279
+ private readonly _tenantSwitched;
280
+ private readonly _currentOrganization;
281
+ private readonly _isSuperAdmin;
282
+ private readonly _hasHydrated;
283
+ /** Read-only computed state */
284
+ readonly user: _angular_core.Signal<AuthUser | null>;
285
+ readonly accessToken: _angular_core.Signal<string | null>;
286
+ readonly refreshToken: _angular_core.Signal<string | null>;
287
+ readonly idToken: _angular_core.Signal<string | null>;
288
+ readonly isLoading: _angular_core.Signal<boolean>;
289
+ readonly error: _angular_core.Signal<string | null>;
290
+ readonly currentTenantId: _angular_core.Signal<string | null>;
291
+ readonly tenantSwitched: _angular_core.Signal<boolean>;
292
+ readonly currentOrganization: _angular_core.Signal<OrganizationContext | null>;
293
+ readonly isSuperAdmin: _angular_core.Signal<boolean>;
294
+ readonly hasHydrated: _angular_core.Signal<boolean>;
295
+ readonly isAuthenticated: _angular_core.Signal<boolean>;
296
+ /** Full state snapshot */
297
+ readonly state: _angular_core.Signal<AuthState>;
298
+ constructor();
299
+ /**
300
+ * Set user and tokens after successful login.
301
+ * Extracts tenant_id from the JWT access token if present.
302
+ */
303
+ login(user: AuthUser, accessToken: string, refreshToken?: string, idToken?: string): void;
304
+ /**
305
+ * Makes the current tenant the one the token is for. A switch mints a token for its target, so a current
306
+ * tenant naming any other was left by an earlier session (or another app on the origin): it is replaced, and
307
+ * the session counts as not switched, so requests carry no X-Tenant-ID.
308
+ */
309
+ alignTenantWithToken(accessToken: string): void;
310
+ /**
311
+ * Clear authentication state on logout
312
+ */
313
+ logout(): void;
314
+ /**
315
+ * Update user information
316
+ */
317
+ setUser(user: AuthUser | null): void;
318
+ /**
319
+ * Update access token
320
+ */
321
+ setAccessToken(token: string | null): void;
322
+ /**
323
+ * Update refresh token
324
+ */
325
+ setRefreshToken(token: string | null): void;
326
+ /**
327
+ * Set current tenant ID (for super admin)
328
+ */
329
+ setCurrentTenantId(tenantId: string | null): void;
330
+ /**
331
+ * Adopt a tenant switch: the new context and the token minted for it.
332
+ *
333
+ * Both move together deliberately. Roles and permissions are per-tenant, so a
334
+ * moment where the header names one tenant and the token carries another
335
+ * either over-grants or under-grants — and a persist() between the two writes
336
+ * would make that state survive a reload.
337
+ */
338
+ switchTenant(tenantId: string, accessToken: string): void;
339
+ /**
340
+ * Set current organization context
341
+ */
342
+ setCurrentOrganization(organization: OrganizationContext | null): void;
343
+ /**
344
+ * Set super admin status
345
+ */
346
+ setSuperAdmin(isSuperAdmin: boolean): void;
347
+ /**
348
+ * Set loading state
349
+ */
350
+ setLoading(isLoading: boolean): void;
351
+ /**
352
+ * Set error message
353
+ */
354
+ setError(error: string | null): void;
355
+ /**
356
+ * Hydrate state from localStorage
357
+ */
358
+ private hydrate;
359
+ /**
360
+ * Persist state to localStorage
361
+ */
362
+ private persist;
363
+ /**
364
+ * Clear localStorage
365
+ */
366
+ private clearStorage;
367
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<AuthStore, never>;
368
+ static ɵprov: _angular_core.ɵɵInjectableDeclaration<AuthStore>;
369
+ }
370
+
371
+ /**
372
+ * Auth Service
373
+ *
374
+ * Handles authentication operations including login, logout, token refresh,
375
+ * and user management.
376
+ *
377
+ * This mirrors the auth services from the React codebase.
378
+ */
379
+ declare class AuthService {
380
+ private readonly http;
381
+ private readonly authStore;
382
+ private apiBaseUrl;
383
+ /**
384
+ * Configure the API base URL
385
+ */
386
+ setApiBaseUrl(url: string): void;
387
+ /**
388
+ * Login with email and password
389
+ */
390
+ login(request: LoginRequest): Observable<LoginResponse>;
391
+ /**
392
+ * Logout the current user
393
+ */
394
+ logout(): Observable<void>;
395
+ /**
396
+ * Logout locally without API call
397
+ */
398
+ logoutLocal(): void;
399
+ /**
400
+ * Refresh the access token
401
+ */
402
+ refreshToken(): Observable<{
403
+ accessToken: string;
404
+ refreshToken?: string;
405
+ }>;
406
+ /**
407
+ * Get the current user profile
408
+ */
409
+ getProfile(): Observable<AuthUser>;
410
+ /**
411
+ * Update the current user profile
412
+ */
413
+ updateProfile(data: Partial<AuthUser>): Observable<AuthUser>;
414
+ /**
415
+ * Check if the current access token is expired
416
+ */
417
+ isTokenExpired(): boolean;
418
+ /**
419
+ * Decode a JWT token
420
+ */
421
+ decodeToken(token: string): TokenPayload;
422
+ /**
423
+ * Get the current access token
424
+ */
425
+ getAccessToken(): string | null;
426
+ /**
427
+ * Check if the user is authenticated
428
+ */
429
+ isAuthenticated(): boolean;
430
+ /**
431
+ * Get the current user
432
+ */
433
+ getUser(): AuthUser | null;
434
+ /**
435
+ * Check if the user is a super admin
436
+ */
437
+ isSuperAdmin(): boolean;
438
+ /**
439
+ * Get the current tenant ID
440
+ */
441
+ getCurrentTenantId(): string | null;
442
+ /**
443
+ * Set the current tenant ID (for super admin)
444
+ */
445
+ setCurrentTenantId(tenantId: string | null): void;
446
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<AuthService, never>;
447
+ static ɵprov: _angular_core.ɵɵInjectableDeclaration<AuthService>;
448
+ }
449
+
450
+ /**
451
+ * Auth Guard
452
+ *
453
+ * Protects routes that require authentication.
454
+ * Redirects to login if user is not authenticated.
455
+ */
456
+ declare const authGuard: CanActivateFn;
457
+ /**
458
+ * Guest Guard
459
+ *
460
+ * Protects routes that should only be accessible to non-authenticated users.
461
+ * Redirects to dashboard if user is already authenticated.
462
+ */
463
+ declare const guestGuard: CanActivateFn;
464
+ /**
465
+ * Super Admin Guard
466
+ *
467
+ * Protects routes that require super admin privileges.
468
+ * Checks PermissionsService which is set during login/permission loading.
469
+ */
470
+ declare const superAdminGuard: CanActivateFn;
471
+ /**
472
+ * Org Admin Guard
473
+ *
474
+ * Protects routes that require org admin or super admin privileges.
475
+ * Super admins can also access org admin routes.
476
+ * Requires a valid organization context (currentOrganization) in the auth store.
477
+ */
478
+ declare const orgAdminGuard: CanActivateFn;
479
+
480
+ /**
481
+ * How long a permission guard waits for the app to load the current user's
482
+ * permissions before denying. Override to tune per app.
483
+ */
484
+ declare const PERMISSION_GUARD_TIMEOUT_MS: InjectionToken<number>;
485
+ /**
486
+ * Permission Guard
487
+ *
488
+ * Protects routes that require specific permissions.
489
+ * Configure the required permission in route data.
490
+ *
491
+ * Usage:
492
+ * ```typescript
493
+ * {
494
+ * path: 'users',
495
+ * component: UsersPageComponent,
496
+ * canActivate: [permissionGuard],
497
+ * data: { permission: 'users.read' }
498
+ * }
499
+ * ```
500
+ */
501
+ declare const permissionGuard: CanActivateFn;
502
+ /**
503
+ * Any Permission Guard
504
+ *
505
+ * Allows access if user has any of the specified permissions.
506
+ *
507
+ * Usage:
508
+ * ```typescript
509
+ * {
510
+ * path: 'resources',
511
+ * component: ResourcesPageComponent,
512
+ * canActivate: [anyPermissionGuard],
513
+ * data: { permissions: ['resources.read', 'resources.write'] }
514
+ * }
515
+ * ```
516
+ */
517
+ declare const anyPermissionGuard: CanActivateFn;
518
+ /**
519
+ * All Permissions Guard
520
+ *
521
+ * Allows access only if user has all of the specified permissions.
522
+ *
523
+ * Usage:
524
+ * ```typescript
525
+ * {
526
+ * path: 'admin',
527
+ * component: AdminPageComponent,
528
+ * canActivate: [allPermissionsGuard],
529
+ * data: { permissions: ['admin.read', 'admin.write'] }
530
+ * }
531
+ * ```
532
+ */
533
+ declare const allPermissionsGuard: CanActivateFn;
534
+
535
+ /**
536
+ * Helpers for the tenant-aware SSO landing flow shared across host portals.
537
+ *
538
+ * The "Login with SSO" affordance on the landing page resolves a tenant by
539
+ * identifier and persists its issuer URI to sessionStorage. The host's OIDC
540
+ * config picks that up at module load (next page navigation) so the OIDC
541
+ * authority points at the tenant's identity-server subdomain instead of the
542
+ * platform default. After resolve + persist, the landing reloads with
543
+ * `pendingAuthorize` set so the post-load handler can call authorize() with
544
+ * the right authority and the tenant's domain_hint / policy / ui_mode.
545
+ */
546
+ interface TenantSso {
547
+ identifier: string;
548
+ issuerUri: string;
549
+ defaultIdpScheme?: string;
550
+ defaultSignInPolicyId?: string;
551
+ defaultSsoUiMode?: 'Journey' | 'Standalone';
552
+ }
553
+ /**
554
+ * Extract tenant identifier from hostname subdomain.
555
+ * e.g., "acme.auth.example.com" → "acme"
556
+ * Returns undefined for localhost, IP addresses, or hostnames with fewer than 3 parts.
557
+ */
558
+ declare function resolveTenantFromHostname(): string | undefined;
559
+ /**
560
+ * Returns the tenant SSO authority persisted in sessionStorage, or the given
561
+ * default. Safe in SSR / non-browser contexts.
562
+ */
563
+ declare function resolveTenantSsoAuthority(defaultAuthority: string): string;
564
+ /**
565
+ * Returns the tenant identifier, issuer URI, and any persisted SSO defaults,
566
+ * or null if none is set.
567
+ */
568
+ declare function readTenantSso(): TenantSso | null;
569
+ /**
570
+ * Persists the tenant SSO context resolved by the landing page. Set
571
+ * `markPendingAuthorize` so the post-reload landing handler kicks off
572
+ * authorize() with the tenant's authority + the stored hints.
573
+ *
574
+ * Wipes angular-auth-oidc-client's cached well-known + state on the way
575
+ * out — the cache is keyed by config id, not authority, so switching
576
+ * tenants would otherwise reuse the previous authority's discovery doc
577
+ * and throw issuer-mismatch on the next authorize call.
578
+ */
579
+ declare function writeTenantSso(sso: TenantSso, markPendingAuthorize?: boolean): void;
580
+ /**
581
+ * Build the customParams object to pass to oidcSecurityService.authorize()
582
+ * from a remembered tenant SSO. Returns undefined if there's nothing worth
583
+ * passing. Callers feed this into the `customParams` field of the authorize
584
+ * call so domain_hint / policy / ui_mode reach /connect/authorize.
585
+ */
586
+ declare function tenantSsoAuthorizeParams(sso: TenantSso): Record<string, string> | undefined;
587
+ /**
588
+ * Returns true (and clears the flag) when a pending authorize call was queued
589
+ * by the landing's SSO submit. Use on landing init to auto-trigger
590
+ * oidcSecurityService.authorize() after a reload picked up the new authority.
591
+ */
592
+ declare function consumePendingAuthorize(): boolean;
593
+ /**
594
+ * Removes all persisted tenant SSO state. Call from logout flows and the
595
+ * "Use a different organisation" affordance on the landing.
596
+ *
597
+ * Also clears angular-auth-oidc-client's sessionStorage cache (well-known
598
+ * discovery doc, in-flight state) — those entries are keyed by config id,
599
+ * not by authority URL, so without wiping them a previously-cached
600
+ * discovery for a different tenant authority survives the switch and
601
+ * throws an "issuer mismatch" on the next authorize call.
602
+ */
603
+ declare function clearTenantSso(): void;
604
+
605
+ /**
606
+ * Auth Interceptor
607
+ *
608
+ * Adds the Authorization header with the access token to outgoing requests.
609
+ * Handles 401 errors by attempting to refresh the token.
610
+ */
611
+ declare const authInterceptor: HttpInterceptorFn;
612
+
613
+ /**
614
+ * Token attachment fallback + 401 retry with refresh deduplication. On refresh
615
+ * failure, stashes the current route in sessionStorage under an opaque key so
616
+ * the post-login callback can restore navigation without leaking the route to
617
+ * URL/history/server logs.
618
+ *
619
+ * Skips the OIDC token endpoint to avoid infinite loops if /connect/token 401s.
620
+ */
621
+ declare const authRefreshInterceptor: HttpInterceptorFn;
622
+ /**
623
+ * Remembers the page the user is on before a sign-in redirect ends their session, so the callback page can
624
+ * bring them back to it ({@link readAndClearReturnUrl}). The callback, renew and error pages, and the root,
625
+ * are not remembered.
626
+ */
627
+ declare function rememberReturnUrl(): void;
628
+ /**
629
+ * Helper for callback pages to restore the pre-login route.
630
+ */
631
+ declare function readAndClearReturnUrl(): string | null;
632
+
633
+ /**
634
+ * Makes each refresh token redeem exactly once across all of the app's tabs, and keeps that redemption alive
635
+ * through a brief server outage.
636
+ *
637
+ * Refresh tokens are single use. Three things would otherwise present one twice:
638
+ * - the OIDC client abandons a renew it considers slow (its periodic check cancels the request every few
639
+ * seconds) and later starts another with the same token;
640
+ * - in one tab, a 401 and the background renew can both ask for a refresh;
641
+ * - tabs can hold the same token (a duplicated tab copies its opener's session; remembered sessions share one
642
+ * store), and each renews on its own timer.
643
+ * So one exchange runs per refresh token whether or not its caller still listens, refreshes are taken in turn
644
+ * across tabs under a Web Lock, and a request for a token another exchange already redeemed — here or in
645
+ * another tab ({@link tokenRotationChannel}) — is answered with what it was exchanged for, its lifetime
646
+ * counted from when the server issued it.
647
+ *
648
+ * The client also deletes the session on any HTTP error, so a 502 from the proxy while the API restarts would
649
+ * sign the user out although the refresh token is still good. Transient statuses are retried after the
650
+ * server's Retry-After (up to 30 s), else 1 s doubling to 10 s; a refusal such as invalid_grant still reaches
651
+ * the client.
652
+ */
653
+ declare const tokenRefreshRetryInterceptor: HttpInterceptorFn;
654
+ /** The refresh token a token request redeems, or null for any other request. */
655
+ declare function refreshTokenOf(req: HttpRequest<unknown>): string | null;
656
+ /**
657
+ * Makes the next refresh a switch to `tenantId` (the token endpoint's `tenant` parameter), or cancels a switch
658
+ * not yet sent. The parameter rides that one request only: the auth library never holds it, so later refreshes
659
+ * carry none (the refresh token remembers the tenant), and a refused switch never reaches the library as a
660
+ * failure — it is answered by a plain refresh instead.
661
+ */
662
+ declare function requestTenantSwitch(tenantId: string | null): void;
663
+ /** Wait before retry number `attempt` (1-based): the server's Retry-After seconds when it sends them (at most
664
+ * 30 s), else 1 s doubling to 10 s. */
665
+ declare function refreshRetryDelayMs(error: HttpErrorResponse, attempt: number): number;
666
+
667
+ /**
668
+ * Tenant Interceptor
669
+ *
670
+ * Adds the X-Tenant-ID header to outgoing API requests when the user
671
+ * has selected a tenant context.
672
+ *
673
+ * This is used in the Admin UI to allow super admins to manage
674
+ * resources across different tenants. The backend validates
675
+ * whether the user is authorized to access the specified tenant.
676
+ *
677
+ * OIDC protocol endpoints are excluded — they must always use the
678
+ * global (non-tenant) context so the issuer stays consistent.
679
+ */
680
+ declare const tenantInterceptor: HttpInterceptorFn;
681
+ /**
682
+ * Organization Interceptor
683
+ *
684
+ * Adds the X-Organization-ID header to outgoing requests when the user
685
+ * has selected an organization context.
686
+ *
687
+ * This is used for multi-tenant applications where users can belong
688
+ * to multiple organizations.
689
+ */
690
+ declare const organizationInterceptor: HttpInterceptorFn;
691
+
692
+ /**
693
+ * Ngrok Interceptor
694
+ *
695
+ * Adds the ngrok-skip-browser-warning header to skip the ngrok interstitial page.
696
+ * This header is required when making API calls through ngrok tunnels.
697
+ */
698
+ declare const ngrokInterceptor: HttpInterceptorFn;
699
+
700
+ /**
701
+ * Error Interceptor
702
+ *
703
+ * Catches HTTP error responses and displays user-friendly toast notifications.
704
+ * Extracts error messages from backend responses when available.
705
+ * Skips 401 errors (handled by the auth interceptor).
706
+ */
707
+ declare const errorInterceptor: HttpInterceptorFn;
708
+
709
+ /**
710
+ * The browser's DPoP key (RFC 9449): an ECDSA P-256 key pair whose private half the page can sign with but
711
+ * never read, so a token bound to it cannot be used anywhere else.
712
+ *
713
+ * The pair is kept in IndexedDB, shared by every tab of the app and surviving reloads, because refresh tokens
714
+ * are bound to it: a remembered session's next refresh needs the same key. Where IndexedDB is unavailable the
715
+ * key lives for the page only, and the session ends with it.
716
+ */
717
+ /** The public half, as it goes in a proof's <c>jwk</c> header. */
718
+ interface DPoPPublicJwk {
719
+ kty: 'EC';
720
+ crv: 'P-256';
721
+ x: string;
722
+ y: string;
723
+ }
724
+ interface DPoPKey {
725
+ readonly privateKey: CryptoKey;
726
+ readonly publicJwk: DPoPPublicJwk;
727
+ }
728
+ /** Whether this page can hold a DPoP key: Web Crypto, which browsers offer only in secure contexts. */
729
+ declare function dpopSupported(): boolean;
730
+ /** The app's DPoP key, loaded or created once per page. */
731
+ declare function dpopKey(): Promise<DPoPKey>;
732
+ /** Forgets the key held by this page, so the next {@link dpopKey} reads storage again (tests). */
733
+ declare function resetDPoPKeyForTests(): void;
734
+
735
+ /** What a DPoP proof (RFC 9449 §4.2) is for: one HTTP request, and the access token it presents if any. */
736
+ interface DPoPProofTarget {
737
+ method: string;
738
+ url: string;
739
+ accessToken?: string | null;
740
+ nonce?: string | null;
741
+ }
742
+ /** A DPoP proof JWT signed by `key` for one request. */
743
+ declare function createDPoPProof(key: DPoPKey, target: DPoPProofTarget): Promise<string>;
744
+ /** The request URI a proof names: absolute, without query or fragment. */
745
+ declare function proofUri(url: string): string;
746
+ /** The <c>ath</c> claim: the base64url SHA-256 of the access token. */
747
+ declare function accessTokenHash(accessToken: string): Promise<string>;
748
+
749
+ /**
750
+ * Sender-constrains the app's tokens with DPoP (RFC 9449).
751
+ *
752
+ * - Token requests (any grant) carry a proof, so the server binds the tokens it issues — access and refresh —
753
+ * to this browser's key ({@link dpopKey}); a copied token is useless without it.
754
+ * - A request presenting a DPoP-bound access token (one whose <c>cnf.jkt</c> names a key) is sent under the
755
+ * DPoP scheme with a proof for that request and token instead of as a bearer token, which the server refuses.
756
+ * - When a server asks for a nonce (<c>use_dpop_nonce</c>), the request is retried once with it.
757
+ *
758
+ * Register it after the interceptors that attach the access token, so it sees the final request.
759
+ * Pages without Web Crypto (non-secure contexts) send requests unchanged; their tokens are not bound.
760
+ */
761
+ declare const dpopInterceptor: HttpInterceptorFn;
762
+ /** The access token a request presents, when it is bound to a DPoP key; else null. */
763
+ declare function boundAccessTokenOf(req: HttpRequest<unknown>): string | null;
764
+ /** Whether a JWT access token is bound to a DPoP key (has <c>cnf.jkt</c>). */
765
+ declare function isDPoPBound(token: string): boolean;
766
+
767
+ /**
768
+ * A `fetch` that sends through the app's HttpClient, so its interceptors apply: the signed-in user's access token,
769
+ * the tenant, DPoP proofs and the refresh-and-retry on an expired token. Give it to the Oluso SDK
770
+ * (`new OlusoClient({ ..., fetch: httpClientFetch(http) })`) to run journeys the app's backend started for the user.
771
+ * Answers with an error status resolve like fetch's do, so the SDK reads the server's message.
772
+ */
773
+ declare function httpClientFetch(http: HttpClient): typeof fetch;
774
+
775
+ export { AuthProvider, AuthService, AuthStore, NoOpAuthProvider, PERMISSION_GUARD_TIMEOUT_MS, RememberMeSecurityStorage, RenewAwareAuthProvider, accessTokenHash, allPermissionsGuard, anyPermissionGuard, authGuard, authInterceptor, authRefreshInterceptor, boundAccessTokenOf, checkAuthRenewingSession, clearTenantSso, consumePendingAuthorize, createDPoPProof, dpopInterceptor, dpopKey, dpopSupported, errorInterceptor, guestGuard, httpClientFetch, isDPoPBound, ngrokInterceptor, orgAdminGuard, organizationInterceptor, permissionGuard, proofUri, readAndClearReturnUrl, readTenantSso, refreshRetryDelayMs, refreshTokenOf, rememberReturnUrl, requestTenantSwitch, resetDPoPKeyForTests, resolveTenantFromHostname, resolveTenantSsoAuthority, superAdminGuard, tenantInterceptor, tenantOfToken, tenantSsoAuthorizeParams, tokenRefreshRetryInterceptor, writeTenantSso };
776
+ export type { AuthRefreshResult, AuthState, AuthUser, DPoPKey, DPoPProofTarget, DPoPPublicJwk, LoginRequest, LoginResponse, OidcConfig, OrganizationContext, StartupAuthClient, TenantSso, TokenPayload };