@meshmakers/shared-auth 3.3.690 → 3.3.710
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.
|
@@ -1,8 +1,190 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { inject, signal, computed, Injectable, makeEnvironmentProviders } from '@angular/core';
|
|
3
|
-
import { OAuthService, provideOAuthClient } from 'angular-oauth2-oidc';
|
|
3
|
+
import { OAuthStorage, OAuthService, provideOAuthClient } from 'angular-oauth2-oidc';
|
|
4
4
|
import { Router } from '@angular/router';
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Known OAuth storage keys used by angular-oauth2-oidc.
|
|
8
|
+
* Used by clearAllTenants() to identify and remove OAuth-related entries
|
|
9
|
+
* across all tenants without affecting other localStorage data.
|
|
10
|
+
*/
|
|
11
|
+
const OAUTH_STORAGE_KEYS = [
|
|
12
|
+
'access_token',
|
|
13
|
+
'refresh_token',
|
|
14
|
+
'id_token',
|
|
15
|
+
'id_token_claims_obj',
|
|
16
|
+
'id_token_header',
|
|
17
|
+
'expires_at',
|
|
18
|
+
'access_token_stored_at',
|
|
19
|
+
'id_token_expires_at',
|
|
20
|
+
'id_token_stored_at',
|
|
21
|
+
'nonce',
|
|
22
|
+
'PKCE_verifier',
|
|
23
|
+
'session_state',
|
|
24
|
+
'granted_scopes',
|
|
25
|
+
'requested_route',
|
|
26
|
+
];
|
|
27
|
+
/**
|
|
28
|
+
* Keys that are specific to a single OAuth authorization flow and must
|
|
29
|
+
* be isolated per browser tab. These are stored in sessionStorage
|
|
30
|
+
* (which is per-tab) to prevent cross-tab nonce/PKCE collisions.
|
|
31
|
+
*
|
|
32
|
+
* Without this, two tabs starting login simultaneously would overwrite
|
|
33
|
+
* each other's nonce in shared localStorage, causing "invalid_nonce_in_state"
|
|
34
|
+
* errors when the first tab's callback tries to validate.
|
|
35
|
+
*/
|
|
36
|
+
const SESSION_SCOPED_KEYS = new Set([
|
|
37
|
+
'nonce',
|
|
38
|
+
'PKCE_verifier',
|
|
39
|
+
'requested_route',
|
|
40
|
+
]);
|
|
41
|
+
/**
|
|
42
|
+
* SessionStorage key used to persist the current storage tenant ID across
|
|
43
|
+
* page reloads (e.g., OAuth redirects). The tenant ID must survive the
|
|
44
|
+
* round-trip to the identity server because the redirect URI may not
|
|
45
|
+
* contain the tenant path segment.
|
|
46
|
+
*/
|
|
47
|
+
const STORAGE_TENANT_KEY = 'octo_storage_tenant';
|
|
48
|
+
/**
|
|
49
|
+
* Tenant-aware OAuth storage that prefixes all keys with a tenant ID
|
|
50
|
+
* and splits storage between localStorage and sessionStorage.
|
|
51
|
+
*
|
|
52
|
+
* **Key prefixing:** When a tenant ID is set, all storage keys are prefixed
|
|
53
|
+
* with `{tenantId}__` (double underscore separator). This isolates OAuth
|
|
54
|
+
* tokens per tenant, preventing race conditions during tenant switches.
|
|
55
|
+
*
|
|
56
|
+
* **Storage split:** Flow-specific ephemeral keys (nonce, PKCE_verifier)
|
|
57
|
+
* are stored in sessionStorage (per browser tab), while tokens and session
|
|
58
|
+
* data are stored in localStorage (shared across tabs). This prevents
|
|
59
|
+
* cross-tab nonce collisions when multiple tabs initiate login simultaneously.
|
|
60
|
+
*
|
|
61
|
+
* When no tenant ID is set (null), keys are stored without a prefix,
|
|
62
|
+
* maintaining backwards compatibility with existing single-tenant apps.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```typescript
|
|
66
|
+
* const storage = new TenantAwareOAuthStorage();
|
|
67
|
+
* storage.setTenantId('maco');
|
|
68
|
+
* storage.setItem('access_token', 'abc');
|
|
69
|
+
* // Stored in: localStorage['maco__access_token'] = 'abc'
|
|
70
|
+
*
|
|
71
|
+
* storage.setItem('nonce', 'xyz');
|
|
72
|
+
* // Stored in: sessionStorage['maco__nonce'] = 'xyz' (per-tab!)
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
class TenantAwareOAuthStorage extends OAuthStorage {
|
|
76
|
+
tenantId = null;
|
|
77
|
+
/**
|
|
78
|
+
* Sets the tenant ID used to prefix storage keys.
|
|
79
|
+
* Must be called before the OAuthService reads/writes tokens.
|
|
80
|
+
* The tenant ID is persisted in sessionStorage so it survives OAuth redirects.
|
|
81
|
+
*
|
|
82
|
+
* @param tenantId The tenant ID, or null for unprefixed (backwards-compatible) mode.
|
|
83
|
+
*/
|
|
84
|
+
setTenantId(tenantId) {
|
|
85
|
+
this.tenantId = tenantId;
|
|
86
|
+
try {
|
|
87
|
+
if (tenantId) {
|
|
88
|
+
sessionStorage.setItem(STORAGE_TENANT_KEY, tenantId);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
sessionStorage.removeItem(STORAGE_TENANT_KEY);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
// sessionStorage may be unavailable
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Returns the currently configured tenant ID.
|
|
100
|
+
*/
|
|
101
|
+
getTenantId() {
|
|
102
|
+
return this.tenantId;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Restores the tenant ID from sessionStorage.
|
|
106
|
+
* Call this on app startup when the tenant cannot be determined from the URL
|
|
107
|
+
* (e.g., after an OAuth redirect to the root path).
|
|
108
|
+
*
|
|
109
|
+
* @returns The restored tenant ID, or null if none was persisted.
|
|
110
|
+
*/
|
|
111
|
+
restoreTenantId() {
|
|
112
|
+
try {
|
|
113
|
+
const stored = sessionStorage.getItem(STORAGE_TENANT_KEY);
|
|
114
|
+
if (stored) {
|
|
115
|
+
this.tenantId = stored;
|
|
116
|
+
return stored;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
// sessionStorage may be unavailable
|
|
121
|
+
}
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Returns the prefixed key for the given base key.
|
|
126
|
+
* When tenantId is null, returns the base key unchanged.
|
|
127
|
+
*/
|
|
128
|
+
prefixKey(key) {
|
|
129
|
+
if (this.tenantId) {
|
|
130
|
+
return `${this.tenantId}__${key}`;
|
|
131
|
+
}
|
|
132
|
+
return key;
|
|
133
|
+
}
|
|
134
|
+
getItem(key) {
|
|
135
|
+
const prefixed = this.prefixKey(key);
|
|
136
|
+
if (SESSION_SCOPED_KEYS.has(key)) {
|
|
137
|
+
return sessionStorage.getItem(prefixed);
|
|
138
|
+
}
|
|
139
|
+
return localStorage.getItem(prefixed);
|
|
140
|
+
}
|
|
141
|
+
removeItem(key) {
|
|
142
|
+
const prefixed = this.prefixKey(key);
|
|
143
|
+
if (SESSION_SCOPED_KEYS.has(key)) {
|
|
144
|
+
sessionStorage.removeItem(prefixed);
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
localStorage.removeItem(prefixed);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
setItem(key, data) {
|
|
151
|
+
const prefixed = this.prefixKey(key);
|
|
152
|
+
if (SESSION_SCOPED_KEYS.has(key)) {
|
|
153
|
+
sessionStorage.setItem(prefixed, data);
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
localStorage.setItem(prefixed, data);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Clears all OAuth-related keys for ALL tenants from both
|
|
161
|
+
* localStorage and sessionStorage, while leaving non-OAuth data intact.
|
|
162
|
+
*
|
|
163
|
+
* Used during full logout to ensure no stale tokens remain for any tenant.
|
|
164
|
+
*/
|
|
165
|
+
clearAllTenants() {
|
|
166
|
+
this.clearOAuthKeysFromStorage(localStorage);
|
|
167
|
+
this.clearOAuthKeysFromStorage(sessionStorage);
|
|
168
|
+
}
|
|
169
|
+
clearOAuthKeysFromStorage(storage) {
|
|
170
|
+
const keysToRemove = [];
|
|
171
|
+
for (let i = 0; i < storage.length; i++) {
|
|
172
|
+
const storageKey = storage.key(i);
|
|
173
|
+
if (!storageKey)
|
|
174
|
+
continue;
|
|
175
|
+
for (const oauthKey of OAUTH_STORAGE_KEYS) {
|
|
176
|
+
if (storageKey === oauthKey || storageKey.endsWith('__' + oauthKey)) {
|
|
177
|
+
keysToRemove.push(storageKey);
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
for (const key of keysToRemove) {
|
|
183
|
+
storage.removeItem(key);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
6
188
|
class AuthorizeOptions {
|
|
7
189
|
wellKnownServiceUris;
|
|
8
190
|
// Url of the Identity Provider
|
|
@@ -38,6 +220,8 @@ class AuthorizeService {
|
|
|
38
220
|
_tokenTenantId = signal(null, ...(ngDevMode ? [{ debugName: "_tokenTenantId" }] : /* istanbul ignore next */ []));
|
|
39
221
|
static TENANT_REAUTH_KEY = 'octo_tenant_reauth';
|
|
40
222
|
static TENANT_SWITCH_ATTEMPTED_KEY = 'octo_tenant_switch_attempted';
|
|
223
|
+
tenantStorage = new TenantAwareOAuthStorage();
|
|
224
|
+
_loginInProgress = false;
|
|
41
225
|
authorizeOptions = null;
|
|
42
226
|
lastAuthConfig = null;
|
|
43
227
|
// =============================================================================
|
|
@@ -114,9 +298,19 @@ class AuthorizeService {
|
|
|
114
298
|
// Reload the page to trigger the auth flow and redirect to login
|
|
115
299
|
this.reloadPage();
|
|
116
300
|
}
|
|
301
|
+
if (e.type === "token_refresh_error") {
|
|
302
|
+
console.warn("AuthorizeService: Token refresh failed — clearing session and redirecting to login");
|
|
303
|
+
this._accessToken.set(null);
|
|
304
|
+
this._user.set(null);
|
|
305
|
+
this._isAuthenticated.set(false);
|
|
306
|
+
this._tokenTenantId.set(null);
|
|
307
|
+
this._allowedTenants.set([]);
|
|
308
|
+
this.oauthService.logOut();
|
|
309
|
+
}
|
|
117
310
|
});
|
|
118
311
|
this.oauthService.events.subscribe(async (e) => {
|
|
119
312
|
if (e.type === "token_received") {
|
|
313
|
+
this._loginInProgress = false;
|
|
120
314
|
await this.loadUserAsync();
|
|
121
315
|
}
|
|
122
316
|
});
|
|
@@ -146,9 +340,11 @@ class AuthorizeService {
|
|
|
146
340
|
// This enables immediate cross-tab logout detection
|
|
147
341
|
window.addEventListener('storage', (event) => {
|
|
148
342
|
console.debug("AuthorizeService: Storage event received", event.key, event.newValue);
|
|
149
|
-
// Check if access_token was removed (logout in another tab)
|
|
343
|
+
// Check if the current tenant's access_token was removed (logout in another tab)
|
|
344
|
+
// With per-tenant storage, the key is prefixed (e.g., "maco__access_token")
|
|
150
345
|
// Note: OAuth library may set to empty string or null when clearing
|
|
151
|
-
|
|
346
|
+
const expectedKey = this.tenantStorage.prefixKey('access_token');
|
|
347
|
+
if (event.key === expectedKey && (event.newValue === null || event.newValue === '') && this._isAuthenticated()) {
|
|
152
348
|
console.debug("AuthorizeService: Access token removed in another tab - logging out and reloading");
|
|
153
349
|
this._accessToken.set(null);
|
|
154
350
|
this._user.set(null);
|
|
@@ -191,6 +387,35 @@ class AuthorizeService {
|
|
|
191
387
|
getServiceUris() {
|
|
192
388
|
return this.authorizeOptions?.wellKnownServiceUris ?? null;
|
|
193
389
|
}
|
|
390
|
+
/**
|
|
391
|
+
* Sets the tenant ID for per-tenant token storage isolation.
|
|
392
|
+
* Must be called BEFORE initialize() to ensure tokens are stored/retrieved
|
|
393
|
+
* under the correct tenant prefix in localStorage.
|
|
394
|
+
*
|
|
395
|
+
* When set, all OAuth storage keys are prefixed with `{tenantId}__`
|
|
396
|
+
* (e.g., `maco__access_token`), preventing token collisions between tenants.
|
|
397
|
+
* The tenant ID is also persisted in sessionStorage so it survives OAuth redirects.
|
|
398
|
+
*
|
|
399
|
+
* @param tenantId The tenant ID to use for storage key prefixing, or null for unprefixed mode.
|
|
400
|
+
*/
|
|
401
|
+
setStorageTenantId(tenantId) {
|
|
402
|
+
this.tenantStorage.setTenantId(tenantId);
|
|
403
|
+
console.debug(`AuthorizeService::setStorageTenantId("${tenantId}")`);
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Restores the storage tenant ID from sessionStorage.
|
|
407
|
+
* Use this when the tenant ID cannot be determined from the URL
|
|
408
|
+
* (e.g., after an OAuth redirect to the root path).
|
|
409
|
+
*
|
|
410
|
+
* @returns The restored tenant ID, or null if none was persisted.
|
|
411
|
+
*/
|
|
412
|
+
restoreStorageTenantId() {
|
|
413
|
+
const tenantId = this.tenantStorage.restoreTenantId();
|
|
414
|
+
if (tenantId) {
|
|
415
|
+
console.debug(`AuthorizeService::restoreStorageTenantId("${tenantId}")`);
|
|
416
|
+
}
|
|
417
|
+
return tenantId;
|
|
418
|
+
}
|
|
194
419
|
/**
|
|
195
420
|
* Gets the current access token synchronously.
|
|
196
421
|
* Use this for functional interceptors that need immediate access to the token.
|
|
@@ -213,10 +438,21 @@ class AuthorizeService {
|
|
|
213
438
|
}
|
|
214
439
|
/**
|
|
215
440
|
* Initiates the login flow.
|
|
441
|
+
* Multiple guards (canActivateChild, canMatch) may call this concurrently
|
|
442
|
+
* during route resolution. Only the first call proceeds — subsequent calls
|
|
443
|
+
* are skipped to prevent generating a new nonce that overwrites the first
|
|
444
|
+
* one, which would cause an "invalid_nonce_in_state" error after the
|
|
445
|
+
* identity server redirects back.
|
|
446
|
+
*
|
|
216
447
|
* @param tenantId Optional tenant ID. When provided, includes acr_values=tenant:{tenantId}
|
|
217
448
|
* so the identity server redirects to the correct tenant's login page.
|
|
218
449
|
*/
|
|
219
450
|
login(tenantId) {
|
|
451
|
+
if (this._loginInProgress) {
|
|
452
|
+
console.debug('AuthorizeService::login skipped (already in progress)');
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
this._loginInProgress = true;
|
|
220
456
|
const effectiveTenantId = tenantId ?? this.authorizeOptions?.defaultTenantId;
|
|
221
457
|
if (effectiveTenantId) {
|
|
222
458
|
this.oauthService.initImplicitFlow('', { acr_values: `tenant:${effectiveTenantId}` });
|
|
@@ -259,45 +495,54 @@ class AuthorizeService {
|
|
|
259
495
|
catch {
|
|
260
496
|
// sessionStorage may be unavailable
|
|
261
497
|
}
|
|
262
|
-
//
|
|
263
|
-
//
|
|
264
|
-
//
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
localStorage.
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
localStorage.removeItem('expires_at');
|
|
273
|
-
localStorage.removeItem('nonce');
|
|
274
|
-
localStorage.removeItem('PKCE_verifier');
|
|
275
|
-
localStorage.removeItem('session_state');
|
|
276
|
-
localStorage.removeItem('granted_scopes');
|
|
277
|
-
localStorage.removeItem('requested_route');
|
|
498
|
+
// Stop automatic refresh and session checks to prevent the OAuth library
|
|
499
|
+
// from firing additional authorize requests during the page reload window.
|
|
500
|
+
// This prevents race conditions where a session check or silent refresh
|
|
501
|
+
// could overwrite the nonce/PKCE verifier of the new tenant's auth flow.
|
|
502
|
+
this.oauthService.stopAutomaticRefresh();
|
|
503
|
+
// With per-tenant storage (TenantAwareOAuthStorage), we do NOT clear
|
|
504
|
+
// tokens from localStorage. Each tenant's tokens are stored under
|
|
505
|
+
// prefixed keys (e.g., "maco__access_token") and are isolated from
|
|
506
|
+
// each other. After page reload, the storage tenant will be set to
|
|
507
|
+
// the target tenant, and cached tokens (if valid) can be reused.
|
|
278
508
|
// Clear our signals
|
|
279
509
|
this._accessToken.set(null);
|
|
280
510
|
this._user.set(null);
|
|
281
511
|
this._isAuthenticated.set(false);
|
|
282
512
|
this._tokenTenantId.set(null);
|
|
283
513
|
// Full page navigation to the target URL — triggers fresh auth flow
|
|
284
|
-
|
|
514
|
+
this.navigateTo(targetUrl);
|
|
285
515
|
return true;
|
|
286
516
|
}
|
|
287
517
|
/**
|
|
288
|
-
* Returns the pending tenant switch target (if any)
|
|
289
|
-
*
|
|
518
|
+
* Returns the pending tenant switch target (if any) WITHOUT clearing it.
|
|
519
|
+
* The key persists across the OAuth redirect cycle (guard → IDS → callback → guard)
|
|
520
|
+
* and is only cleared after a successful token exchange in loadUserAsync().
|
|
521
|
+
*
|
|
522
|
+
* Previously this method removed the key immediately, but that caused a race condition:
|
|
523
|
+
* the guard consumed it before the IDS redirect, and after the callback redirect the
|
|
524
|
+
* key was gone — causing the guard to fall back to the route tenant (wrong tenant).
|
|
290
525
|
*/
|
|
291
526
|
consumePendingTenantSwitch() {
|
|
292
527
|
try {
|
|
293
|
-
|
|
294
|
-
sessionStorage.removeItem(AuthorizeService.TENANT_REAUTH_KEY);
|
|
295
|
-
return tenantId;
|
|
528
|
+
return sessionStorage.getItem(AuthorizeService.TENANT_REAUTH_KEY);
|
|
296
529
|
}
|
|
297
530
|
catch {
|
|
298
531
|
return null;
|
|
299
532
|
}
|
|
300
533
|
}
|
|
534
|
+
/**
|
|
535
|
+
* Clears the pending tenant switch key. Called after a successful token exchange
|
|
536
|
+
* when the token's tenant_id matches the target, completing the switch cycle.
|
|
537
|
+
*/
|
|
538
|
+
clearPendingTenantSwitch() {
|
|
539
|
+
try {
|
|
540
|
+
sessionStorage.removeItem(AuthorizeService.TENANT_REAUTH_KEY);
|
|
541
|
+
}
|
|
542
|
+
catch {
|
|
543
|
+
// sessionStorage may be unavailable
|
|
544
|
+
}
|
|
545
|
+
}
|
|
301
546
|
/**
|
|
302
547
|
* Returns the tenant for which a switch was already attempted (if any) and clears it.
|
|
303
548
|
* Used by the guard to prevent infinite redirect loops: if the Identity Server
|
|
@@ -329,6 +574,11 @@ class AuthorizeService {
|
|
|
329
574
|
const postLogoutRedirectUri = this.lastAuthConfig?.postLogoutRedirectUri;
|
|
330
575
|
// Clear local OAuth state (tokens, discovery doc, etc.)
|
|
331
576
|
this.oauthService.logOut(true); // true = noRedirectToLogoutUrl (we redirect manually)
|
|
577
|
+
// Clear OAuth tokens for ALL tenants, not just the current one.
|
|
578
|
+
// With per-tenant storage, oauthService.logOut() only clears the current
|
|
579
|
+
// tenant's prefixed keys. We need to ensure no stale tokens remain for
|
|
580
|
+
// any tenant after a full logout.
|
|
581
|
+
this.tenantStorage.clearAllTenants();
|
|
332
582
|
if (endSessionEndpoint) {
|
|
333
583
|
// Build the end_session URL with id_token_hint and post_logout_redirect_uri
|
|
334
584
|
const params = new URLSearchParams();
|
|
@@ -338,7 +588,7 @@ class AuthorizeService {
|
|
|
338
588
|
if (postLogoutRedirectUri) {
|
|
339
589
|
params.set('post_logout_redirect_uri', postLogoutRedirectUri);
|
|
340
590
|
}
|
|
341
|
-
|
|
591
|
+
this.navigateTo(`${endSessionEndpoint}?${params.toString()}`);
|
|
342
592
|
}
|
|
343
593
|
else {
|
|
344
594
|
// Fallback: no end_session_endpoint available, just reload
|
|
@@ -406,7 +656,7 @@ class AuthorizeService {
|
|
|
406
656
|
};
|
|
407
657
|
this.authorizeOptions = authorizeOptions;
|
|
408
658
|
this.lastAuthConfig = config;
|
|
409
|
-
this.oauthService.setStorage(
|
|
659
|
+
this.oauthService.setStorage(this.tenantStorage);
|
|
410
660
|
this.oauthService.configure(config);
|
|
411
661
|
console.debug("AuthorizeService::initialize::loadingDiscoveryDocumentAndTryLogin");
|
|
412
662
|
await this.oauthService.loadDiscoveryDocumentAndTryLogin();
|
|
@@ -481,6 +731,10 @@ class AuthorizeService {
|
|
|
481
731
|
// Parse tenant_id from the access token (used for tenant mismatch detection)
|
|
482
732
|
const tokenTenantId = this.parseTenantIdFromToken(accessToken);
|
|
483
733
|
this._tokenTenantId.set(tokenTenantId);
|
|
734
|
+
// Clear the pending tenant switch key now that we have a valid token.
|
|
735
|
+
// This completes the switch cycle and prevents the guard from re-using
|
|
736
|
+
// the pending tenant on subsequent route activations.
|
|
737
|
+
this.clearPendingTenantSwitch();
|
|
484
738
|
console.debug(`AuthorizeService::loadUserAsync::done (tokenTenantId="${tokenTenantId}", allowedTenants=${JSON.stringify(this._allowedTenants())})`);
|
|
485
739
|
}
|
|
486
740
|
/**
|
|
@@ -568,6 +822,14 @@ class AuthorizeService {
|
|
|
568
822
|
reloadPage() {
|
|
569
823
|
window.location.reload();
|
|
570
824
|
}
|
|
825
|
+
/**
|
|
826
|
+
* Navigates to the given URL via full page navigation.
|
|
827
|
+
* This method is protected to allow mocking in tests.
|
|
828
|
+
* @internal
|
|
829
|
+
*/
|
|
830
|
+
navigateTo(url) {
|
|
831
|
+
window.location.href = url;
|
|
832
|
+
}
|
|
571
833
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: AuthorizeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
572
834
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: AuthorizeService });
|
|
573
835
|
}
|
|
@@ -836,5 +1098,5 @@ function provideMmSharedAuth() {
|
|
|
836
1098
|
* Generated bundle index. Do not edit.
|
|
837
1099
|
*/
|
|
838
1100
|
|
|
839
|
-
export { AuthorizeOptions, AuthorizeService, Roles, authorizeChildGuard, authorizeDeactivateGuard, authorizeGuard, authorizeInterceptor, authorizeMatchGuard, provideMmSharedAuth };
|
|
1101
|
+
export { AuthorizeOptions, AuthorizeService, Roles, TenantAwareOAuthStorage, authorizeChildGuard, authorizeDeactivateGuard, authorizeGuard, authorizeInterceptor, authorizeMatchGuard, provideMmSharedAuth };
|
|
840
1102
|
//# sourceMappingURL=meshmakers-shared-auth.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"meshmakers-shared-auth.mjs","sources":["../../../../projects/meshmakers/shared-auth/src/lib/authorize.service.ts","../../../../projects/meshmakers/shared-auth/src/lib/roles.ts","../../../../projects/meshmakers/shared-auth/src/lib/authorize.interceptor.ts","../../../../projects/meshmakers/shared-auth/src/lib/authorize.guard.ts","../../../../projects/meshmakers/shared-auth/src/public-api.ts","../../../../projects/meshmakers/shared-auth/src/meshmakers-shared-auth.ts"],"sourcesContent":["import { Injectable, Signal, WritableSignal, computed, inject, signal } from \"@angular/core\";\nimport { AuthConfig, OAuthService } from \"angular-oauth2-oidc\";\nimport { Roles } from \"./roles\";\n\nexport interface IUser {\n family_name: string | null;\n given_name: string | null;\n name: string;\n role: string[] | null;\n sub: string;\n idp: string;\n email: string | null;\n}\n\nexport class AuthorizeOptions {\n wellKnownServiceUris?: string[];\n // Url of the Identity Provider\n issuer?: string;\n // URL of the SPA to redirect the user to after login\n redirectUri?: string;\n postLogoutRedirectUri?: string;\n // The SPA's id. The SPA is registered with this id at the auth-server\n clientId?: string;\n // set the scope for the permissions the client should request\n // The first three are defined by OIDC. The 4th is a use case-specific one\n scope?: string;\n showDebugInformation?: boolean;\n sessionChecksEnabled?: boolean;\n // Default tenant ID for single-tenant apps. When set, login() uses this tenant\n // if no tenantId is explicitly provided (sends acr_values=tenant:{defaultTenantId}).\n defaultTenantId?: string;\n}\n\n@Injectable()\nexport class AuthorizeService {\n private readonly oauthService = inject(OAuthService);\n\n // =============================================================================\n // INTERNAL STATE (Writable Signals)\n // =============================================================================\n\n private readonly _isAuthenticated: WritableSignal<boolean> = signal(false);\n private readonly _issuer: WritableSignal<string | null> = signal(null);\n private readonly _accessToken: WritableSignal<string | null> = signal(null);\n private readonly _user: WritableSignal<IUser | null> = signal(null);\n private readonly _userInitials: WritableSignal<string | null> = signal(null);\n private readonly _isInitialized: WritableSignal<boolean> = signal(false);\n private readonly _isInitializing: WritableSignal<boolean> = signal(false);\n private readonly _sessionLoading: WritableSignal<boolean> = signal(false);\n private readonly _allowedTenants: WritableSignal<string[]> = signal([]);\n private readonly _tokenTenantId: WritableSignal<string | null> = signal(null);\n\n private static readonly TENANT_REAUTH_KEY = 'octo_tenant_reauth';\n private static readonly TENANT_SWITCH_ATTEMPTED_KEY = 'octo_tenant_switch_attempted';\n\n private authorizeOptions: AuthorizeOptions | null = null;\n private lastAuthConfig: AuthConfig | null = null;\n\n // =============================================================================\n // PUBLIC API (Readonly Signals) - NEW API\n // =============================================================================\n\n /**\n * Signal indicating whether the user is currently authenticated.\n */\n readonly isAuthenticated: Signal<boolean> = this._isAuthenticated.asReadonly();\n\n /**\n * Signal containing the issuer URL.\n */\n readonly issuer: Signal<string | null> = this._issuer.asReadonly();\n\n /**\n * Signal containing the current access token.\n */\n readonly accessToken: Signal<string | null> = this._accessToken.asReadonly();\n\n /**\n * Signal containing the current user information.\n */\n readonly user: Signal<IUser | null> = this._user.asReadonly();\n\n /**\n * Computed signal containing the user's initials (e.g., \"JD\" for John Doe).\n */\n readonly userInitials: Signal<string | null> = this._userInitials.asReadonly();\n\n /**\n * Signal indicating whether the session is currently loading.\n */\n readonly sessionLoading: Signal<boolean> = this._sessionLoading.asReadonly();\n\n /**\n * Signal containing the list of tenants the user is allowed to access.\n * Parsed from the allowed_tenants claims in the access token.\n */\n readonly allowedTenants: Signal<string[]> = this._allowedTenants.asReadonly();\n\n /**\n * Signal containing the tenant_id claim from the current access token.\n * Used to detect tenant mismatch when navigating between tenants.\n */\n readonly tokenTenantId: Signal<string | null> = this._tokenTenantId.asReadonly();\n\n /**\n * Computed signal containing the user's roles.\n */\n readonly roles: Signal<string[]> = computed(() => this._user()?.role ?? []);\n\n /**\n * Computed signal for the user's display name.\n * Uses given_name + family_name if available, otherwise derives from the username.\n */\n readonly displayName: Signal<string | null> = computed(() => {\n const user = this._user();\n if (!user) return null;\n if (user.given_name && user.family_name) {\n return user.given_name + ' ' + user.family_name;\n }\n return this.deriveDisplayNameFromUsername(user.name);\n });\n\n constructor() {\n console.debug(\"AuthorizeService::created\");\n\n this.oauthService.discoveryDocumentLoaded$.subscribe((_) => {\n console.debug(\"discoveryDocumentLoaded$\");\n });\n\n this.oauthService.events.subscribe((e) => {\n console.debug(\"oauth/oidc event\", e);\n });\n\n this.oauthService.events\n .pipe((source) => source)\n .subscribe((e) => {\n if (e.type === \"session_terminated\") {\n console.debug(\"Your session has been terminated!\");\n this._accessToken.set(null);\n this._user.set(null);\n this._isAuthenticated.set(false);\n this._tokenTenantId.set(null);\n // Reload the page to trigger the auth flow and redirect to login\n this.reloadPage();\n }\n });\n\n this.oauthService.events.subscribe(async (e) => {\n if (e.type === \"token_received\") {\n await this.loadUserAsync();\n }\n });\n\n this.oauthService.events.subscribe(async (e) => {\n if (e.type === \"session_unchanged\") {\n if (this._user() == null) {\n await this.loadUserAsync();\n }\n }\n });\n\n this.oauthService.events.subscribe((e) => {\n if (e.type === \"logout\") {\n console.debug(\"AuthorizeService: Logout event received\");\n this._accessToken.set(null);\n this._user.set(null);\n this._isAuthenticated.set(false);\n this._tokenTenantId.set(null);\n this._allowedTenants.set([]);\n // Do NOT call reloadPage() here — oauthService.logOut() already\n // redirects to the Identity Server's end_session_endpoint.\n // Calling reload() would race with that redirect and cause the\n // page to reload before the server-side session is terminated,\n // leaving the user still logged in.\n }\n });\n\n // Listen for storage events from other tabs (e.g., SLO logout callback)\n // This enables immediate cross-tab logout detection\n window.addEventListener('storage', (event) => {\n console.debug(\"AuthorizeService: Storage event received\", event.key, event.newValue);\n // Check if access_token was removed (logout in another tab)\n // Note: OAuth library may set to empty string or null when clearing\n if (event.key === 'access_token' && (event.newValue === null || event.newValue === '') && this._isAuthenticated()) {\n console.debug(\"AuthorizeService: Access token removed in another tab - logging out and reloading\");\n this._accessToken.set(null);\n this._user.set(null);\n this._isAuthenticated.set(false);\n this._tokenTenantId.set(null);\n // Reload the page to trigger the auth flow and redirect to login\n this.reloadPage();\n }\n });\n\n // Also listen for BroadcastChannel messages for cross-tab logout\n // This is more reliable than storage events for iframe-based SLO\n if (typeof BroadcastChannel !== 'undefined') {\n console.debug(\"AuthorizeService: Setting up BroadcastChannel listener for 'octo-auth-logout'\");\n const logoutChannel = new BroadcastChannel('octo-auth-logout');\n logoutChannel.onmessage = (event) => {\n console.debug(\"AuthorizeService: BroadcastChannel message received\", event.data);\n if (event.data?.type === 'logout' && this._isAuthenticated()) {\n console.debug(\"AuthorizeService: Logout broadcast received - reloading\");\n this._accessToken.set(null);\n this._user.set(null);\n this._isAuthenticated.set(false);\n this._tokenTenantId.set(null);\n this.reloadPage();\n }\n };\n } else {\n console.warn(\"AuthorizeService: BroadcastChannel not supported in this browser\");\n }\n }\n\n /**\n * Checks if the current user has the specified role.\n */\n public isInRole(role: Roles): boolean {\n return this._user()?.role?.includes(role) ?? false;\n }\n\n /**\n * Gets the configured service URIs that should receive the authorization token.\n */\n public getServiceUris(): string[] | null {\n return this.authorizeOptions?.wellKnownServiceUris ?? null;\n }\n\n /**\n * Gets the current access token synchronously.\n * Use this for functional interceptors that need immediate access to the token.\n *\n * @returns The current access token or null if not authenticated\n */\n public getAccessTokenSync(): string | null {\n return this._accessToken();\n }\n\n /**\n * Checks if the user is allowed to access the specified tenant.\n * Returns true if no allowed_tenants claims are present (backwards compatibility).\n */\n public isTenantAllowed(tenantId: string): boolean {\n const allowed = this._allowedTenants();\n if (allowed.length === 0) {\n return true; // No claims = backwards compatible (old tokens)\n }\n return allowed.some(t => t.toLowerCase() === tenantId.toLowerCase());\n }\n\n /**\n * Initiates the login flow.\n * @param tenantId Optional tenant ID. When provided, includes acr_values=tenant:{tenantId}\n * so the identity server redirects to the correct tenant's login page.\n */\n public login(tenantId?: string): void {\n const effectiveTenantId = tenantId ?? this.authorizeOptions?.defaultTenantId;\n if (effectiveTenantId) {\n this.oauthService.initImplicitFlow('', { acr_values: `tenant:${effectiveTenantId}` });\n } else {\n this.oauthService.initImplicitFlow();\n }\n }\n\n /**\n * Forces re-authentication for a different tenant by clearing the local\n * OAuth session and reloading the page. On reload, the guard will see\n * isAuthenticated=false and call login(tenantId) with the correct acr_values.\n *\n * This is used when the current token's tenant_id does not match the\n * route's tenantId (e.g., navigating from octosystem to meshtest).\n */\n /**\n * Returns true if the switch was initiated, false if skipped (loop prevention).\n */\n public switchTenant(targetTenantId: string, targetUrl: string): boolean {\n console.debug(`AuthorizeService::switchTenant to \"${targetTenantId}\" at \"${targetUrl}\"`);\n\n // Prevent infinite redirect loop: if we already attempted a switch to this\n // exact tenant and ended up here again, the Identity Server did not issue\n // a token for the target tenant. Do NOT attempt again.\n try {\n const previousAttempt = sessionStorage.getItem(AuthorizeService.TENANT_SWITCH_ATTEMPTED_KEY);\n if (previousAttempt && previousAttempt.toLowerCase() === targetTenantId.toLowerCase()) {\n console.warn(`AuthorizeService::switchTenant — already attempted switch to \"${targetTenantId}\", skipping to prevent loop`);\n return false;\n }\n } catch {\n // sessionStorage may be unavailable\n }\n\n // Store target tenant so login() uses the correct acr_values after reload\n try {\n sessionStorage.setItem(AuthorizeService.TENANT_REAUTH_KEY, targetTenantId);\n sessionStorage.setItem(AuthorizeService.TENANT_SWITCH_ATTEMPTED_KEY, targetTenantId);\n } catch {\n // sessionStorage may be unavailable\n }\n\n // Clear local OAuth tokens directly from storage.\n // Do NOT use oauthService.logOut() — it fires a 'logout' event\n // whose handler calls reloadPage(), racing with our navigation below.\n localStorage.removeItem('access_token');\n localStorage.removeItem('id_token');\n localStorage.removeItem('refresh_token');\n localStorage.removeItem('id_token_claims_obj');\n localStorage.removeItem('id_token_expires_at');\n localStorage.removeItem('id_token_stored_at');\n localStorage.removeItem('access_token_stored_at');\n localStorage.removeItem('expires_at');\n localStorage.removeItem('nonce');\n localStorage.removeItem('PKCE_verifier');\n localStorage.removeItem('session_state');\n localStorage.removeItem('granted_scopes');\n localStorage.removeItem('requested_route');\n\n // Clear our signals\n this._accessToken.set(null);\n this._user.set(null);\n this._isAuthenticated.set(false);\n this._tokenTenantId.set(null);\n\n // Full page navigation to the target URL — triggers fresh auth flow\n window.location.href = targetUrl;\n return true;\n }\n\n /**\n * Returns the pending tenant switch target (if any) and clears it.\n * Called by the guard to pass the correct tenantId to login() after a switchTenant reload.\n */\n public consumePendingTenantSwitch(): string | null {\n try {\n const tenantId = sessionStorage.getItem(AuthorizeService.TENANT_REAUTH_KEY);\n sessionStorage.removeItem(AuthorizeService.TENANT_REAUTH_KEY);\n return tenantId;\n } catch {\n return null;\n }\n }\n\n /**\n * Returns the tenant for which a switch was already attempted (if any) and clears it.\n * Used by the guard to prevent infinite redirect loops: if the Identity Server\n * issues a token for the wrong tenant even after a switch, we skip the second attempt.\n */\n public consumeSwitchAttempted(): string | null {\n try {\n const tenantId = sessionStorage.getItem(AuthorizeService.TENANT_SWITCH_ATTEMPTED_KEY);\n sessionStorage.removeItem(AuthorizeService.TENANT_SWITCH_ATTEMPTED_KEY);\n return tenantId;\n } catch {\n return null;\n }\n }\n\n /**\n * Logs out the current user by redirecting to the Identity Server's\n * OIDC end_session_endpoint for proper Single Logout (SLO).\n *\n * We cannot rely on oauthService.logOut() for the redirect because it calls\n * clearStorage() which may clear internal state before the redirect happens.\n * Instead, we capture the logoutUrl and id_token first, then clear state,\n * then manually redirect to the end_session_endpoint.\n */\n public logout(): void {\n // Read the end_session_endpoint (stored as logoutUrl on the service) and id_token BEFORE clearing storage\n const endSessionEndpoint = this.oauthService.logoutUrl;\n const idToken = this.oauthService.getIdToken();\n const postLogoutRedirectUri = this.lastAuthConfig?.postLogoutRedirectUri;\n\n // Clear local OAuth state (tokens, discovery doc, etc.)\n this.oauthService.logOut(true); // true = noRedirectToLogoutUrl (we redirect manually)\n\n if (endSessionEndpoint) {\n // Build the end_session URL with id_token_hint and post_logout_redirect_uri\n const params = new URLSearchParams();\n if (idToken) {\n params.set('id_token_hint', idToken);\n }\n if (postLogoutRedirectUri) {\n params.set('post_logout_redirect_uri', postLogoutRedirectUri);\n }\n window.location.href = `${endSessionEndpoint}?${params.toString()}`;\n } else {\n // Fallback: no end_session_endpoint available, just reload\n this.reloadPage();\n }\n }\n\n /**\n * Updates the redirect URIs without performing a full re-initialization.\n * Use this when the OAuth session is already established and only the\n * redirect targets need to change (e.g., when switching tenants).\n */\n public updateRedirectUris(redirectUri: string, postLogoutRedirectUri: string): void {\n if (this.authorizeOptions) {\n this.authorizeOptions.redirectUri = redirectUri;\n this.authorizeOptions.postLogoutRedirectUri = postLogoutRedirectUri;\n }\n\n if (this.lastAuthConfig) {\n this.lastAuthConfig.redirectUri = redirectUri;\n this.lastAuthConfig.postLogoutRedirectUri = postLogoutRedirectUri;\n }\n\n // Update the redirect URIs directly on the OAuthService without calling\n // configure(), because configure() does Object.assign(this, new AuthConfig(), config)\n // which resets ALL properties — including logoutUrl, tokenEndpoint, and other\n // discovery document endpoints — back to their AuthConfig defaults (empty).\n this.oauthService.redirectUri = redirectUri;\n this.oauthService.postLogoutRedirectUri = postLogoutRedirectUri;\n\n console.debug(\"AuthorizeService::updateRedirectUris::done\");\n }\n\n /**\n * Refreshes the access token and updates the allowed tenants signal.\n * Call this after actions that change the user's tenant access (e.g., provisioning).\n */\n public async refreshAccessToken(): Promise<void> {\n console.debug(\"AuthorizeService::refreshAccessToken::started\");\n await this.oauthService.refreshToken();\n await this.loadUserAsync();\n console.debug(\"AuthorizeService::refreshAccessToken::done\");\n }\n\n /**\n * Initializes the authorization service with the specified options.\n */\n public async initialize(authorizeOptions: AuthorizeOptions): Promise<void> {\n console.debug(\"AuthorizeService::initialize::started\");\n\n await this.uninitialize();\n\n if (this._isInitializing()) {\n return;\n }\n if (this._isInitialized()) {\n console.debug(\"AuthorizeService::initialize::alreadyInitialized\");\n return;\n }\n this._isInitializing.set(true);\n\n try {\n const config: AuthConfig = {\n responseType: \"code\",\n issuer: authorizeOptions.issuer,\n redirectUri: authorizeOptions.redirectUri,\n postLogoutRedirectUri: authorizeOptions.postLogoutRedirectUri,\n clientId: authorizeOptions.clientId,\n scope: authorizeOptions.scope,\n showDebugInformation: authorizeOptions.showDebugInformation,\n sessionChecksEnabled: authorizeOptions.sessionChecksEnabled,\n sessionCheckIntervall: 60 * 1000,\n preserveRequestedRoute: true\n };\n\n this.authorizeOptions = authorizeOptions;\n this.lastAuthConfig = config;\n\n this.oauthService.setStorage(localStorage);\n this.oauthService.configure(config);\n console.debug(\"AuthorizeService::initialize::loadingDiscoveryDocumentAndTryLogin\");\n await this.oauthService.loadDiscoveryDocumentAndTryLogin();\n\n console.debug(\"AuthorizeService::initialize::setupAutomaticSilentRefresh\");\n this.oauthService.setupAutomaticSilentRefresh();\n\n this._issuer.set(authorizeOptions.issuer ?? null);\n\n if (this.oauthService.hasValidIdToken()) {\n // if the idToken is still valid, we can use the session\n console.debug(\"AuthorizeService::initialize::hasValidIdToken\");\n this._sessionLoading.set(true);\n await this.oauthService.refreshToken();\n }\n\n this._isInitialized.set(true);\n console.debug(\"AuthorizeService::initialize::done\");\n } finally {\n this._isInitializing.set(false);\n }\n\n console.debug(\"AuthorizeService::initialize::completed\");\n }\n\n /**\n * Uninitializes the authorization service.\n */\n public async uninitialize(): Promise<void> {\n console.debug(\"AuthorizeService::uninitialize::started\");\n\n if (this._isInitializing()) {\n return;\n }\n if (!this._isInitialized()) {\n console.debug(\"AuthorizeService::uninitialize::alreadyUninitialized\");\n return;\n }\n\n try {\n this._isInitializing.set(true);\n\n this.oauthService.stopAutomaticRefresh();\n\n this.authorizeOptions = null;\n this.lastAuthConfig = null;\n\n // Note: Do NOT clear auth signals (_accessToken, _isAuthenticated, etc.) here.\n // The access token and user info are globally valid (not per-tenant) and remain\n // valid during re-initialization. Clearing them creates a window where the HTTP\n // interceptor sends requests without a Bearer token, causing 401 errors.\n // Signals are already properly cleared on logout/session_terminated events.\n\n this._isInitialized.set(false);\n console.debug(\"AuthorizeService::uninitialize::done\");\n } finally {\n this._isInitializing.set(false);\n }\n\n console.debug(\"AuthorizeService::uninitialize::completed\");\n }\n\n private async loadUserAsync(): Promise<void> {\n const claims = this.oauthService.getIdentityClaims();\n if (!claims) {\n console.error(\"claims where null when loading identity claims\");\n return;\n }\n\n const user = claims as IUser;\n if (user.given_name && user.family_name) {\n this._userInitials.set(user.given_name.charAt(0).toUpperCase() + user.family_name.charAt(0).toUpperCase());\n } else {\n const derived = this.deriveDisplayNameFromUsername(user.name);\n this._userInitials.set(this.deriveInitials(derived));\n }\n\n const accessToken = this.oauthService.getAccessToken();\n this._user.set(user);\n this._accessToken.set(accessToken);\n this._isAuthenticated.set(true);\n this._sessionLoading.set(false);\n\n // Parse allowed_tenants from the access token\n this._allowedTenants.set(this.parseAllowedTenantsFromToken(accessToken));\n\n // Parse tenant_id from the access token (used for tenant mismatch detection)\n const tokenTenantId = this.parseTenantIdFromToken(accessToken);\n this._tokenTenantId.set(tokenTenantId);\n\n console.debug(`AuthorizeService::loadUserAsync::done (tokenTenantId=\"${tokenTenantId}\", allowedTenants=${JSON.stringify(this._allowedTenants())})`);\n }\n\n /**\n * Decodes the JWT access token payload and extracts allowed_tenants claims.\n * The claim can be a single string or an array of strings.\n */\n private parseAllowedTenantsFromToken(accessToken: string | null): string[] {\n if (!accessToken) {\n return [];\n }\n\n try {\n const parts = accessToken.split('.');\n if (parts.length !== 3) {\n return [];\n }\n\n const base64 = parts[1].replace(/-/g, '+').replace(/_/g, '/');\n const payload = JSON.parse(atob(base64));\n const allowedTenants = payload['allowed_tenants'];\n\n if (!allowedTenants) {\n return [];\n }\n\n if (Array.isArray(allowedTenants)) {\n return allowedTenants;\n }\n\n // Single value claim\n if (typeof allowedTenants === 'string') {\n return [allowedTenants];\n }\n\n return [];\n } catch (e) {\n console.warn('Failed to parse allowed_tenants from access token', e);\n return [];\n }\n }\n\n /**\n * Decodes the JWT access token payload and extracts the tenant_id claim.\n */\n private parseTenantIdFromToken(accessToken: string | null): string | null {\n if (!accessToken) {\n return null;\n }\n\n try {\n const parts = accessToken.split('.');\n if (parts.length !== 3) {\n return null;\n }\n\n const payload = JSON.parse(atob(parts[1]));\n return payload['tenant_id'] ?? null;\n } catch {\n return null;\n }\n }\n\n private deriveDisplayNameFromUsername(username: string): string {\n let name = username;\n // Strip xt_{tenantId}_ prefix\n const xtMatch = name.match(/^xt_[^_]+_(.+)$/);\n if (xtMatch) { name = xtMatch[1]; }\n // Extract local part of email\n const atIndex = name.indexOf('@');\n if (atIndex > 0) { name = name.substring(0, atIndex); }\n // Split by dots and capitalize\n const parts = name.split('.').filter(p => p.length > 0);\n return parts.map(p => p.charAt(0).toUpperCase() + p.slice(1)).join(' ');\n }\n\n private deriveInitials(displayName: string): string {\n const words = displayName.split(' ').filter(w => w.length > 0);\n if (words.length >= 2) {\n return words[0].charAt(0).toUpperCase() + words[1].charAt(0).toUpperCase();\n }\n if (words.length === 1 && words[0].length >= 2) {\n return words[0].charAt(0).toUpperCase() + words[0].charAt(1).toLowerCase();\n }\n return '??';\n }\n\n /**\n * Reloads the page. This method is protected to allow mocking in tests.\n * @internal\n */\n protected reloadPage(): void {\n window.location.reload();\n }\n}\n","export enum Roles {\n ReportingManagement = 'ReportingManagement',\n ReportingViewer = 'ReportingViewer',\n AdminPanelManagement = 'AdminPanelManagement',\n BotManagement = 'BotManagement',\n UserManagement = 'UserManagement',\n CommunicationManagement = 'CommunicationManagement',\n TenantManagement = 'TenantManagement',\n Development = 'Development'\n}\n","import { inject } from '@angular/core';\nimport { HttpHandlerFn, HttpInterceptorFn, HttpRequest } from '@angular/common/http';\nimport { AuthorizeService } from './authorize.service';\n\n// =============================================================================\n// URL MATCHING UTILITIES\n// =============================================================================\n\n/**\n * Checks if the request URL is from the same origin as the application.\n */\nfunction isSameOriginUrl(req: HttpRequest<unknown>): boolean {\n // It's an absolute url with the same origin.\n if (req.url.startsWith(`${window.location.origin}/`)) {\n return true;\n }\n\n // It's a protocol relative url with the same origin.\n // For example: //www.example.com/api/Products\n if (req.url.startsWith(`//${window.location.host}/`)) {\n return true;\n }\n\n // It's a relative url like /api/Products\n if (/^\\/[^/].*/.test(req.url)) {\n return true;\n }\n\n // It's an absolute or protocol relative url that doesn't have the same origin.\n return false;\n}\n\n/**\n * Checks if the request URL matches any of the known service URIs.\n */\nfunction isKnownServiceUri(req: HttpRequest<unknown>, serviceUris: string[] | null): boolean {\n if (serviceUris != null) {\n for (const serviceUri of serviceUris) {\n if (req.url.startsWith(serviceUri)) {\n return true;\n }\n }\n }\n return false;\n}\n\n// =============================================================================\n// FUNCTIONAL INTERCEPTOR (RECOMMENDED)\n// =============================================================================\n\n/**\n * Functional HTTP interceptor that adds Bearer token to authorized requests.\n *\n * Adds the Authorization header to requests that are either:\n * - Same-origin requests (relative URLs or same host)\n * - Requests to known service URIs configured in AuthorizeOptions\n *\n * @example\n * ```typescript\n * // app.config.ts\n * import { provideHttpClient, withInterceptors } from '@angular/common/http';\n * import { authorizeInterceptor } from '@meshmakers/shared-auth';\n *\n * export const appConfig: ApplicationConfig = {\n * providers: [\n * provideHttpClient(withInterceptors([authorizeInterceptor])),\n * provideMmSharedAuth(),\n * ]\n * };\n * ```\n */\nexport const authorizeInterceptor: HttpInterceptorFn = (req: HttpRequest<unknown>, next: HttpHandlerFn) => {\n const authorizeService = inject(AuthorizeService);\n const token = authorizeService.getAccessTokenSync();\n const serviceUris = authorizeService.getServiceUris();\n\n if (token && (isSameOriginUrl(req) || isKnownServiceUri(req, serviceUris))) {\n req = req.clone({\n setHeaders: {\n Authorization: `Bearer ${token}`\n }\n });\n }\n\n return next(req);\n};\n","import { inject } from '@angular/core';\nimport { ActivatedRouteSnapshot, CanActivateFn, CanMatchFn, Router, RouterStateSnapshot } from '@angular/router';\nimport { AuthorizeService } from './authorize.service';\n\n/**\n * Walks up the route tree to find the tenantId parameter.\n */\nfunction findRouteTenantId(route: ActivatedRouteSnapshot): string | undefined {\n let current: ActivatedRouteSnapshot | null = route;\n while (current) {\n if (current.params?.['tenantId']) {\n return current.params['tenantId'];\n }\n current = current.parent;\n }\n return undefined;\n}\n\n/**\n * Handles authorization check for route activation.\n * Redirects to login if not authenticated, or to home if user lacks required roles.\n * Forces re-authentication when the token's tenant_id does not match the route's tenantId.\n *\n * @param route - The activated route snapshot containing route data\n * @returns true if authorized, false otherwise\n *\n * @example\n * ```typescript\n * // Route without role requirements\n * { path: 'dashboard', component: DashboardComponent, canActivate: [authorizeGuard] }\n *\n * // Route with role requirements\n * { path: 'admin', component: AdminComponent, canActivate: [authorizeGuard], data: { roles: ['AdminPanelManagement'] } }\n * ```\n */\nexport const authorizeGuard: CanActivateFn = async (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {\n const authorizeService = inject(AuthorizeService);\n const router = inject(Router);\n\n // Use signal directly (synchronous)\n const isAuthenticated = authorizeService.isAuthenticated();\n\n if (!isAuthenticated) {\n // Check if this is a reload after switchTenant — use the stored tenantId\n const pendingTenant = authorizeService.consumePendingTenantSwitch();\n const tenantId = pendingTenant ?? findRouteTenantId(route);\n authorizeService.login(tenantId);\n return false;\n }\n\n // Force re-authentication if the token was issued for a different tenant.\n // switchTenant clears the local session and reloads, so the next guard\n // invocation enters the !isAuthenticated branch above with the correct tenantId.\n const tokenTenantId = authorizeService.tokenTenantId();\n const routeTenantId = findRouteTenantId(route);\n if (tokenTenantId && routeTenantId && routeTenantId.toLowerCase() !== tokenTenantId.toLowerCase()) {\n console.debug(`[AuthGuard] Tenant mismatch: token=\"${tokenTenantId}\", route=\"${routeTenantId}\" — attempting tenant switch`);\n // switchTenant returns false if a switch was already attempted (loop prevention).\n // Use state.url (the router's target URL) — not window.location.href which is\n // still the current page during in-app navigation (e.g., tenant switcher dropdown).\n const targetUrl = window.location.origin + state.url;\n if (authorizeService.switchTenant(routeTenantId, targetUrl)) {\n return false;\n }\n // Switch was skipped — fall through to role-based checks\n console.warn(`[AuthGuard] Tenant mismatch persists after switch attempt (token=\"${tokenTenantId}\", route=\"${routeTenantId}\"). Proceeding with current token.`);\n } else {\n // No mismatch — clear any leftover switch-attempted flag\n authorizeService.consumeSwitchAttempted();\n }\n\n // Use roles signal directly (synchronous)\n const userRoles = authorizeService.roles();\n const requiredRoles = route.data['roles'] as string[] | undefined;\n\n if (requiredRoles === undefined || requiredRoles === null) {\n if (typeof ngDevMode === 'undefined' || ngDevMode) {\n console.warn(`[AuthGuard] Route \"${route.routeConfig?.path}\" has no required roles defined — access granted to any authenticated user.`);\n }\n return true;\n }\n\n // Empty roles array (roles: []) means intentionally open to any authenticated user\n if (requiredRoles.length === 0) {\n return true;\n }\n\n if (!requiredRoles.some(role => userRoles.includes(role))) {\n // Navigate to the current tenant's home, not root ''.\n // Navigating to '' would redirect to a default tenant (e.g., octosystem),\n // causing a tenant mismatch → switchTenant → redirect loop.\n const tenantHome = routeTenantId ? `/${routeTenantId}` : '';\n await router.navigate([tenantHome]);\n return false;\n }\n\n return true;\n};\n\n/**\n * Guard for child routes. Delegates to authorizeGuard.\n *\n * @example\n * ```typescript\n * {\n * path: 'parent',\n * canActivateChild: [authorizeChildGuard],\n * children: [\n * { path: 'child', component: ChildComponent, data: { roles: ['RequiredRole'] } }\n * ]\n * }\n * ```\n */\nexport const authorizeChildGuard: CanActivateFn = authorizeGuard;\n\n/**\n * Guard for lazy-loaded routes. Checks if user is authenticated.\n * Replaces the deprecated canLoad guard.\n *\n * @example\n * ```typescript\n * {\n * path: 'lazy',\n * loadChildren: () => import('./lazy/lazy.routes'),\n * canMatch: [authorizeMatchGuard]\n * }\n * ```\n */\nexport const authorizeMatchGuard: CanMatchFn = (_route, segments) => {\n const authorizeService = inject(AuthorizeService);\n\n // Use signal directly (synchronous)\n const isAuthenticated = authorizeService.isAuthenticated();\n\n if (!isAuthenticated) {\n // The first URL segment is typically the tenantId (e.g., /:tenantId/...)\n const tenantId = segments.length > 0 ? segments[0].path : undefined;\n authorizeService.login(tenantId);\n return false;\n }\n\n return true;\n};\n\n/**\n * Guard that always allows deactivation.\n * Use this as a placeholder or override in specific routes.\n *\n * @example\n * ```typescript\n * { path: 'form', component: FormComponent, canDeactivate: [authorizeDeactivateGuard] }\n * ```\n */\nexport const authorizeDeactivateGuard = () => true;\n","/*\n * Public API Surface of shared-auth\n */\n\nimport { EnvironmentProviders, makeEnvironmentProviders } from '@angular/core';\nimport { AuthorizeService } from './lib/authorize.service';\nimport { provideOAuthClient } from 'angular-oauth2-oidc';\n\n// Core services\nexport * from './lib/authorize.service';\nexport * from './lib/roles';\n\n// Functional interceptor\nexport { authorizeInterceptor } from './lib/authorize.interceptor';\n\n// Functional guards\nexport {\n authorizeGuard,\n authorizeChildGuard,\n authorizeMatchGuard,\n authorizeDeactivateGuard\n} from './lib/authorize.guard';\n\n// UI Components (Kendo) - available via '@meshmakers/shared-auth/login-ui'\n// import { LoginAppBarSectionComponent } from '@meshmakers/shared-auth/login-ui';\n\n/**\n * Provides all shared-auth dependencies.\n *\n * @example\n * ```typescript\n * // app.config.ts\n * import { provideHttpClient, withInterceptors } from '@angular/common/http';\n * import { provideMmSharedAuth, authorizeInterceptor } from '@meshmakers/shared-auth';\n *\n * export const appConfig: ApplicationConfig = {\n * providers: [\n * provideHttpClient(withInterceptors([authorizeInterceptor])),\n * provideMmSharedAuth(),\n * // ... other providers\n * ]\n * };\n * ```\n *\n * @remarks\n * Functional guards and interceptors don't need to be provided - they use inject() internally.\n * For the functional interceptor, use `provideHttpClient(withInterceptors([authorizeInterceptor]))`.\n */\nexport function provideMmSharedAuth(): EnvironmentProviders {\n return makeEnvironmentProviders([\n provideOAuthClient(),\n AuthorizeService\n ]);\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"meshmakers-shared-auth.mjs","sources":["../../../../projects/meshmakers/shared-auth/src/lib/tenant-aware-oauth-storage.ts","../../../../projects/meshmakers/shared-auth/src/lib/authorize.service.ts","../../../../projects/meshmakers/shared-auth/src/lib/roles.ts","../../../../projects/meshmakers/shared-auth/src/lib/authorize.interceptor.ts","../../../../projects/meshmakers/shared-auth/src/lib/authorize.guard.ts","../../../../projects/meshmakers/shared-auth/src/public-api.ts","../../../../projects/meshmakers/shared-auth/src/meshmakers-shared-auth.ts"],"sourcesContent":["import { OAuthStorage } from 'angular-oauth2-oidc';\n\n/**\n * Known OAuth storage keys used by angular-oauth2-oidc.\n * Used by clearAllTenants() to identify and remove OAuth-related entries\n * across all tenants without affecting other localStorage data.\n */\nconst OAUTH_STORAGE_KEYS = [\n 'access_token',\n 'refresh_token',\n 'id_token',\n 'id_token_claims_obj',\n 'id_token_header',\n 'expires_at',\n 'access_token_stored_at',\n 'id_token_expires_at',\n 'id_token_stored_at',\n 'nonce',\n 'PKCE_verifier',\n 'session_state',\n 'granted_scopes',\n 'requested_route',\n];\n\n/**\n * Keys that are specific to a single OAuth authorization flow and must\n * be isolated per browser tab. These are stored in sessionStorage\n * (which is per-tab) to prevent cross-tab nonce/PKCE collisions.\n *\n * Without this, two tabs starting login simultaneously would overwrite\n * each other's nonce in shared localStorage, causing \"invalid_nonce_in_state\"\n * errors when the first tab's callback tries to validate.\n */\nconst SESSION_SCOPED_KEYS = new Set([\n 'nonce',\n 'PKCE_verifier',\n 'requested_route',\n]);\n\n/**\n * SessionStorage key used to persist the current storage tenant ID across\n * page reloads (e.g., OAuth redirects). The tenant ID must survive the\n * round-trip to the identity server because the redirect URI may not\n * contain the tenant path segment.\n */\nconst STORAGE_TENANT_KEY = 'octo_storage_tenant';\n\n/**\n * Tenant-aware OAuth storage that prefixes all keys with a tenant ID\n * and splits storage between localStorage and sessionStorage.\n *\n * **Key prefixing:** When a tenant ID is set, all storage keys are prefixed\n * with `{tenantId}__` (double underscore separator). This isolates OAuth\n * tokens per tenant, preventing race conditions during tenant switches.\n *\n * **Storage split:** Flow-specific ephemeral keys (nonce, PKCE_verifier)\n * are stored in sessionStorage (per browser tab), while tokens and session\n * data are stored in localStorage (shared across tabs). This prevents\n * cross-tab nonce collisions when multiple tabs initiate login simultaneously.\n *\n * When no tenant ID is set (null), keys are stored without a prefix,\n * maintaining backwards compatibility with existing single-tenant apps.\n *\n * @example\n * ```typescript\n * const storage = new TenantAwareOAuthStorage();\n * storage.setTenantId('maco');\n * storage.setItem('access_token', 'abc');\n * // Stored in: localStorage['maco__access_token'] = 'abc'\n *\n * storage.setItem('nonce', 'xyz');\n * // Stored in: sessionStorage['maco__nonce'] = 'xyz' (per-tab!)\n * ```\n */\nexport class TenantAwareOAuthStorage extends OAuthStorage {\n private tenantId: string | null = null;\n\n /**\n * Sets the tenant ID used to prefix storage keys.\n * Must be called before the OAuthService reads/writes tokens.\n * The tenant ID is persisted in sessionStorage so it survives OAuth redirects.\n *\n * @param tenantId The tenant ID, or null for unprefixed (backwards-compatible) mode.\n */\n setTenantId(tenantId: string | null): void {\n this.tenantId = tenantId;\n try {\n if (tenantId) {\n sessionStorage.setItem(STORAGE_TENANT_KEY, tenantId);\n } else {\n sessionStorage.removeItem(STORAGE_TENANT_KEY);\n }\n } catch {\n // sessionStorage may be unavailable\n }\n }\n\n /**\n * Returns the currently configured tenant ID.\n */\n getTenantId(): string | null {\n return this.tenantId;\n }\n\n /**\n * Restores the tenant ID from sessionStorage.\n * Call this on app startup when the tenant cannot be determined from the URL\n * (e.g., after an OAuth redirect to the root path).\n *\n * @returns The restored tenant ID, or null if none was persisted.\n */\n restoreTenantId(): string | null {\n try {\n const stored = sessionStorage.getItem(STORAGE_TENANT_KEY);\n if (stored) {\n this.tenantId = stored;\n return stored;\n }\n } catch {\n // sessionStorage may be unavailable\n }\n return null;\n }\n\n /**\n * Returns the prefixed key for the given base key.\n * When tenantId is null, returns the base key unchanged.\n */\n prefixKey(key: string): string {\n if (this.tenantId) {\n return `${this.tenantId}__${key}`;\n }\n return key;\n }\n\n getItem(key: string): string | null {\n const prefixed = this.prefixKey(key);\n if (SESSION_SCOPED_KEYS.has(key)) {\n return sessionStorage.getItem(prefixed);\n }\n return localStorage.getItem(prefixed);\n }\n\n removeItem(key: string): void {\n const prefixed = this.prefixKey(key);\n if (SESSION_SCOPED_KEYS.has(key)) {\n sessionStorage.removeItem(prefixed);\n } else {\n localStorage.removeItem(prefixed);\n }\n }\n\n setItem(key: string, data: string): void {\n const prefixed = this.prefixKey(key);\n if (SESSION_SCOPED_KEYS.has(key)) {\n sessionStorage.setItem(prefixed, data);\n } else {\n localStorage.setItem(prefixed, data);\n }\n }\n\n /**\n * Clears all OAuth-related keys for ALL tenants from both\n * localStorage and sessionStorage, while leaving non-OAuth data intact.\n *\n * Used during full logout to ensure no stale tokens remain for any tenant.\n */\n clearAllTenants(): void {\n this.clearOAuthKeysFromStorage(localStorage);\n this.clearOAuthKeysFromStorage(sessionStorage);\n }\n\n private clearOAuthKeysFromStorage(storage: Storage): void {\n const keysToRemove: string[] = [];\n for (let i = 0; i < storage.length; i++) {\n const storageKey = storage.key(i);\n if (!storageKey) continue;\n\n for (const oauthKey of OAUTH_STORAGE_KEYS) {\n if (storageKey === oauthKey || storageKey.endsWith('__' + oauthKey)) {\n keysToRemove.push(storageKey);\n break;\n }\n }\n }\n for (const key of keysToRemove) {\n storage.removeItem(key);\n }\n }\n}\n","import { Injectable, Signal, WritableSignal, computed, inject, signal } from \"@angular/core\";\nimport { AuthConfig, OAuthService } from \"angular-oauth2-oidc\";\nimport { Roles } from \"./roles\";\nimport { TenantAwareOAuthStorage } from \"./tenant-aware-oauth-storage\";\n\nexport interface IUser {\n family_name: string | null;\n given_name: string | null;\n name: string;\n role: string[] | null;\n sub: string;\n idp: string;\n email: string | null;\n}\n\nexport class AuthorizeOptions {\n wellKnownServiceUris?: string[];\n // Url of the Identity Provider\n issuer?: string;\n // URL of the SPA to redirect the user to after login\n redirectUri?: string;\n postLogoutRedirectUri?: string;\n // The SPA's id. The SPA is registered with this id at the auth-server\n clientId?: string;\n // set the scope for the permissions the client should request\n // The first three are defined by OIDC. The 4th is a use case-specific one\n scope?: string;\n showDebugInformation?: boolean;\n sessionChecksEnabled?: boolean;\n // Default tenant ID for single-tenant apps. When set, login() uses this tenant\n // if no tenantId is explicitly provided (sends acr_values=tenant:{defaultTenantId}).\n defaultTenantId?: string;\n}\n\n@Injectable()\nexport class AuthorizeService {\n private readonly oauthService = inject(OAuthService);\n\n // =============================================================================\n // INTERNAL STATE (Writable Signals)\n // =============================================================================\n\n private readonly _isAuthenticated: WritableSignal<boolean> = signal(false);\n private readonly _issuer: WritableSignal<string | null> = signal(null);\n private readonly _accessToken: WritableSignal<string | null> = signal(null);\n private readonly _user: WritableSignal<IUser | null> = signal(null);\n private readonly _userInitials: WritableSignal<string | null> = signal(null);\n private readonly _isInitialized: WritableSignal<boolean> = signal(false);\n private readonly _isInitializing: WritableSignal<boolean> = signal(false);\n private readonly _sessionLoading: WritableSignal<boolean> = signal(false);\n private readonly _allowedTenants: WritableSignal<string[]> = signal([]);\n private readonly _tokenTenantId: WritableSignal<string | null> = signal(null);\n\n private static readonly TENANT_REAUTH_KEY = 'octo_tenant_reauth';\n private static readonly TENANT_SWITCH_ATTEMPTED_KEY = 'octo_tenant_switch_attempted';\n\n private readonly tenantStorage = new TenantAwareOAuthStorage();\n private _loginInProgress = false;\n private authorizeOptions: AuthorizeOptions | null = null;\n private lastAuthConfig: AuthConfig | null = null;\n\n // =============================================================================\n // PUBLIC API (Readonly Signals) - NEW API\n // =============================================================================\n\n /**\n * Signal indicating whether the user is currently authenticated.\n */\n readonly isAuthenticated: Signal<boolean> = this._isAuthenticated.asReadonly();\n\n /**\n * Signal containing the issuer URL.\n */\n readonly issuer: Signal<string | null> = this._issuer.asReadonly();\n\n /**\n * Signal containing the current access token.\n */\n readonly accessToken: Signal<string | null> = this._accessToken.asReadonly();\n\n /**\n * Signal containing the current user information.\n */\n readonly user: Signal<IUser | null> = this._user.asReadonly();\n\n /**\n * Computed signal containing the user's initials (e.g., \"JD\" for John Doe).\n */\n readonly userInitials: Signal<string | null> = this._userInitials.asReadonly();\n\n /**\n * Signal indicating whether the session is currently loading.\n */\n readonly sessionLoading: Signal<boolean> = this._sessionLoading.asReadonly();\n\n /**\n * Signal containing the list of tenants the user is allowed to access.\n * Parsed from the allowed_tenants claims in the access token.\n */\n readonly allowedTenants: Signal<string[]> = this._allowedTenants.asReadonly();\n\n /**\n * Signal containing the tenant_id claim from the current access token.\n * Used to detect tenant mismatch when navigating between tenants.\n */\n readonly tokenTenantId: Signal<string | null> = this._tokenTenantId.asReadonly();\n\n /**\n * Computed signal containing the user's roles.\n */\n readonly roles: Signal<string[]> = computed(() => this._user()?.role ?? []);\n\n /**\n * Computed signal for the user's display name.\n * Uses given_name + family_name if available, otherwise derives from the username.\n */\n readonly displayName: Signal<string | null> = computed(() => {\n const user = this._user();\n if (!user) return null;\n if (user.given_name && user.family_name) {\n return user.given_name + ' ' + user.family_name;\n }\n return this.deriveDisplayNameFromUsername(user.name);\n });\n\n constructor() {\n console.debug(\"AuthorizeService::created\");\n\n this.oauthService.discoveryDocumentLoaded$.subscribe((_) => {\n console.debug(\"discoveryDocumentLoaded$\");\n });\n\n this.oauthService.events.subscribe((e) => {\n console.debug(\"oauth/oidc event\", e);\n });\n\n this.oauthService.events\n .pipe((source) => source)\n .subscribe((e) => {\n if (e.type === \"session_terminated\") {\n console.debug(\"Your session has been terminated!\");\n this._accessToken.set(null);\n this._user.set(null);\n this._isAuthenticated.set(false);\n this._tokenTenantId.set(null);\n // Reload the page to trigger the auth flow and redirect to login\n this.reloadPage();\n }\n\n if (e.type === \"token_refresh_error\") {\n console.warn(\"AuthorizeService: Token refresh failed — clearing session and redirecting to login\");\n this._accessToken.set(null);\n this._user.set(null);\n this._isAuthenticated.set(false);\n this._tokenTenantId.set(null);\n this._allowedTenants.set([]);\n this.oauthService.logOut();\n }\n });\n\n this.oauthService.events.subscribe(async (e) => {\n if (e.type === \"token_received\") {\n this._loginInProgress = false;\n await this.loadUserAsync();\n }\n });\n\n this.oauthService.events.subscribe(async (e) => {\n if (e.type === \"session_unchanged\") {\n if (this._user() == null) {\n await this.loadUserAsync();\n }\n }\n });\n\n this.oauthService.events.subscribe((e) => {\n if (e.type === \"logout\") {\n console.debug(\"AuthorizeService: Logout event received\");\n this._accessToken.set(null);\n this._user.set(null);\n this._isAuthenticated.set(false);\n this._tokenTenantId.set(null);\n this._allowedTenants.set([]);\n // Do NOT call reloadPage() here — oauthService.logOut() already\n // redirects to the Identity Server's end_session_endpoint.\n // Calling reload() would race with that redirect and cause the\n // page to reload before the server-side session is terminated,\n // leaving the user still logged in.\n }\n });\n\n // Listen for storage events from other tabs (e.g., SLO logout callback)\n // This enables immediate cross-tab logout detection\n window.addEventListener('storage', (event) => {\n console.debug(\"AuthorizeService: Storage event received\", event.key, event.newValue);\n // Check if the current tenant's access_token was removed (logout in another tab)\n // With per-tenant storage, the key is prefixed (e.g., \"maco__access_token\")\n // Note: OAuth library may set to empty string or null when clearing\n const expectedKey = this.tenantStorage.prefixKey('access_token');\n if (event.key === expectedKey && (event.newValue === null || event.newValue === '') && this._isAuthenticated()) {\n console.debug(\"AuthorizeService: Access token removed in another tab - logging out and reloading\");\n this._accessToken.set(null);\n this._user.set(null);\n this._isAuthenticated.set(false);\n this._tokenTenantId.set(null);\n // Reload the page to trigger the auth flow and redirect to login\n this.reloadPage();\n }\n });\n\n // Also listen for BroadcastChannel messages for cross-tab logout\n // This is more reliable than storage events for iframe-based SLO\n if (typeof BroadcastChannel !== 'undefined') {\n console.debug(\"AuthorizeService: Setting up BroadcastChannel listener for 'octo-auth-logout'\");\n const logoutChannel = new BroadcastChannel('octo-auth-logout');\n logoutChannel.onmessage = (event) => {\n console.debug(\"AuthorizeService: BroadcastChannel message received\", event.data);\n if (event.data?.type === 'logout' && this._isAuthenticated()) {\n console.debug(\"AuthorizeService: Logout broadcast received - reloading\");\n this._accessToken.set(null);\n this._user.set(null);\n this._isAuthenticated.set(false);\n this._tokenTenantId.set(null);\n this.reloadPage();\n }\n };\n } else {\n console.warn(\"AuthorizeService: BroadcastChannel not supported in this browser\");\n }\n }\n\n /**\n * Checks if the current user has the specified role.\n */\n public isInRole(role: Roles): boolean {\n return this._user()?.role?.includes(role) ?? false;\n }\n\n /**\n * Gets the configured service URIs that should receive the authorization token.\n */\n public getServiceUris(): string[] | null {\n return this.authorizeOptions?.wellKnownServiceUris ?? null;\n }\n\n /**\n * Sets the tenant ID for per-tenant token storage isolation.\n * Must be called BEFORE initialize() to ensure tokens are stored/retrieved\n * under the correct tenant prefix in localStorage.\n *\n * When set, all OAuth storage keys are prefixed with `{tenantId}__`\n * (e.g., `maco__access_token`), preventing token collisions between tenants.\n * The tenant ID is also persisted in sessionStorage so it survives OAuth redirects.\n *\n * @param tenantId The tenant ID to use for storage key prefixing, or null for unprefixed mode.\n */\n public setStorageTenantId(tenantId: string | null): void {\n this.tenantStorage.setTenantId(tenantId);\n console.debug(`AuthorizeService::setStorageTenantId(\"${tenantId}\")`);\n }\n\n /**\n * Restores the storage tenant ID from sessionStorage.\n * Use this when the tenant ID cannot be determined from the URL\n * (e.g., after an OAuth redirect to the root path).\n *\n * @returns The restored tenant ID, or null if none was persisted.\n */\n public restoreStorageTenantId(): string | null {\n const tenantId = this.tenantStorage.restoreTenantId();\n if (tenantId) {\n console.debug(`AuthorizeService::restoreStorageTenantId(\"${tenantId}\")`);\n }\n return tenantId;\n }\n\n /**\n * Gets the current access token synchronously.\n * Use this for functional interceptors that need immediate access to the token.\n *\n * @returns The current access token or null if not authenticated\n */\n public getAccessTokenSync(): string | null {\n return this._accessToken();\n }\n\n /**\n * Checks if the user is allowed to access the specified tenant.\n * Returns true if no allowed_tenants claims are present (backwards compatibility).\n */\n public isTenantAllowed(tenantId: string): boolean {\n const allowed = this._allowedTenants();\n if (allowed.length === 0) {\n return true; // No claims = backwards compatible (old tokens)\n }\n return allowed.some(t => t.toLowerCase() === tenantId.toLowerCase());\n }\n\n /**\n * Initiates the login flow.\n * Multiple guards (canActivateChild, canMatch) may call this concurrently\n * during route resolution. Only the first call proceeds — subsequent calls\n * are skipped to prevent generating a new nonce that overwrites the first\n * one, which would cause an \"invalid_nonce_in_state\" error after the\n * identity server redirects back.\n *\n * @param tenantId Optional tenant ID. When provided, includes acr_values=tenant:{tenantId}\n * so the identity server redirects to the correct tenant's login page.\n */\n public login(tenantId?: string): void {\n if (this._loginInProgress) {\n console.debug('AuthorizeService::login skipped (already in progress)');\n return;\n }\n this._loginInProgress = true;\n\n const effectiveTenantId = tenantId ?? this.authorizeOptions?.defaultTenantId;\n if (effectiveTenantId) {\n this.oauthService.initImplicitFlow('', { acr_values: `tenant:${effectiveTenantId}` });\n } else {\n this.oauthService.initImplicitFlow();\n }\n }\n\n /**\n * Forces re-authentication for a different tenant by clearing the local\n * OAuth session and reloading the page. On reload, the guard will see\n * isAuthenticated=false and call login(tenantId) with the correct acr_values.\n *\n * This is used when the current token's tenant_id does not match the\n * route's tenantId (e.g., navigating from octosystem to meshtest).\n */\n /**\n * Returns true if the switch was initiated, false if skipped (loop prevention).\n */\n public switchTenant(targetTenantId: string, targetUrl: string): boolean {\n console.debug(`AuthorizeService::switchTenant to \"${targetTenantId}\" at \"${targetUrl}\"`);\n\n // Prevent infinite redirect loop: if we already attempted a switch to this\n // exact tenant and ended up here again, the Identity Server did not issue\n // a token for the target tenant. Do NOT attempt again.\n try {\n const previousAttempt = sessionStorage.getItem(AuthorizeService.TENANT_SWITCH_ATTEMPTED_KEY);\n if (previousAttempt && previousAttempt.toLowerCase() === targetTenantId.toLowerCase()) {\n console.warn(`AuthorizeService::switchTenant — already attempted switch to \"${targetTenantId}\", skipping to prevent loop`);\n return false;\n }\n } catch {\n // sessionStorage may be unavailable\n }\n\n // Store target tenant so login() uses the correct acr_values after reload\n try {\n sessionStorage.setItem(AuthorizeService.TENANT_REAUTH_KEY, targetTenantId);\n sessionStorage.setItem(AuthorizeService.TENANT_SWITCH_ATTEMPTED_KEY, targetTenantId);\n } catch {\n // sessionStorage may be unavailable\n }\n\n // Stop automatic refresh and session checks to prevent the OAuth library\n // from firing additional authorize requests during the page reload window.\n // This prevents race conditions where a session check or silent refresh\n // could overwrite the nonce/PKCE verifier of the new tenant's auth flow.\n this.oauthService.stopAutomaticRefresh();\n\n // With per-tenant storage (TenantAwareOAuthStorage), we do NOT clear\n // tokens from localStorage. Each tenant's tokens are stored under\n // prefixed keys (e.g., \"maco__access_token\") and are isolated from\n // each other. After page reload, the storage tenant will be set to\n // the target tenant, and cached tokens (if valid) can be reused.\n\n // Clear our signals\n this._accessToken.set(null);\n this._user.set(null);\n this._isAuthenticated.set(false);\n this._tokenTenantId.set(null);\n\n // Full page navigation to the target URL — triggers fresh auth flow\n this.navigateTo(targetUrl);\n return true;\n }\n\n /**\n * Returns the pending tenant switch target (if any) WITHOUT clearing it.\n * The key persists across the OAuth redirect cycle (guard → IDS → callback → guard)\n * and is only cleared after a successful token exchange in loadUserAsync().\n *\n * Previously this method removed the key immediately, but that caused a race condition:\n * the guard consumed it before the IDS redirect, and after the callback redirect the\n * key was gone — causing the guard to fall back to the route tenant (wrong tenant).\n */\n public consumePendingTenantSwitch(): string | null {\n try {\n return sessionStorage.getItem(AuthorizeService.TENANT_REAUTH_KEY);\n } catch {\n return null;\n }\n }\n\n /**\n * Clears the pending tenant switch key. Called after a successful token exchange\n * when the token's tenant_id matches the target, completing the switch cycle.\n */\n public clearPendingTenantSwitch(): void {\n try {\n sessionStorage.removeItem(AuthorizeService.TENANT_REAUTH_KEY);\n } catch {\n // sessionStorage may be unavailable\n }\n }\n\n /**\n * Returns the tenant for which a switch was already attempted (if any) and clears it.\n * Used by the guard to prevent infinite redirect loops: if the Identity Server\n * issues a token for the wrong tenant even after a switch, we skip the second attempt.\n */\n public consumeSwitchAttempted(): string | null {\n try {\n const tenantId = sessionStorage.getItem(AuthorizeService.TENANT_SWITCH_ATTEMPTED_KEY);\n sessionStorage.removeItem(AuthorizeService.TENANT_SWITCH_ATTEMPTED_KEY);\n return tenantId;\n } catch {\n return null;\n }\n }\n\n /**\n * Logs out the current user by redirecting to the Identity Server's\n * OIDC end_session_endpoint for proper Single Logout (SLO).\n *\n * We cannot rely on oauthService.logOut() for the redirect because it calls\n * clearStorage() which may clear internal state before the redirect happens.\n * Instead, we capture the logoutUrl and id_token first, then clear state,\n * then manually redirect to the end_session_endpoint.\n */\n public logout(): void {\n // Read the end_session_endpoint (stored as logoutUrl on the service) and id_token BEFORE clearing storage\n const endSessionEndpoint = this.oauthService.logoutUrl;\n const idToken = this.oauthService.getIdToken();\n const postLogoutRedirectUri = this.lastAuthConfig?.postLogoutRedirectUri;\n\n // Clear local OAuth state (tokens, discovery doc, etc.)\n this.oauthService.logOut(true); // true = noRedirectToLogoutUrl (we redirect manually)\n\n // Clear OAuth tokens for ALL tenants, not just the current one.\n // With per-tenant storage, oauthService.logOut() only clears the current\n // tenant's prefixed keys. We need to ensure no stale tokens remain for\n // any tenant after a full logout.\n this.tenantStorage.clearAllTenants();\n\n if (endSessionEndpoint) {\n // Build the end_session URL with id_token_hint and post_logout_redirect_uri\n const params = new URLSearchParams();\n if (idToken) {\n params.set('id_token_hint', idToken);\n }\n if (postLogoutRedirectUri) {\n params.set('post_logout_redirect_uri', postLogoutRedirectUri);\n }\n this.navigateTo(`${endSessionEndpoint}?${params.toString()}`);\n } else {\n // Fallback: no end_session_endpoint available, just reload\n this.reloadPage();\n }\n }\n\n /**\n * Updates the redirect URIs without performing a full re-initialization.\n * Use this when the OAuth session is already established and only the\n * redirect targets need to change (e.g., when switching tenants).\n */\n public updateRedirectUris(redirectUri: string, postLogoutRedirectUri: string): void {\n if (this.authorizeOptions) {\n this.authorizeOptions.redirectUri = redirectUri;\n this.authorizeOptions.postLogoutRedirectUri = postLogoutRedirectUri;\n }\n\n if (this.lastAuthConfig) {\n this.lastAuthConfig.redirectUri = redirectUri;\n this.lastAuthConfig.postLogoutRedirectUri = postLogoutRedirectUri;\n }\n\n // Update the redirect URIs directly on the OAuthService without calling\n // configure(), because configure() does Object.assign(this, new AuthConfig(), config)\n // which resets ALL properties — including logoutUrl, tokenEndpoint, and other\n // discovery document endpoints — back to their AuthConfig defaults (empty).\n this.oauthService.redirectUri = redirectUri;\n this.oauthService.postLogoutRedirectUri = postLogoutRedirectUri;\n\n console.debug(\"AuthorizeService::updateRedirectUris::done\");\n }\n\n /**\n * Refreshes the access token and updates the allowed tenants signal.\n * Call this after actions that change the user's tenant access (e.g., provisioning).\n */\n public async refreshAccessToken(): Promise<void> {\n console.debug(\"AuthorizeService::refreshAccessToken::started\");\n await this.oauthService.refreshToken();\n await this.loadUserAsync();\n console.debug(\"AuthorizeService::refreshAccessToken::done\");\n }\n\n /**\n * Initializes the authorization service with the specified options.\n */\n public async initialize(authorizeOptions: AuthorizeOptions): Promise<void> {\n console.debug(\"AuthorizeService::initialize::started\");\n\n await this.uninitialize();\n\n if (this._isInitializing()) {\n return;\n }\n if (this._isInitialized()) {\n console.debug(\"AuthorizeService::initialize::alreadyInitialized\");\n return;\n }\n this._isInitializing.set(true);\n\n try {\n const config: AuthConfig = {\n responseType: \"code\",\n issuer: authorizeOptions.issuer,\n redirectUri: authorizeOptions.redirectUri,\n postLogoutRedirectUri: authorizeOptions.postLogoutRedirectUri,\n clientId: authorizeOptions.clientId,\n scope: authorizeOptions.scope,\n showDebugInformation: authorizeOptions.showDebugInformation,\n sessionChecksEnabled: authorizeOptions.sessionChecksEnabled,\n sessionCheckIntervall: 60 * 1000,\n preserveRequestedRoute: true\n };\n\n this.authorizeOptions = authorizeOptions;\n this.lastAuthConfig = config;\n\n this.oauthService.setStorage(this.tenantStorage);\n this.oauthService.configure(config);\n console.debug(\"AuthorizeService::initialize::loadingDiscoveryDocumentAndTryLogin\");\n await this.oauthService.loadDiscoveryDocumentAndTryLogin();\n\n console.debug(\"AuthorizeService::initialize::setupAutomaticSilentRefresh\");\n this.oauthService.setupAutomaticSilentRefresh();\n\n this._issuer.set(authorizeOptions.issuer ?? null);\n\n if (this.oauthService.hasValidIdToken()) {\n // if the idToken is still valid, we can use the session\n console.debug(\"AuthorizeService::initialize::hasValidIdToken\");\n this._sessionLoading.set(true);\n await this.oauthService.refreshToken();\n }\n\n this._isInitialized.set(true);\n console.debug(\"AuthorizeService::initialize::done\");\n } finally {\n this._isInitializing.set(false);\n }\n\n console.debug(\"AuthorizeService::initialize::completed\");\n }\n\n /**\n * Uninitializes the authorization service.\n */\n public async uninitialize(): Promise<void> {\n console.debug(\"AuthorizeService::uninitialize::started\");\n\n if (this._isInitializing()) {\n return;\n }\n if (!this._isInitialized()) {\n console.debug(\"AuthorizeService::uninitialize::alreadyUninitialized\");\n return;\n }\n\n try {\n this._isInitializing.set(true);\n\n this.oauthService.stopAutomaticRefresh();\n\n this.authorizeOptions = null;\n this.lastAuthConfig = null;\n\n // Note: Do NOT clear auth signals (_accessToken, _isAuthenticated, etc.) here.\n // The access token and user info are globally valid (not per-tenant) and remain\n // valid during re-initialization. Clearing them creates a window where the HTTP\n // interceptor sends requests without a Bearer token, causing 401 errors.\n // Signals are already properly cleared on logout/session_terminated events.\n\n this._isInitialized.set(false);\n console.debug(\"AuthorizeService::uninitialize::done\");\n } finally {\n this._isInitializing.set(false);\n }\n\n console.debug(\"AuthorizeService::uninitialize::completed\");\n }\n\n private async loadUserAsync(): Promise<void> {\n const claims = this.oauthService.getIdentityClaims();\n if (!claims) {\n console.error(\"claims where null when loading identity claims\");\n return;\n }\n\n const user = claims as IUser;\n if (user.given_name && user.family_name) {\n this._userInitials.set(user.given_name.charAt(0).toUpperCase() + user.family_name.charAt(0).toUpperCase());\n } else {\n const derived = this.deriveDisplayNameFromUsername(user.name);\n this._userInitials.set(this.deriveInitials(derived));\n }\n\n const accessToken = this.oauthService.getAccessToken();\n this._user.set(user);\n this._accessToken.set(accessToken);\n this._isAuthenticated.set(true);\n this._sessionLoading.set(false);\n\n // Parse allowed_tenants from the access token\n this._allowedTenants.set(this.parseAllowedTenantsFromToken(accessToken));\n\n // Parse tenant_id from the access token (used for tenant mismatch detection)\n const tokenTenantId = this.parseTenantIdFromToken(accessToken);\n this._tokenTenantId.set(tokenTenantId);\n\n // Clear the pending tenant switch key now that we have a valid token.\n // This completes the switch cycle and prevents the guard from re-using\n // the pending tenant on subsequent route activations.\n this.clearPendingTenantSwitch();\n\n console.debug(`AuthorizeService::loadUserAsync::done (tokenTenantId=\"${tokenTenantId}\", allowedTenants=${JSON.stringify(this._allowedTenants())})`);\n }\n\n /**\n * Decodes the JWT access token payload and extracts allowed_tenants claims.\n * The claim can be a single string or an array of strings.\n */\n private parseAllowedTenantsFromToken(accessToken: string | null): string[] {\n if (!accessToken) {\n return [];\n }\n\n try {\n const parts = accessToken.split('.');\n if (parts.length !== 3) {\n return [];\n }\n\n const base64 = parts[1].replace(/-/g, '+').replace(/_/g, '/');\n const payload = JSON.parse(atob(base64));\n const allowedTenants = payload['allowed_tenants'];\n\n if (!allowedTenants) {\n return [];\n }\n\n if (Array.isArray(allowedTenants)) {\n return allowedTenants;\n }\n\n // Single value claim\n if (typeof allowedTenants === 'string') {\n return [allowedTenants];\n }\n\n return [];\n } catch (e) {\n console.warn('Failed to parse allowed_tenants from access token', e);\n return [];\n }\n }\n\n /**\n * Decodes the JWT access token payload and extracts the tenant_id claim.\n */\n private parseTenantIdFromToken(accessToken: string | null): string | null {\n if (!accessToken) {\n return null;\n }\n\n try {\n const parts = accessToken.split('.');\n if (parts.length !== 3) {\n return null;\n }\n\n const payload = JSON.parse(atob(parts[1]));\n return payload['tenant_id'] ?? null;\n } catch {\n return null;\n }\n }\n\n private deriveDisplayNameFromUsername(username: string): string {\n let name = username;\n // Strip xt_{tenantId}_ prefix\n const xtMatch = name.match(/^xt_[^_]+_(.+)$/);\n if (xtMatch) { name = xtMatch[1]; }\n // Extract local part of email\n const atIndex = name.indexOf('@');\n if (atIndex > 0) { name = name.substring(0, atIndex); }\n // Split by dots and capitalize\n const parts = name.split('.').filter(p => p.length > 0);\n return parts.map(p => p.charAt(0).toUpperCase() + p.slice(1)).join(' ');\n }\n\n private deriveInitials(displayName: string): string {\n const words = displayName.split(' ').filter(w => w.length > 0);\n if (words.length >= 2) {\n return words[0].charAt(0).toUpperCase() + words[1].charAt(0).toUpperCase();\n }\n if (words.length === 1 && words[0].length >= 2) {\n return words[0].charAt(0).toUpperCase() + words[0].charAt(1).toLowerCase();\n }\n return '??';\n }\n\n /**\n * Reloads the page. This method is protected to allow mocking in tests.\n * @internal\n */\n protected reloadPage(): void {\n window.location.reload();\n }\n\n /**\n * Navigates to the given URL via full page navigation.\n * This method is protected to allow mocking in tests.\n * @internal\n */\n protected navigateTo(url: string): void {\n window.location.href = url;\n }\n}\n","export enum Roles {\n ReportingManagement = 'ReportingManagement',\n ReportingViewer = 'ReportingViewer',\n AdminPanelManagement = 'AdminPanelManagement',\n BotManagement = 'BotManagement',\n UserManagement = 'UserManagement',\n CommunicationManagement = 'CommunicationManagement',\n TenantManagement = 'TenantManagement',\n Development = 'Development'\n}\n","import { inject } from '@angular/core';\nimport { HttpHandlerFn, HttpInterceptorFn, HttpRequest } from '@angular/common/http';\nimport { AuthorizeService } from './authorize.service';\n\n// =============================================================================\n// URL MATCHING UTILITIES\n// =============================================================================\n\n/**\n * Checks if the request URL is from the same origin as the application.\n */\nfunction isSameOriginUrl(req: HttpRequest<unknown>): boolean {\n // It's an absolute url with the same origin.\n if (req.url.startsWith(`${window.location.origin}/`)) {\n return true;\n }\n\n // It's a protocol relative url with the same origin.\n // For example: //www.example.com/api/Products\n if (req.url.startsWith(`//${window.location.host}/`)) {\n return true;\n }\n\n // It's a relative url like /api/Products\n if (/^\\/[^/].*/.test(req.url)) {\n return true;\n }\n\n // It's an absolute or protocol relative url that doesn't have the same origin.\n return false;\n}\n\n/**\n * Checks if the request URL matches any of the known service URIs.\n */\nfunction isKnownServiceUri(req: HttpRequest<unknown>, serviceUris: string[] | null): boolean {\n if (serviceUris != null) {\n for (const serviceUri of serviceUris) {\n if (req.url.startsWith(serviceUri)) {\n return true;\n }\n }\n }\n return false;\n}\n\n// =============================================================================\n// FUNCTIONAL INTERCEPTOR (RECOMMENDED)\n// =============================================================================\n\n/**\n * Functional HTTP interceptor that adds Bearer token to authorized requests.\n *\n * Adds the Authorization header to requests that are either:\n * - Same-origin requests (relative URLs or same host)\n * - Requests to known service URIs configured in AuthorizeOptions\n *\n * @example\n * ```typescript\n * // app.config.ts\n * import { provideHttpClient, withInterceptors } from '@angular/common/http';\n * import { authorizeInterceptor } from '@meshmakers/shared-auth';\n *\n * export const appConfig: ApplicationConfig = {\n * providers: [\n * provideHttpClient(withInterceptors([authorizeInterceptor])),\n * provideMmSharedAuth(),\n * ]\n * };\n * ```\n */\nexport const authorizeInterceptor: HttpInterceptorFn = (req: HttpRequest<unknown>, next: HttpHandlerFn) => {\n const authorizeService = inject(AuthorizeService);\n const token = authorizeService.getAccessTokenSync();\n const serviceUris = authorizeService.getServiceUris();\n\n if (token && (isSameOriginUrl(req) || isKnownServiceUri(req, serviceUris))) {\n req = req.clone({\n setHeaders: {\n Authorization: `Bearer ${token}`\n }\n });\n }\n\n return next(req);\n};\n","import { inject } from '@angular/core';\nimport { ActivatedRouteSnapshot, CanActivateFn, CanMatchFn, Router, RouterStateSnapshot } from '@angular/router';\nimport { AuthorizeService } from './authorize.service';\n\n/**\n * Walks up the route tree to find the tenantId parameter.\n */\nfunction findRouteTenantId(route: ActivatedRouteSnapshot): string | undefined {\n let current: ActivatedRouteSnapshot | null = route;\n while (current) {\n if (current.params?.['tenantId']) {\n return current.params['tenantId'];\n }\n current = current.parent;\n }\n return undefined;\n}\n\n/**\n * Handles authorization check for route activation.\n * Redirects to login if not authenticated, or to home if user lacks required roles.\n * Forces re-authentication when the token's tenant_id does not match the route's tenantId.\n *\n * @param route - The activated route snapshot containing route data\n * @returns true if authorized, false otherwise\n *\n * @example\n * ```typescript\n * // Route without role requirements\n * { path: 'dashboard', component: DashboardComponent, canActivate: [authorizeGuard] }\n *\n * // Route with role requirements\n * { path: 'admin', component: AdminComponent, canActivate: [authorizeGuard], data: { roles: ['AdminPanelManagement'] } }\n * ```\n */\nexport const authorizeGuard: CanActivateFn = async (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {\n const authorizeService = inject(AuthorizeService);\n const router = inject(Router);\n\n // Use signal directly (synchronous)\n const isAuthenticated = authorizeService.isAuthenticated();\n\n if (!isAuthenticated) {\n // Check if this is a reload after switchTenant — use the stored tenantId\n const pendingTenant = authorizeService.consumePendingTenantSwitch();\n const tenantId = pendingTenant ?? findRouteTenantId(route);\n authorizeService.login(tenantId);\n return false;\n }\n\n // Force re-authentication if the token was issued for a different tenant.\n // switchTenant clears the local session and reloads, so the next guard\n // invocation enters the !isAuthenticated branch above with the correct tenantId.\n const tokenTenantId = authorizeService.tokenTenantId();\n const routeTenantId = findRouteTenantId(route);\n if (tokenTenantId && routeTenantId && routeTenantId.toLowerCase() !== tokenTenantId.toLowerCase()) {\n console.debug(`[AuthGuard] Tenant mismatch: token=\"${tokenTenantId}\", route=\"${routeTenantId}\" — attempting tenant switch`);\n // switchTenant returns false if a switch was already attempted (loop prevention).\n // Use state.url (the router's target URL) — not window.location.href which is\n // still the current page during in-app navigation (e.g., tenant switcher dropdown).\n const targetUrl = window.location.origin + state.url;\n if (authorizeService.switchTenant(routeTenantId, targetUrl)) {\n return false;\n }\n // Switch was skipped — fall through to role-based checks\n console.warn(`[AuthGuard] Tenant mismatch persists after switch attempt (token=\"${tokenTenantId}\", route=\"${routeTenantId}\"). Proceeding with current token.`);\n } else {\n // No mismatch — clear any leftover switch-attempted flag\n authorizeService.consumeSwitchAttempted();\n }\n\n // Use roles signal directly (synchronous)\n const userRoles = authorizeService.roles();\n const requiredRoles = route.data['roles'] as string[] | undefined;\n\n if (requiredRoles === undefined || requiredRoles === null) {\n if (typeof ngDevMode === 'undefined' || ngDevMode) {\n console.warn(`[AuthGuard] Route \"${route.routeConfig?.path}\" has no required roles defined — access granted to any authenticated user.`);\n }\n return true;\n }\n\n // Empty roles array (roles: []) means intentionally open to any authenticated user\n if (requiredRoles.length === 0) {\n return true;\n }\n\n if (!requiredRoles.some(role => userRoles.includes(role))) {\n // Navigate to the current tenant's home, not root ''.\n // Navigating to '' would redirect to a default tenant (e.g., octosystem),\n // causing a tenant mismatch → switchTenant → redirect loop.\n const tenantHome = routeTenantId ? `/${routeTenantId}` : '';\n await router.navigate([tenantHome]);\n return false;\n }\n\n return true;\n};\n\n/**\n * Guard for child routes. Delegates to authorizeGuard.\n *\n * @example\n * ```typescript\n * {\n * path: 'parent',\n * canActivateChild: [authorizeChildGuard],\n * children: [\n * { path: 'child', component: ChildComponent, data: { roles: ['RequiredRole'] } }\n * ]\n * }\n * ```\n */\nexport const authorizeChildGuard: CanActivateFn = authorizeGuard;\n\n/**\n * Guard for lazy-loaded routes. Checks if user is authenticated.\n * Replaces the deprecated canLoad guard.\n *\n * @example\n * ```typescript\n * {\n * path: 'lazy',\n * loadChildren: () => import('./lazy/lazy.routes'),\n * canMatch: [authorizeMatchGuard]\n * }\n * ```\n */\nexport const authorizeMatchGuard: CanMatchFn = (_route, segments) => {\n const authorizeService = inject(AuthorizeService);\n\n // Use signal directly (synchronous)\n const isAuthenticated = authorizeService.isAuthenticated();\n\n if (!isAuthenticated) {\n // The first URL segment is typically the tenantId (e.g., /:tenantId/...)\n const tenantId = segments.length > 0 ? segments[0].path : undefined;\n authorizeService.login(tenantId);\n return false;\n }\n\n return true;\n};\n\n/**\n * Guard that always allows deactivation.\n * Use this as a placeholder or override in specific routes.\n *\n * @example\n * ```typescript\n * { path: 'form', component: FormComponent, canDeactivate: [authorizeDeactivateGuard] }\n * ```\n */\nexport const authorizeDeactivateGuard = () => true;\n","/*\n * Public API Surface of shared-auth\n */\n\nimport { EnvironmentProviders, makeEnvironmentProviders } from '@angular/core';\nimport { AuthorizeService } from './lib/authorize.service';\nimport { provideOAuthClient } from 'angular-oauth2-oidc';\n\n// Core services\nexport * from './lib/authorize.service';\nexport * from './lib/roles';\nexport { TenantAwareOAuthStorage } from './lib/tenant-aware-oauth-storage';\n\n// Functional interceptor\nexport { authorizeInterceptor } from './lib/authorize.interceptor';\n\n// Functional guards\nexport {\n authorizeGuard,\n authorizeChildGuard,\n authorizeMatchGuard,\n authorizeDeactivateGuard\n} from './lib/authorize.guard';\n\n// UI Components (Kendo) - available via '@meshmakers/shared-auth/login-ui'\n// import { LoginAppBarSectionComponent } from '@meshmakers/shared-auth/login-ui';\n\n/**\n * Provides all shared-auth dependencies.\n *\n * @example\n * ```typescript\n * // app.config.ts\n * import { provideHttpClient, withInterceptors } from '@angular/common/http';\n * import { provideMmSharedAuth, authorizeInterceptor } from '@meshmakers/shared-auth';\n *\n * export const appConfig: ApplicationConfig = {\n * providers: [\n * provideHttpClient(withInterceptors([authorizeInterceptor])),\n * provideMmSharedAuth(),\n * // ... other providers\n * ]\n * };\n * ```\n *\n * @remarks\n * Functional guards and interceptors don't need to be provided - they use inject() internally.\n * For the functional interceptor, use `provideHttpClient(withInterceptors([authorizeInterceptor]))`.\n */\nexport function provideMmSharedAuth(): EnvironmentProviders {\n return makeEnvironmentProviders([\n provideOAuthClient(),\n AuthorizeService\n ]);\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { Signal, EnvironmentProviders } from '@angular/core';
|
|
3
|
+
import { OAuthStorage } from 'angular-oauth2-oidc';
|
|
3
4
|
import { CanActivateFn, CanMatchFn } from '@angular/router';
|
|
4
5
|
import { HttpInterceptorFn } from '@angular/common/http';
|
|
5
6
|
|
|
@@ -48,6 +49,8 @@ declare class AuthorizeService {
|
|
|
48
49
|
private readonly _tokenTenantId;
|
|
49
50
|
private static readonly TENANT_REAUTH_KEY;
|
|
50
51
|
private static readonly TENANT_SWITCH_ATTEMPTED_KEY;
|
|
52
|
+
private readonly tenantStorage;
|
|
53
|
+
private _loginInProgress;
|
|
51
54
|
private authorizeOptions;
|
|
52
55
|
private lastAuthConfig;
|
|
53
56
|
/**
|
|
@@ -102,6 +105,26 @@ declare class AuthorizeService {
|
|
|
102
105
|
* Gets the configured service URIs that should receive the authorization token.
|
|
103
106
|
*/
|
|
104
107
|
getServiceUris(): string[] | null;
|
|
108
|
+
/**
|
|
109
|
+
* Sets the tenant ID for per-tenant token storage isolation.
|
|
110
|
+
* Must be called BEFORE initialize() to ensure tokens are stored/retrieved
|
|
111
|
+
* under the correct tenant prefix in localStorage.
|
|
112
|
+
*
|
|
113
|
+
* When set, all OAuth storage keys are prefixed with `{tenantId}__`
|
|
114
|
+
* (e.g., `maco__access_token`), preventing token collisions between tenants.
|
|
115
|
+
* The tenant ID is also persisted in sessionStorage so it survives OAuth redirects.
|
|
116
|
+
*
|
|
117
|
+
* @param tenantId The tenant ID to use for storage key prefixing, or null for unprefixed mode.
|
|
118
|
+
*/
|
|
119
|
+
setStorageTenantId(tenantId: string | null): void;
|
|
120
|
+
/**
|
|
121
|
+
* Restores the storage tenant ID from sessionStorage.
|
|
122
|
+
* Use this when the tenant ID cannot be determined from the URL
|
|
123
|
+
* (e.g., after an OAuth redirect to the root path).
|
|
124
|
+
*
|
|
125
|
+
* @returns The restored tenant ID, or null if none was persisted.
|
|
126
|
+
*/
|
|
127
|
+
restoreStorageTenantId(): string | null;
|
|
105
128
|
/**
|
|
106
129
|
* Gets the current access token synchronously.
|
|
107
130
|
* Use this for functional interceptors that need immediate access to the token.
|
|
@@ -116,6 +139,12 @@ declare class AuthorizeService {
|
|
|
116
139
|
isTenantAllowed(tenantId: string): boolean;
|
|
117
140
|
/**
|
|
118
141
|
* Initiates the login flow.
|
|
142
|
+
* Multiple guards (canActivateChild, canMatch) may call this concurrently
|
|
143
|
+
* during route resolution. Only the first call proceeds — subsequent calls
|
|
144
|
+
* are skipped to prevent generating a new nonce that overwrites the first
|
|
145
|
+
* one, which would cause an "invalid_nonce_in_state" error after the
|
|
146
|
+
* identity server redirects back.
|
|
147
|
+
*
|
|
119
148
|
* @param tenantId Optional tenant ID. When provided, includes acr_values=tenant:{tenantId}
|
|
120
149
|
* so the identity server redirects to the correct tenant's login page.
|
|
121
150
|
*/
|
|
@@ -133,10 +162,20 @@ declare class AuthorizeService {
|
|
|
133
162
|
*/
|
|
134
163
|
switchTenant(targetTenantId: string, targetUrl: string): boolean;
|
|
135
164
|
/**
|
|
136
|
-
* Returns the pending tenant switch target (if any)
|
|
137
|
-
*
|
|
165
|
+
* Returns the pending tenant switch target (if any) WITHOUT clearing it.
|
|
166
|
+
* The key persists across the OAuth redirect cycle (guard → IDS → callback → guard)
|
|
167
|
+
* and is only cleared after a successful token exchange in loadUserAsync().
|
|
168
|
+
*
|
|
169
|
+
* Previously this method removed the key immediately, but that caused a race condition:
|
|
170
|
+
* the guard consumed it before the IDS redirect, and after the callback redirect the
|
|
171
|
+
* key was gone — causing the guard to fall back to the route tenant (wrong tenant).
|
|
138
172
|
*/
|
|
139
173
|
consumePendingTenantSwitch(): string | null;
|
|
174
|
+
/**
|
|
175
|
+
* Clears the pending tenant switch key. Called after a successful token exchange
|
|
176
|
+
* when the token's tenant_id matches the target, completing the switch cycle.
|
|
177
|
+
*/
|
|
178
|
+
clearPendingTenantSwitch(): void;
|
|
140
179
|
/**
|
|
141
180
|
* Returns the tenant for which a switch was already attempted (if any) and clears it.
|
|
142
181
|
* Used by the guard to prevent infinite redirect loops: if the Identity Server
|
|
@@ -189,10 +228,83 @@ declare class AuthorizeService {
|
|
|
189
228
|
* @internal
|
|
190
229
|
*/
|
|
191
230
|
protected reloadPage(): void;
|
|
231
|
+
/**
|
|
232
|
+
* Navigates to the given URL via full page navigation.
|
|
233
|
+
* This method is protected to allow mocking in tests.
|
|
234
|
+
* @internal
|
|
235
|
+
*/
|
|
236
|
+
protected navigateTo(url: string): void;
|
|
192
237
|
static ɵfac: i0.ɵɵFactoryDeclaration<AuthorizeService, never>;
|
|
193
238
|
static ɵprov: i0.ɵɵInjectableDeclaration<AuthorizeService>;
|
|
194
239
|
}
|
|
195
240
|
|
|
241
|
+
/**
|
|
242
|
+
* Tenant-aware OAuth storage that prefixes all keys with a tenant ID
|
|
243
|
+
* and splits storage between localStorage and sessionStorage.
|
|
244
|
+
*
|
|
245
|
+
* **Key prefixing:** When a tenant ID is set, all storage keys are prefixed
|
|
246
|
+
* with `{tenantId}__` (double underscore separator). This isolates OAuth
|
|
247
|
+
* tokens per tenant, preventing race conditions during tenant switches.
|
|
248
|
+
*
|
|
249
|
+
* **Storage split:** Flow-specific ephemeral keys (nonce, PKCE_verifier)
|
|
250
|
+
* are stored in sessionStorage (per browser tab), while tokens and session
|
|
251
|
+
* data are stored in localStorage (shared across tabs). This prevents
|
|
252
|
+
* cross-tab nonce collisions when multiple tabs initiate login simultaneously.
|
|
253
|
+
*
|
|
254
|
+
* When no tenant ID is set (null), keys are stored without a prefix,
|
|
255
|
+
* maintaining backwards compatibility with existing single-tenant apps.
|
|
256
|
+
*
|
|
257
|
+
* @example
|
|
258
|
+
* ```typescript
|
|
259
|
+
* const storage = new TenantAwareOAuthStorage();
|
|
260
|
+
* storage.setTenantId('maco');
|
|
261
|
+
* storage.setItem('access_token', 'abc');
|
|
262
|
+
* // Stored in: localStorage['maco__access_token'] = 'abc'
|
|
263
|
+
*
|
|
264
|
+
* storage.setItem('nonce', 'xyz');
|
|
265
|
+
* // Stored in: sessionStorage['maco__nonce'] = 'xyz' (per-tab!)
|
|
266
|
+
* ```
|
|
267
|
+
*/
|
|
268
|
+
declare class TenantAwareOAuthStorage extends OAuthStorage {
|
|
269
|
+
private tenantId;
|
|
270
|
+
/**
|
|
271
|
+
* Sets the tenant ID used to prefix storage keys.
|
|
272
|
+
* Must be called before the OAuthService reads/writes tokens.
|
|
273
|
+
* The tenant ID is persisted in sessionStorage so it survives OAuth redirects.
|
|
274
|
+
*
|
|
275
|
+
* @param tenantId The tenant ID, or null for unprefixed (backwards-compatible) mode.
|
|
276
|
+
*/
|
|
277
|
+
setTenantId(tenantId: string | null): void;
|
|
278
|
+
/**
|
|
279
|
+
* Returns the currently configured tenant ID.
|
|
280
|
+
*/
|
|
281
|
+
getTenantId(): string | null;
|
|
282
|
+
/**
|
|
283
|
+
* Restores the tenant ID from sessionStorage.
|
|
284
|
+
* Call this on app startup when the tenant cannot be determined from the URL
|
|
285
|
+
* (e.g., after an OAuth redirect to the root path).
|
|
286
|
+
*
|
|
287
|
+
* @returns The restored tenant ID, or null if none was persisted.
|
|
288
|
+
*/
|
|
289
|
+
restoreTenantId(): string | null;
|
|
290
|
+
/**
|
|
291
|
+
* Returns the prefixed key for the given base key.
|
|
292
|
+
* When tenantId is null, returns the base key unchanged.
|
|
293
|
+
*/
|
|
294
|
+
prefixKey(key: string): string;
|
|
295
|
+
getItem(key: string): string | null;
|
|
296
|
+
removeItem(key: string): void;
|
|
297
|
+
setItem(key: string, data: string): void;
|
|
298
|
+
/**
|
|
299
|
+
* Clears all OAuth-related keys for ALL tenants from both
|
|
300
|
+
* localStorage and sessionStorage, while leaving non-OAuth data intact.
|
|
301
|
+
*
|
|
302
|
+
* Used during full logout to ensure no stale tokens remain for any tenant.
|
|
303
|
+
*/
|
|
304
|
+
clearAllTenants(): void;
|
|
305
|
+
private clearOAuthKeysFromStorage;
|
|
306
|
+
}
|
|
307
|
+
|
|
196
308
|
/**
|
|
197
309
|
* Functional HTTP interceptor that adds Bearer token to authorized requests.
|
|
198
310
|
*
|
|
@@ -298,5 +410,5 @@ declare const authorizeDeactivateGuard: () => boolean;
|
|
|
298
410
|
*/
|
|
299
411
|
declare function provideMmSharedAuth(): EnvironmentProviders;
|
|
300
412
|
|
|
301
|
-
export { AuthorizeOptions, AuthorizeService, Roles, authorizeChildGuard, authorizeDeactivateGuard, authorizeGuard, authorizeInterceptor, authorizeMatchGuard, provideMmSharedAuth };
|
|
413
|
+
export { AuthorizeOptions, AuthorizeService, Roles, TenantAwareOAuthStorage, authorizeChildGuard, authorizeDeactivateGuard, authorizeGuard, authorizeInterceptor, authorizeMatchGuard, provideMmSharedAuth };
|
|
302
414
|
export type { IUser };
|