@meshmakers/shared-auth 3.3.580 → 3.3.600
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/meshmakers-shared-auth-login-ui.mjs +15 -16
- package/fesm2022/meshmakers-shared-auth-login-ui.mjs.map +1 -1
- package/fesm2022/meshmakers-shared-auth.mjs +365 -17
- package/fesm2022/meshmakers-shared-auth.mjs.map +1 -1
- package/package.json +1 -1
- package/types/meshmakers-shared-auth-login-ui.d.ts +7 -5
- package/types/meshmakers-shared-auth.d.ts +82 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, EventEmitter, computed,
|
|
2
|
+
import { inject, EventEmitter, computed, ElementRef, HostListener, Input, Output, ViewChild, Component } from '@angular/core';
|
|
3
3
|
import { AuthorizeService } from '@meshmakers/shared-auth';
|
|
4
4
|
import { AvatarComponent } from '@progress/kendo-angular-layout';
|
|
5
5
|
import { ButtonComponent } from '@progress/kendo-angular-buttons';
|
|
@@ -16,31 +16,30 @@ class LoginAppBarSectionComponent {
|
|
|
16
16
|
*/
|
|
17
17
|
userName = computed(() => this.authorizeService.user()?.name ?? null, ...(ngDevMode ? [{ debugName: "userName" }] : []));
|
|
18
18
|
/**
|
|
19
|
-
* Computed signal for the user's
|
|
19
|
+
* Computed signal for the user's display name.
|
|
20
|
+
* Delegates to AuthorizeService which handles cross-tenant username derivation.
|
|
20
21
|
*/
|
|
21
|
-
fullName =
|
|
22
|
-
const user = this.authorizeService.user();
|
|
23
|
-
if (user?.given_name && user?.family_name) {
|
|
24
|
-
return user.given_name + " " + user.family_name;
|
|
25
|
-
}
|
|
26
|
-
return null;
|
|
27
|
-
}, ...(ngDevMode ? [{ debugName: "fullName" }] : []));
|
|
22
|
+
fullName = this.authorizeService.displayName;
|
|
28
23
|
/**
|
|
29
|
-
*
|
|
24
|
+
* Computed signal for the profile management URI.
|
|
25
|
+
* Uses the tenant_id from the access token to build the correct URL.
|
|
30
26
|
*/
|
|
31
|
-
profileUri =
|
|
27
|
+
profileUri = computed(() => {
|
|
28
|
+
const issuerUri = this.authorizeService.issuer();
|
|
29
|
+
const tenantId = this.authorizeService.tokenTenantId();
|
|
30
|
+
if (issuerUri && tenantId) {
|
|
31
|
+
return `${issuerUri}${tenantId}/manage`;
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}, ...(ngDevMode ? [{ debugName: "profileUri" }] : []));
|
|
32
35
|
anchor = null;
|
|
33
36
|
popup = null;
|
|
34
37
|
constructor() {
|
|
35
38
|
this._showPopup = false;
|
|
36
39
|
this._showRegister = false;
|
|
37
40
|
}
|
|
38
|
-
|
|
41
|
+
ngOnInit() {
|
|
39
42
|
console.debug('mm-login-app-bar-section::created');
|
|
40
|
-
const issuerUri = this.authorizeService.issuer();
|
|
41
|
-
if (issuerUri) {
|
|
42
|
-
this.profileUri.set(issuerUri + "Manage");
|
|
43
|
-
}
|
|
44
43
|
}
|
|
45
44
|
get register() {
|
|
46
45
|
return this._register;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"meshmakers-shared-auth-login-ui.mjs","sources":["../../../../projects/meshmakers/shared-auth/login-ui/src/mm-login-app-bar-section/login-app-bar-section.component.ts","../../../../projects/meshmakers/shared-auth/login-ui/src/mm-login-app-bar-section/login-app-bar-section.component.html","../../../../projects/meshmakers/shared-auth/login-ui/src/public-api.ts","../../../../projects/meshmakers/shared-auth/login-ui/src/meshmakers-shared-auth-login-ui.ts"],"sourcesContent":["import { Component, computed, ElementRef, EventEmitter, HostListener, Input, OnInit, Output, Signal, signal, ViewChild, WritableSignal, inject } from '@angular/core';\nimport { AuthorizeService } from '@meshmakers/shared-auth';\nimport { AvatarComponent } from '@progress/kendo-angular-layout';\nimport { ButtonComponent } from '@progress/kendo-angular-buttons';\nimport { PopupComponent } from '@progress/kendo-angular-popup';\nimport { LoaderComponent } from '@progress/kendo-angular-indicators';\n\n@Component({\n selector: 'mm-login-app-bar-section',\n imports: [\n AvatarComponent,\n ButtonComponent,\n PopupComponent,\n LoaderComponent\n ],\n templateUrl: './login-app-bar-section.component.html',\n styleUrl: './login-app-bar-section.component.scss'\n})\nexport class LoginAppBarSectionComponent implements OnInit {\n protected readonly authorizeService = inject(AuthorizeService);\n\n private readonly _register = new EventEmitter();\n private _showRegister = false;\n private _showPopup = false;\n\n /**\n * Computed signal for the user's display name.\n */\n protected readonly userName: Signal<string | null> = computed(() =>\n this.authorizeService.user()?.name ?? null\n );\n\n /**\n * Computed signal for the user's full name (given name + family name).\n */\n protected readonly fullName: Signal<string | null> = computed(() => {\n const user = this.authorizeService.user();\n if (user?.given_name && user?.family_name) {\n return user.given_name + \" \" + user.family_name;\n }\n return null;\n });\n\n /**\n * Signal for the profile management URI.\n */\n protected readonly profileUri: WritableSignal<string | null> = signal(null);\n\n @ViewChild(\"user\", { read: ElementRef })\n private anchor: ElementRef | null = null;\n\n @ViewChild(\"popup\", { read: ElementRef })\n private popup: ElementRef | null = null;\n\n constructor() {\n this._showPopup = false;\n this._showRegister = false;\n }\n\n async ngOnInit(): Promise<void> {\n console.debug('mm-login-app-bar-section::created');\n\n const issuerUri = this.authorizeService.issuer();\n if (issuerUri) {\n this.profileUri.set(issuerUri + \"Manage\");\n }\n }\n\n @Output() get register(): EventEmitter<void> {\n return this._register;\n }\n\n public get showPopup(): boolean {\n return this._showPopup;\n }\n\n public set showPopup(value: boolean) {\n this._showPopup = value;\n }\n\n public get showRegister(): boolean {\n return this._showRegister;\n }\n\n @Input()\n public set showRegister(value: boolean) {\n this._showRegister = value;\n }\n\n @HostListener(\"document:keydown\", [\"$event\"])\n public keydown(event: KeyboardEvent): void {\n if (event.code === \"Escape\") {\n this.onToggle(false);\n }\n }\n\n @HostListener(\"document:click\", [\"$event\"])\n public documentClick(event: MouseEvent): void {\n if (!this.contains(event.target)) {\n this.onToggle(false);\n }\n }\n\n private contains(target: EventTarget | null): boolean {\n return (\n this.anchor?.nativeElement.contains(target) ||\n (this.popup ? this.popup.nativeElement.contains(target) : false)\n );\n }\n\n public onToggle(show?: boolean): void {\n this._showPopup = show !== undefined ? show : !this._showPopup;\n }\n\n protected onLogin(): void {\n this.authorizeService.login();\n }\n\n protected onLogout(): void {\n this.authorizeService.logout();\n }\n\n protected onRegister(): void {\n this.register.emit();\n }\n}\n","@if (authorizeService.sessionLoading()) {\n <kendo-loader type=\"pulsing\"></kendo-loader>\n}\n\n@if (!authorizeService.sessionLoading() && !authorizeService.isAuthenticated()) {\n <div>\n @if (showRegister) {\n <button kendoButton size=\"large\" class=\"login-button\" (click)='onRegister()'>Register</button>\n }\n <button kendoButton size=\"large\" themeColor=\"primary\" class=\"login-button\" (click)='onLogin()'>Login</button>\n </div>\n}\n\n@if (!authorizeService.sessionLoading() && authorizeService.isAuthenticated()) {\n <div>\n <button kendoButton #user fillMode=\"flat\" (click)=\"onToggle()\">\n <kendo-avatar [initials]=\"authorizeService.userInitials() || '??'\"\n shape=\"circle\"\n width=\"32px\"\n height=\"32px\"></kendo-avatar>\n </button>\n @if (showPopup) {\n <kendo-popup\n [anchor]=\"user.element\"\n (anchorViewportLeave)=\"showPopup = false\">\n <div class=\"content\">\n <kendo-avatar class=\"user-avatar\"\n [initials]=\"authorizeService.userInitials() || '??'\"\n width=\"80px\"\n height=\"80px\"\n shape=\"circle\"\n ></kendo-avatar>\n\n @if (fullName(); as name) {\n <p class=\"user-name\">{{ name }}</p>\n }\n <p class=\"user-name\">{{ userName() }}</p>\n\n <div class=\"buttons\">\n <button kendoButton themeColor=\"primary\" (click)=\"onLogout()\">\n Logout\n </button>\n @if (profileUri(); as uri) {\n <a class=\"k-button k-button-md k-rounded-md k-button-solid\" [href]=\"uri\" target=\"_blank\">\n Manage Profile\n </a>\n }\n </div>\n </div>\n </kendo-popup>\n }\n </div>\n}\n","/*\n * Public API Surface of shared-auth/login-ui\n *\n * This secondary entry point contains the Kendo-based LoginAppBarSectionComponent.\n * Import from '@meshmakers/shared-auth/login-ui' only in apps that use Kendo UI.\n */\nexport * from './mm-login-app-bar-section/login-app-bar-section.component';\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-login-ui.mjs","sources":["../../../../projects/meshmakers/shared-auth/login-ui/src/mm-login-app-bar-section/login-app-bar-section.component.ts","../../../../projects/meshmakers/shared-auth/login-ui/src/mm-login-app-bar-section/login-app-bar-section.component.html","../../../../projects/meshmakers/shared-auth/login-ui/src/public-api.ts","../../../../projects/meshmakers/shared-auth/login-ui/src/meshmakers-shared-auth-login-ui.ts"],"sourcesContent":["import { Component, computed, ElementRef, EventEmitter, HostListener, Input, OnInit, Output, Signal, ViewChild, inject } from '@angular/core';\nimport { AuthorizeService } from '@meshmakers/shared-auth';\nimport { AvatarComponent } from '@progress/kendo-angular-layout';\nimport { ButtonComponent } from '@progress/kendo-angular-buttons';\nimport { PopupComponent } from '@progress/kendo-angular-popup';\nimport { LoaderComponent } from '@progress/kendo-angular-indicators';\n\n@Component({\n selector: 'mm-login-app-bar-section',\n imports: [\n AvatarComponent,\n ButtonComponent,\n PopupComponent,\n LoaderComponent\n ],\n templateUrl: './login-app-bar-section.component.html',\n styleUrl: './login-app-bar-section.component.scss'\n})\nexport class LoginAppBarSectionComponent implements OnInit {\n protected readonly authorizeService = inject(AuthorizeService);\n\n private readonly _register = new EventEmitter();\n private _showRegister = false;\n private _showPopup = false;\n\n /**\n * Computed signal for the user's display name.\n */\n protected readonly userName: Signal<string | null> = computed(() =>\n this.authorizeService.user()?.name ?? null\n );\n\n /**\n * Computed signal for the user's display name.\n * Delegates to AuthorizeService which handles cross-tenant username derivation.\n */\n protected readonly fullName: Signal<string | null> = this.authorizeService.displayName;\n\n /**\n * Computed signal for the profile management URI.\n * Uses the tenant_id from the access token to build the correct URL.\n */\n protected readonly profileUri: Signal<string | null> = computed(() => {\n const issuerUri = this.authorizeService.issuer();\n const tenantId = this.authorizeService.tokenTenantId();\n if (issuerUri && tenantId) {\n return `${issuerUri}${tenantId}/manage`;\n }\n return null;\n });\n\n @ViewChild(\"user\", { read: ElementRef })\n private anchor: ElementRef | null = null;\n\n @ViewChild(\"popup\", { read: ElementRef })\n private popup: ElementRef | null = null;\n\n constructor() {\n this._showPopup = false;\n this._showRegister = false;\n }\n\n ngOnInit(): void {\n console.debug('mm-login-app-bar-section::created');\n }\n\n @Output() get register(): EventEmitter<void> {\n return this._register;\n }\n\n public get showPopup(): boolean {\n return this._showPopup;\n }\n\n public set showPopup(value: boolean) {\n this._showPopup = value;\n }\n\n public get showRegister(): boolean {\n return this._showRegister;\n }\n\n @Input()\n public set showRegister(value: boolean) {\n this._showRegister = value;\n }\n\n @HostListener(\"document:keydown\", [\"$event\"])\n public keydown(event: KeyboardEvent): void {\n if (event.code === \"Escape\") {\n this.onToggle(false);\n }\n }\n\n @HostListener(\"document:click\", [\"$event\"])\n public documentClick(event: MouseEvent): void {\n if (!this.contains(event.target)) {\n this.onToggle(false);\n }\n }\n\n private contains(target: EventTarget | null): boolean {\n return (\n this.anchor?.nativeElement.contains(target) ||\n (this.popup ? this.popup.nativeElement.contains(target) : false)\n );\n }\n\n public onToggle(show?: boolean): void {\n this._showPopup = show !== undefined ? show : !this._showPopup;\n }\n\n protected onLogin(): void {\n this.authorizeService.login();\n }\n\n protected onLogout(): void {\n this.authorizeService.logout();\n }\n\n protected onRegister(): void {\n this.register.emit();\n }\n}\n","@if (authorizeService.sessionLoading()) {\n <kendo-loader type=\"pulsing\"></kendo-loader>\n}\n\n@if (!authorizeService.sessionLoading() && !authorizeService.isAuthenticated()) {\n <div>\n @if (showRegister) {\n <button kendoButton size=\"large\" class=\"login-button\" (click)='onRegister()'>Register</button>\n }\n <button kendoButton size=\"large\" themeColor=\"primary\" class=\"login-button\" (click)='onLogin()'>Login</button>\n </div>\n}\n\n@if (!authorizeService.sessionLoading() && authorizeService.isAuthenticated()) {\n <div>\n <button kendoButton #user fillMode=\"flat\" (click)=\"onToggle()\">\n <kendo-avatar [initials]=\"authorizeService.userInitials() || '??'\"\n shape=\"circle\"\n width=\"32px\"\n height=\"32px\"></kendo-avatar>\n </button>\n @if (showPopup) {\n <kendo-popup\n [anchor]=\"user.element\"\n (anchorViewportLeave)=\"showPopup = false\">\n <div class=\"content\">\n <kendo-avatar class=\"user-avatar\"\n [initials]=\"authorizeService.userInitials() || '??'\"\n width=\"80px\"\n height=\"80px\"\n shape=\"circle\"\n ></kendo-avatar>\n\n @if (fullName(); as name) {\n <p class=\"user-name\">{{ name }}</p>\n }\n <p class=\"user-name\">{{ userName() }}</p>\n\n <div class=\"buttons\">\n <button kendoButton themeColor=\"primary\" (click)=\"onLogout()\">\n Logout\n </button>\n @if (profileUri(); as uri) {\n <a class=\"k-button k-button-md k-rounded-md k-button-solid\" [href]=\"uri\" target=\"_blank\">\n Manage Profile\n </a>\n }\n </div>\n </div>\n </kendo-popup>\n }\n </div>\n}\n","/*\n * Public API Surface of shared-auth/login-ui\n *\n * This secondary entry point contains the Kendo-based LoginAppBarSectionComponent.\n * Import from '@meshmakers/shared-auth/login-ui' only in apps that use Kendo UI.\n */\nexport * from './mm-login-app-bar-section/login-app-bar-section.component';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
@@ -17,6 +17,9 @@ class AuthorizeOptions {
|
|
|
17
17
|
scope;
|
|
18
18
|
showDebugInformation;
|
|
19
19
|
sessionChecksEnabled;
|
|
20
|
+
// Default tenant ID for single-tenant apps. When set, login() uses this tenant
|
|
21
|
+
// if no tenantId is explicitly provided (sends acr_values=tenant:{defaultTenantId}).
|
|
22
|
+
defaultTenantId;
|
|
20
23
|
}
|
|
21
24
|
class AuthorizeService {
|
|
22
25
|
oauthService = inject(OAuthService);
|
|
@@ -31,7 +34,12 @@ class AuthorizeService {
|
|
|
31
34
|
_isInitialized = signal(false, ...(ngDevMode ? [{ debugName: "_isInitialized" }] : []));
|
|
32
35
|
_isInitializing = signal(false, ...(ngDevMode ? [{ debugName: "_isInitializing" }] : []));
|
|
33
36
|
_sessionLoading = signal(false, ...(ngDevMode ? [{ debugName: "_sessionLoading" }] : []));
|
|
37
|
+
_allowedTenants = signal([], ...(ngDevMode ? [{ debugName: "_allowedTenants" }] : []));
|
|
38
|
+
_tokenTenantId = signal(null, ...(ngDevMode ? [{ debugName: "_tokenTenantId" }] : []));
|
|
39
|
+
static TENANT_REAUTH_KEY = 'octo_tenant_reauth';
|
|
40
|
+
static TENANT_SWITCH_ATTEMPTED_KEY = 'octo_tenant_switch_attempted';
|
|
34
41
|
authorizeOptions = null;
|
|
42
|
+
lastAuthConfig = null;
|
|
35
43
|
// =============================================================================
|
|
36
44
|
// PUBLIC API (Readonly Signals) - NEW API
|
|
37
45
|
// =============================================================================
|
|
@@ -59,10 +67,33 @@ class AuthorizeService {
|
|
|
59
67
|
* Signal indicating whether the session is currently loading.
|
|
60
68
|
*/
|
|
61
69
|
sessionLoading = this._sessionLoading.asReadonly();
|
|
70
|
+
/**
|
|
71
|
+
* Signal containing the list of tenants the user is allowed to access.
|
|
72
|
+
* Parsed from the allowed_tenants claims in the access token.
|
|
73
|
+
*/
|
|
74
|
+
allowedTenants = this._allowedTenants.asReadonly();
|
|
75
|
+
/**
|
|
76
|
+
* Signal containing the tenant_id claim from the current access token.
|
|
77
|
+
* Used to detect tenant mismatch when navigating between tenants.
|
|
78
|
+
*/
|
|
79
|
+
tokenTenantId = this._tokenTenantId.asReadonly();
|
|
62
80
|
/**
|
|
63
81
|
* Computed signal containing the user's roles.
|
|
64
82
|
*/
|
|
65
83
|
roles = computed(() => this._user()?.role ?? [], ...(ngDevMode ? [{ debugName: "roles" }] : []));
|
|
84
|
+
/**
|
|
85
|
+
* Computed signal for the user's display name.
|
|
86
|
+
* Uses given_name + family_name if available, otherwise derives from the username.
|
|
87
|
+
*/
|
|
88
|
+
displayName = computed(() => {
|
|
89
|
+
const user = this._user();
|
|
90
|
+
if (!user)
|
|
91
|
+
return null;
|
|
92
|
+
if (user.given_name && user.family_name) {
|
|
93
|
+
return user.given_name + ' ' + user.family_name;
|
|
94
|
+
}
|
|
95
|
+
return this.deriveDisplayNameFromUsername(user.name);
|
|
96
|
+
}, ...(ngDevMode ? [{ debugName: "displayName" }] : []));
|
|
66
97
|
constructor() {
|
|
67
98
|
console.debug("AuthorizeService::created");
|
|
68
99
|
this.oauthService.discoveryDocumentLoaded$.subscribe((_) => {
|
|
@@ -79,6 +110,7 @@ class AuthorizeService {
|
|
|
79
110
|
this._accessToken.set(null);
|
|
80
111
|
this._user.set(null);
|
|
81
112
|
this._isAuthenticated.set(false);
|
|
113
|
+
this._tokenTenantId.set(null);
|
|
82
114
|
// Reload the page to trigger the auth flow and redirect to login
|
|
83
115
|
this.reloadPage();
|
|
84
116
|
}
|
|
@@ -101,8 +133,13 @@ class AuthorizeService {
|
|
|
101
133
|
this._accessToken.set(null);
|
|
102
134
|
this._user.set(null);
|
|
103
135
|
this._isAuthenticated.set(false);
|
|
104
|
-
|
|
105
|
-
this.
|
|
136
|
+
this._tokenTenantId.set(null);
|
|
137
|
+
this._allowedTenants.set([]);
|
|
138
|
+
// Do NOT call reloadPage() here — oauthService.logOut() already
|
|
139
|
+
// redirects to the Identity Server's end_session_endpoint.
|
|
140
|
+
// Calling reload() would race with that redirect and cause the
|
|
141
|
+
// page to reload before the server-side session is terminated,
|
|
142
|
+
// leaving the user still logged in.
|
|
106
143
|
}
|
|
107
144
|
});
|
|
108
145
|
// Listen for storage events from other tabs (e.g., SLO logout callback)
|
|
@@ -116,6 +153,7 @@ class AuthorizeService {
|
|
|
116
153
|
this._accessToken.set(null);
|
|
117
154
|
this._user.set(null);
|
|
118
155
|
this._isAuthenticated.set(false);
|
|
156
|
+
this._tokenTenantId.set(null);
|
|
119
157
|
// Reload the page to trigger the auth flow and redirect to login
|
|
120
158
|
this.reloadPage();
|
|
121
159
|
}
|
|
@@ -132,6 +170,7 @@ class AuthorizeService {
|
|
|
132
170
|
this._accessToken.set(null);
|
|
133
171
|
this._user.set(null);
|
|
134
172
|
this._isAuthenticated.set(false);
|
|
173
|
+
this._tokenTenantId.set(null);
|
|
135
174
|
this.reloadPage();
|
|
136
175
|
}
|
|
137
176
|
};
|
|
@@ -161,17 +200,182 @@ class AuthorizeService {
|
|
|
161
200
|
getAccessTokenSync() {
|
|
162
201
|
return this._accessToken();
|
|
163
202
|
}
|
|
203
|
+
/**
|
|
204
|
+
* Checks if the user is allowed to access the specified tenant.
|
|
205
|
+
* Returns true if no allowed_tenants claims are present (backwards compatibility).
|
|
206
|
+
*/
|
|
207
|
+
isTenantAllowed(tenantId) {
|
|
208
|
+
const allowed = this._allowedTenants();
|
|
209
|
+
if (allowed.length === 0) {
|
|
210
|
+
return true; // No claims = backwards compatible (old tokens)
|
|
211
|
+
}
|
|
212
|
+
return allowed.some(t => t.toLowerCase() === tenantId.toLowerCase());
|
|
213
|
+
}
|
|
164
214
|
/**
|
|
165
215
|
* Initiates the login flow.
|
|
216
|
+
* @param tenantId Optional tenant ID. When provided, includes acr_values=tenant:{tenantId}
|
|
217
|
+
* so the identity server redirects to the correct tenant's login page.
|
|
166
218
|
*/
|
|
167
|
-
login() {
|
|
168
|
-
this.
|
|
219
|
+
login(tenantId) {
|
|
220
|
+
const effectiveTenantId = tenantId ?? this.authorizeOptions?.defaultTenantId;
|
|
221
|
+
if (effectiveTenantId) {
|
|
222
|
+
this.oauthService.initImplicitFlow('', { acr_values: `tenant:${effectiveTenantId}` });
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
this.oauthService.initImplicitFlow();
|
|
226
|
+
}
|
|
169
227
|
}
|
|
170
228
|
/**
|
|
171
|
-
*
|
|
229
|
+
* Forces re-authentication for a different tenant by clearing the local
|
|
230
|
+
* OAuth session and reloading the page. On reload, the guard will see
|
|
231
|
+
* isAuthenticated=false and call login(tenantId) with the correct acr_values.
|
|
232
|
+
*
|
|
233
|
+
* This is used when the current token's tenant_id does not match the
|
|
234
|
+
* route's tenantId (e.g., navigating from octosystem to meshtest).
|
|
235
|
+
*/
|
|
236
|
+
/**
|
|
237
|
+
* Returns true if the switch was initiated, false if skipped (loop prevention).
|
|
238
|
+
*/
|
|
239
|
+
switchTenant(targetTenantId, targetUrl) {
|
|
240
|
+
console.debug(`AuthorizeService::switchTenant to "${targetTenantId}" at "${targetUrl}"`);
|
|
241
|
+
// Prevent infinite redirect loop: if we already attempted a switch to this
|
|
242
|
+
// exact tenant and ended up here again, the Identity Server did not issue
|
|
243
|
+
// a token for the target tenant. Do NOT attempt again.
|
|
244
|
+
try {
|
|
245
|
+
const previousAttempt = sessionStorage.getItem(AuthorizeService.TENANT_SWITCH_ATTEMPTED_KEY);
|
|
246
|
+
if (previousAttempt && previousAttempt.toLowerCase() === targetTenantId.toLowerCase()) {
|
|
247
|
+
console.warn(`AuthorizeService::switchTenant — already attempted switch to "${targetTenantId}", skipping to prevent loop`);
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
// sessionStorage may be unavailable
|
|
253
|
+
}
|
|
254
|
+
// Store target tenant so login() uses the correct acr_values after reload
|
|
255
|
+
try {
|
|
256
|
+
sessionStorage.setItem(AuthorizeService.TENANT_REAUTH_KEY, targetTenantId);
|
|
257
|
+
sessionStorage.setItem(AuthorizeService.TENANT_SWITCH_ATTEMPTED_KEY, targetTenantId);
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
// sessionStorage may be unavailable
|
|
261
|
+
}
|
|
262
|
+
// Clear local OAuth tokens directly from storage.
|
|
263
|
+
// Do NOT use oauthService.logOut() — it fires a 'logout' event
|
|
264
|
+
// whose handler calls reloadPage(), racing with our navigation below.
|
|
265
|
+
localStorage.removeItem('access_token');
|
|
266
|
+
localStorage.removeItem('id_token');
|
|
267
|
+
localStorage.removeItem('refresh_token');
|
|
268
|
+
localStorage.removeItem('id_token_claims_obj');
|
|
269
|
+
localStorage.removeItem('id_token_expires_at');
|
|
270
|
+
localStorage.removeItem('id_token_stored_at');
|
|
271
|
+
localStorage.removeItem('access_token_stored_at');
|
|
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');
|
|
278
|
+
// Clear our signals
|
|
279
|
+
this._accessToken.set(null);
|
|
280
|
+
this._user.set(null);
|
|
281
|
+
this._isAuthenticated.set(false);
|
|
282
|
+
this._tokenTenantId.set(null);
|
|
283
|
+
// Full page navigation to the target URL — triggers fresh auth flow
|
|
284
|
+
window.location.href = targetUrl;
|
|
285
|
+
return true;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Returns the pending tenant switch target (if any) and clears it.
|
|
289
|
+
* Called by the guard to pass the correct tenantId to login() after a switchTenant reload.
|
|
290
|
+
*/
|
|
291
|
+
consumePendingTenantSwitch() {
|
|
292
|
+
try {
|
|
293
|
+
const tenantId = sessionStorage.getItem(AuthorizeService.TENANT_REAUTH_KEY);
|
|
294
|
+
sessionStorage.removeItem(AuthorizeService.TENANT_REAUTH_KEY);
|
|
295
|
+
return tenantId;
|
|
296
|
+
}
|
|
297
|
+
catch {
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Returns the tenant for which a switch was already attempted (if any) and clears it.
|
|
303
|
+
* Used by the guard to prevent infinite redirect loops: if the Identity Server
|
|
304
|
+
* issues a token for the wrong tenant even after a switch, we skip the second attempt.
|
|
305
|
+
*/
|
|
306
|
+
consumeSwitchAttempted() {
|
|
307
|
+
try {
|
|
308
|
+
const tenantId = sessionStorage.getItem(AuthorizeService.TENANT_SWITCH_ATTEMPTED_KEY);
|
|
309
|
+
sessionStorage.removeItem(AuthorizeService.TENANT_SWITCH_ATTEMPTED_KEY);
|
|
310
|
+
return tenantId;
|
|
311
|
+
}
|
|
312
|
+
catch {
|
|
313
|
+
return null;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Logs out the current user by redirecting to the Identity Server's
|
|
318
|
+
* OIDC end_session_endpoint for proper Single Logout (SLO).
|
|
319
|
+
*
|
|
320
|
+
* We cannot rely on oauthService.logOut() for the redirect because it calls
|
|
321
|
+
* clearStorage() which may clear internal state before the redirect happens.
|
|
322
|
+
* Instead, we capture the logoutUrl and id_token first, then clear state,
|
|
323
|
+
* then manually redirect to the end_session_endpoint.
|
|
172
324
|
*/
|
|
173
325
|
logout() {
|
|
174
|
-
|
|
326
|
+
// Read the end_session_endpoint (stored as logoutUrl on the service) and id_token BEFORE clearing storage
|
|
327
|
+
const endSessionEndpoint = this.oauthService.logoutUrl;
|
|
328
|
+
const idToken = this.oauthService.getIdToken();
|
|
329
|
+
const postLogoutRedirectUri = this.lastAuthConfig?.postLogoutRedirectUri;
|
|
330
|
+
// Clear local OAuth state (tokens, discovery doc, etc.)
|
|
331
|
+
this.oauthService.logOut(true); // true = noRedirectToLogoutUrl (we redirect manually)
|
|
332
|
+
if (endSessionEndpoint) {
|
|
333
|
+
// Build the end_session URL with id_token_hint and post_logout_redirect_uri
|
|
334
|
+
const params = new URLSearchParams();
|
|
335
|
+
if (idToken) {
|
|
336
|
+
params.set('id_token_hint', idToken);
|
|
337
|
+
}
|
|
338
|
+
if (postLogoutRedirectUri) {
|
|
339
|
+
params.set('post_logout_redirect_uri', postLogoutRedirectUri);
|
|
340
|
+
}
|
|
341
|
+
window.location.href = `${endSessionEndpoint}?${params.toString()}`;
|
|
342
|
+
}
|
|
343
|
+
else {
|
|
344
|
+
// Fallback: no end_session_endpoint available, just reload
|
|
345
|
+
this.reloadPage();
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Updates the redirect URIs without performing a full re-initialization.
|
|
350
|
+
* Use this when the OAuth session is already established and only the
|
|
351
|
+
* redirect targets need to change (e.g., when switching tenants).
|
|
352
|
+
*/
|
|
353
|
+
updateRedirectUris(redirectUri, postLogoutRedirectUri) {
|
|
354
|
+
if (this.authorizeOptions) {
|
|
355
|
+
this.authorizeOptions.redirectUri = redirectUri;
|
|
356
|
+
this.authorizeOptions.postLogoutRedirectUri = postLogoutRedirectUri;
|
|
357
|
+
}
|
|
358
|
+
if (this.lastAuthConfig) {
|
|
359
|
+
this.lastAuthConfig.redirectUri = redirectUri;
|
|
360
|
+
this.lastAuthConfig.postLogoutRedirectUri = postLogoutRedirectUri;
|
|
361
|
+
}
|
|
362
|
+
// Update the redirect URIs directly on the OAuthService without calling
|
|
363
|
+
// configure(), because configure() does Object.assign(this, new AuthConfig(), config)
|
|
364
|
+
// which resets ALL properties — including logoutUrl, tokenEndpoint, and other
|
|
365
|
+
// discovery document endpoints — back to their AuthConfig defaults (empty).
|
|
366
|
+
this.oauthService.redirectUri = redirectUri;
|
|
367
|
+
this.oauthService.postLogoutRedirectUri = postLogoutRedirectUri;
|
|
368
|
+
console.debug("AuthorizeService::updateRedirectUris::done");
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Refreshes the access token and updates the allowed tenants signal.
|
|
372
|
+
* Call this after actions that change the user's tenant access (e.g., provisioning).
|
|
373
|
+
*/
|
|
374
|
+
async refreshAccessToken() {
|
|
375
|
+
console.debug("AuthorizeService::refreshAccessToken::started");
|
|
376
|
+
await this.oauthService.refreshToken();
|
|
377
|
+
await this.loadUserAsync();
|
|
378
|
+
console.debug("AuthorizeService::refreshAccessToken::done");
|
|
175
379
|
}
|
|
176
380
|
/**
|
|
177
381
|
* Initializes the authorization service with the specified options.
|
|
@@ -201,6 +405,7 @@ class AuthorizeService {
|
|
|
201
405
|
preserveRequestedRoute: true
|
|
202
406
|
};
|
|
203
407
|
this.authorizeOptions = authorizeOptions;
|
|
408
|
+
this.lastAuthConfig = config;
|
|
204
409
|
this.oauthService.setStorage(localStorage);
|
|
205
410
|
this.oauthService.configure(config);
|
|
206
411
|
console.debug("AuthorizeService::initialize::loadingDiscoveryDocumentAndTryLogin");
|
|
@@ -238,6 +443,12 @@ class AuthorizeService {
|
|
|
238
443
|
this._isInitializing.set(true);
|
|
239
444
|
this.oauthService.stopAutomaticRefresh();
|
|
240
445
|
this.authorizeOptions = null;
|
|
446
|
+
this.lastAuthConfig = null;
|
|
447
|
+
// Note: Do NOT clear auth signals (_accessToken, _isAuthenticated, etc.) here.
|
|
448
|
+
// The access token and user info are globally valid (not per-tenant) and remain
|
|
449
|
+
// valid during re-initialization. Clearing them creates a window where the HTTP
|
|
450
|
+
// interceptor sends requests without a Bearer token, causing 401 errors.
|
|
451
|
+
// Signals are already properly cleared on logout/session_terminated events.
|
|
241
452
|
this._isInitialized.set(false);
|
|
242
453
|
console.debug("AuthorizeService::uninitialize::done");
|
|
243
454
|
}
|
|
@@ -253,19 +464,102 @@ class AuthorizeService {
|
|
|
253
464
|
return;
|
|
254
465
|
}
|
|
255
466
|
const user = claims;
|
|
256
|
-
if (user.
|
|
257
|
-
|
|
258
|
-
this._userInitials.set(initials);
|
|
467
|
+
if (user.given_name && user.family_name) {
|
|
468
|
+
this._userInitials.set(user.given_name.charAt(0).toUpperCase() + user.family_name.charAt(0).toUpperCase());
|
|
259
469
|
}
|
|
260
470
|
else {
|
|
261
|
-
this.
|
|
471
|
+
const derived = this.deriveDisplayNameFromUsername(user.name);
|
|
472
|
+
this._userInitials.set(this.deriveInitials(derived));
|
|
262
473
|
}
|
|
263
474
|
const accessToken = this.oauthService.getAccessToken();
|
|
264
475
|
this._user.set(user);
|
|
265
476
|
this._accessToken.set(accessToken);
|
|
266
477
|
this._isAuthenticated.set(true);
|
|
267
478
|
this._sessionLoading.set(false);
|
|
268
|
-
|
|
479
|
+
// Parse allowed_tenants from the access token
|
|
480
|
+
this._allowedTenants.set(this.parseAllowedTenantsFromToken(accessToken));
|
|
481
|
+
// Parse tenant_id from the access token (used for tenant mismatch detection)
|
|
482
|
+
const tokenTenantId = this.parseTenantIdFromToken(accessToken);
|
|
483
|
+
this._tokenTenantId.set(tokenTenantId);
|
|
484
|
+
console.debug(`AuthorizeService::loadUserAsync::done (tokenTenantId="${tokenTenantId}", allowedTenants=${JSON.stringify(this._allowedTenants())})`);
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Decodes the JWT access token payload and extracts allowed_tenants claims.
|
|
488
|
+
* The claim can be a single string or an array of strings.
|
|
489
|
+
*/
|
|
490
|
+
parseAllowedTenantsFromToken(accessToken) {
|
|
491
|
+
if (!accessToken) {
|
|
492
|
+
return [];
|
|
493
|
+
}
|
|
494
|
+
try {
|
|
495
|
+
const parts = accessToken.split('.');
|
|
496
|
+
if (parts.length !== 3) {
|
|
497
|
+
return [];
|
|
498
|
+
}
|
|
499
|
+
const base64 = parts[1].replace(/-/g, '+').replace(/_/g, '/');
|
|
500
|
+
const payload = JSON.parse(atob(base64));
|
|
501
|
+
const allowedTenants = payload['allowed_tenants'];
|
|
502
|
+
if (!allowedTenants) {
|
|
503
|
+
return [];
|
|
504
|
+
}
|
|
505
|
+
if (Array.isArray(allowedTenants)) {
|
|
506
|
+
return allowedTenants;
|
|
507
|
+
}
|
|
508
|
+
// Single value claim
|
|
509
|
+
if (typeof allowedTenants === 'string') {
|
|
510
|
+
return [allowedTenants];
|
|
511
|
+
}
|
|
512
|
+
return [];
|
|
513
|
+
}
|
|
514
|
+
catch (e) {
|
|
515
|
+
console.warn('Failed to parse allowed_tenants from access token', e);
|
|
516
|
+
return [];
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Decodes the JWT access token payload and extracts the tenant_id claim.
|
|
521
|
+
*/
|
|
522
|
+
parseTenantIdFromToken(accessToken) {
|
|
523
|
+
if (!accessToken) {
|
|
524
|
+
return null;
|
|
525
|
+
}
|
|
526
|
+
try {
|
|
527
|
+
const parts = accessToken.split('.');
|
|
528
|
+
if (parts.length !== 3) {
|
|
529
|
+
return null;
|
|
530
|
+
}
|
|
531
|
+
const payload = JSON.parse(atob(parts[1]));
|
|
532
|
+
return payload['tenant_id'] ?? null;
|
|
533
|
+
}
|
|
534
|
+
catch {
|
|
535
|
+
return null;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
deriveDisplayNameFromUsername(username) {
|
|
539
|
+
let name = username;
|
|
540
|
+
// Strip xt_{tenantId}_ prefix
|
|
541
|
+
const xtMatch = name.match(/^xt_[^_]+_(.+)$/);
|
|
542
|
+
if (xtMatch) {
|
|
543
|
+
name = xtMatch[1];
|
|
544
|
+
}
|
|
545
|
+
// Extract local part of email
|
|
546
|
+
const atIndex = name.indexOf('@');
|
|
547
|
+
if (atIndex > 0) {
|
|
548
|
+
name = name.substring(0, atIndex);
|
|
549
|
+
}
|
|
550
|
+
// Split by dots and capitalize
|
|
551
|
+
const parts = name.split('.').filter(p => p.length > 0);
|
|
552
|
+
return parts.map(p => p.charAt(0).toUpperCase() + p.slice(1)).join(' ');
|
|
553
|
+
}
|
|
554
|
+
deriveInitials(displayName) {
|
|
555
|
+
const words = displayName.split(' ').filter(w => w.length > 0);
|
|
556
|
+
if (words.length >= 2) {
|
|
557
|
+
return words[0].charAt(0).toUpperCase() + words[1].charAt(0).toUpperCase();
|
|
558
|
+
}
|
|
559
|
+
if (words.length === 1 && words[0].length >= 2) {
|
|
560
|
+
return words[0].charAt(0).toUpperCase() + words[0].charAt(1).toLowerCase();
|
|
561
|
+
}
|
|
562
|
+
return '??';
|
|
269
563
|
}
|
|
270
564
|
/**
|
|
271
565
|
* Reloads the page. This method is protected to allow mocking in tests.
|
|
@@ -367,9 +661,23 @@ const authorizeInterceptor = (req, next) => {
|
|
|
367
661
|
return next(req);
|
|
368
662
|
};
|
|
369
663
|
|
|
664
|
+
/**
|
|
665
|
+
* Walks up the route tree to find the tenantId parameter.
|
|
666
|
+
*/
|
|
667
|
+
function findRouteTenantId(route) {
|
|
668
|
+
let current = route;
|
|
669
|
+
while (current) {
|
|
670
|
+
if (current.params?.['tenantId']) {
|
|
671
|
+
return current.params['tenantId'];
|
|
672
|
+
}
|
|
673
|
+
current = current.parent;
|
|
674
|
+
}
|
|
675
|
+
return undefined;
|
|
676
|
+
}
|
|
370
677
|
/**
|
|
371
678
|
* Handles authorization check for route activation.
|
|
372
679
|
* Redirects to login if not authenticated, or to home if user lacks required roles.
|
|
680
|
+
* Forces re-authentication when the token's tenant_id does not match the route's tenantId.
|
|
373
681
|
*
|
|
374
682
|
* @param route - The activated route snapshot containing route data
|
|
375
683
|
* @returns true if authorized, false otherwise
|
|
@@ -383,20 +691,58 @@ const authorizeInterceptor = (req, next) => {
|
|
|
383
691
|
* { path: 'admin', component: AdminComponent, canActivate: [authorizeGuard], data: { roles: ['AdminPanelManagement'] } }
|
|
384
692
|
* ```
|
|
385
693
|
*/
|
|
386
|
-
const authorizeGuard = async (route) => {
|
|
694
|
+
const authorizeGuard = async (route, state) => {
|
|
387
695
|
const authorizeService = inject(AuthorizeService);
|
|
388
696
|
const router = inject(Router);
|
|
389
697
|
// Use signal directly (synchronous)
|
|
390
698
|
const isAuthenticated = authorizeService.isAuthenticated();
|
|
391
699
|
if (!isAuthenticated) {
|
|
392
|
-
|
|
700
|
+
// Check if this is a reload after switchTenant — use the stored tenantId
|
|
701
|
+
const pendingTenant = authorizeService.consumePendingTenantSwitch();
|
|
702
|
+
const tenantId = pendingTenant ?? findRouteTenantId(route);
|
|
703
|
+
authorizeService.login(tenantId);
|
|
393
704
|
return false;
|
|
394
705
|
}
|
|
706
|
+
// Force re-authentication if the token was issued for a different tenant.
|
|
707
|
+
// switchTenant clears the local session and reloads, so the next guard
|
|
708
|
+
// invocation enters the !isAuthenticated branch above with the correct tenantId.
|
|
709
|
+
const tokenTenantId = authorizeService.tokenTenantId();
|
|
710
|
+
const routeTenantId = findRouteTenantId(route);
|
|
711
|
+
if (tokenTenantId && routeTenantId && routeTenantId.toLowerCase() !== tokenTenantId.toLowerCase()) {
|
|
712
|
+
console.debug(`[AuthGuard] Tenant mismatch: token="${tokenTenantId}", route="${routeTenantId}" — attempting tenant switch`);
|
|
713
|
+
// switchTenant returns false if a switch was already attempted (loop prevention).
|
|
714
|
+
// Use state.url (the router's target URL) — not window.location.href which is
|
|
715
|
+
// still the current page during in-app navigation (e.g., tenant switcher dropdown).
|
|
716
|
+
const targetUrl = window.location.origin + state.url;
|
|
717
|
+
if (authorizeService.switchTenant(routeTenantId, targetUrl)) {
|
|
718
|
+
return false;
|
|
719
|
+
}
|
|
720
|
+
// Switch was skipped — fall through to role-based checks
|
|
721
|
+
console.warn(`[AuthGuard] Tenant mismatch persists after switch attempt (token="${tokenTenantId}", route="${routeTenantId}"). Proceeding with current token.`);
|
|
722
|
+
}
|
|
723
|
+
else {
|
|
724
|
+
// No mismatch — clear any leftover switch-attempted flag
|
|
725
|
+
authorizeService.consumeSwitchAttempted();
|
|
726
|
+
}
|
|
395
727
|
// Use roles signal directly (synchronous)
|
|
396
728
|
const userRoles = authorizeService.roles();
|
|
397
729
|
const requiredRoles = route.data['roles'];
|
|
398
|
-
if (requiredRoles
|
|
399
|
-
|
|
730
|
+
if (requiredRoles === undefined || requiredRoles === null) {
|
|
731
|
+
if (typeof ngDevMode === 'undefined' || ngDevMode) {
|
|
732
|
+
console.warn(`[AuthGuard] Route "${route.routeConfig?.path}" has no required roles defined — access granted to any authenticated user.`);
|
|
733
|
+
}
|
|
734
|
+
return true;
|
|
735
|
+
}
|
|
736
|
+
// Empty roles array (roles: []) means intentionally open to any authenticated user
|
|
737
|
+
if (requiredRoles.length === 0) {
|
|
738
|
+
return true;
|
|
739
|
+
}
|
|
740
|
+
if (!requiredRoles.some(role => userRoles.includes(role))) {
|
|
741
|
+
// Navigate to the current tenant's home, not root ''.
|
|
742
|
+
// Navigating to '' would redirect to a default tenant (e.g., octosystem),
|
|
743
|
+
// causing a tenant mismatch → switchTenant → redirect loop.
|
|
744
|
+
const tenantHome = routeTenantId ? `/${routeTenantId}` : '';
|
|
745
|
+
await router.navigate([tenantHome]);
|
|
400
746
|
return false;
|
|
401
747
|
}
|
|
402
748
|
return true;
|
|
@@ -429,12 +775,14 @@ const authorizeChildGuard = authorizeGuard;
|
|
|
429
775
|
* }
|
|
430
776
|
* ```
|
|
431
777
|
*/
|
|
432
|
-
const authorizeMatchGuard = () => {
|
|
778
|
+
const authorizeMatchGuard = (_route, segments) => {
|
|
433
779
|
const authorizeService = inject(AuthorizeService);
|
|
434
780
|
// Use signal directly (synchronous)
|
|
435
781
|
const isAuthenticated = authorizeService.isAuthenticated();
|
|
436
782
|
if (!isAuthenticated) {
|
|
437
|
-
|
|
783
|
+
// The first URL segment is typically the tenantId (e.g., /:tenantId/...)
|
|
784
|
+
const tenantId = segments.length > 0 ? segments[0].path : undefined;
|
|
785
|
+
authorizeService.login(tenantId);
|
|
438
786
|
return false;
|
|
439
787
|
}
|
|
440
788
|
return true;
|
|
@@ -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}\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\n private authorizeOptions: AuthorizeOptions | 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 * Computed signal containing the user's roles.\n */\n readonly roles: Signal<string[]> = computed(() => this._user()?.role ?? []);\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 // 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 // Reload the page to trigger the auth flow and redirect to login\n this.reloadPage();\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 // 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.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 * Initiates the login flow.\n */\n public login(): void {\n this.oauthService.initImplicitFlow();\n }\n\n /**\n * Logs out the current user.\n */\n public logout(): void {\n this.oauthService.logOut(false);\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\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\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.family_name && user.given_name) {\n const initials = user.given_name.charAt(0) + user.family_name.charAt(0);\n this._userInitials.set(initials);\n } else {\n this._userInitials.set(user.name.charAt(0) + user.name.charAt(1));\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 console.debug(\"AuthorizeService::loadUserAsync::done\");\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 } from '@angular/router';\nimport { AuthorizeService } from './authorize.service';\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 *\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) => {\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 authorizeService.login();\n return false;\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 && !requiredRoles.some(role => userRoles.includes(role))) {\n await router.navigate(['']);\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 = () => {\n const authorizeService = inject(AuthorizeService);\n\n // Use signal directly (synchronous)\n const isAuthenticated = authorizeService.isAuthenticated();\n\n if (!isAuthenticated) {\n authorizeService.login();\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/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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"}
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { OnInit, Signal,
|
|
2
|
+
import { OnInit, Signal, EventEmitter } from '@angular/core';
|
|
3
3
|
import { AuthorizeService } from '@meshmakers/shared-auth';
|
|
4
4
|
|
|
5
5
|
declare class LoginAppBarSectionComponent implements OnInit {
|
|
@@ -12,17 +12,19 @@ declare class LoginAppBarSectionComponent implements OnInit {
|
|
|
12
12
|
*/
|
|
13
13
|
protected readonly userName: Signal<string | null>;
|
|
14
14
|
/**
|
|
15
|
-
* Computed signal for the user's
|
|
15
|
+
* Computed signal for the user's display name.
|
|
16
|
+
* Delegates to AuthorizeService which handles cross-tenant username derivation.
|
|
16
17
|
*/
|
|
17
18
|
protected readonly fullName: Signal<string | null>;
|
|
18
19
|
/**
|
|
19
|
-
*
|
|
20
|
+
* Computed signal for the profile management URI.
|
|
21
|
+
* Uses the tenant_id from the access token to build the correct URL.
|
|
20
22
|
*/
|
|
21
|
-
protected readonly profileUri:
|
|
23
|
+
protected readonly profileUri: Signal<string | null>;
|
|
22
24
|
private anchor;
|
|
23
25
|
private popup;
|
|
24
26
|
constructor();
|
|
25
|
-
ngOnInit():
|
|
27
|
+
ngOnInit(): void;
|
|
26
28
|
get register(): EventEmitter<void>;
|
|
27
29
|
get showPopup(): boolean;
|
|
28
30
|
set showPopup(value: boolean);
|
|
@@ -32,6 +32,7 @@ declare class AuthorizeOptions {
|
|
|
32
32
|
scope?: string;
|
|
33
33
|
showDebugInformation?: boolean;
|
|
34
34
|
sessionChecksEnabled?: boolean;
|
|
35
|
+
defaultTenantId?: string;
|
|
35
36
|
}
|
|
36
37
|
declare class AuthorizeService {
|
|
37
38
|
private readonly oauthService;
|
|
@@ -43,7 +44,12 @@ declare class AuthorizeService {
|
|
|
43
44
|
private readonly _isInitialized;
|
|
44
45
|
private readonly _isInitializing;
|
|
45
46
|
private readonly _sessionLoading;
|
|
47
|
+
private readonly _allowedTenants;
|
|
48
|
+
private readonly _tokenTenantId;
|
|
49
|
+
private static readonly TENANT_REAUTH_KEY;
|
|
50
|
+
private static readonly TENANT_SWITCH_ATTEMPTED_KEY;
|
|
46
51
|
private authorizeOptions;
|
|
52
|
+
private lastAuthConfig;
|
|
47
53
|
/**
|
|
48
54
|
* Signal indicating whether the user is currently authenticated.
|
|
49
55
|
*/
|
|
@@ -68,10 +74,25 @@ declare class AuthorizeService {
|
|
|
68
74
|
* Signal indicating whether the session is currently loading.
|
|
69
75
|
*/
|
|
70
76
|
readonly sessionLoading: Signal<boolean>;
|
|
77
|
+
/**
|
|
78
|
+
* Signal containing the list of tenants the user is allowed to access.
|
|
79
|
+
* Parsed from the allowed_tenants claims in the access token.
|
|
80
|
+
*/
|
|
81
|
+
readonly allowedTenants: Signal<string[]>;
|
|
82
|
+
/**
|
|
83
|
+
* Signal containing the tenant_id claim from the current access token.
|
|
84
|
+
* Used to detect tenant mismatch when navigating between tenants.
|
|
85
|
+
*/
|
|
86
|
+
readonly tokenTenantId: Signal<string | null>;
|
|
71
87
|
/**
|
|
72
88
|
* Computed signal containing the user's roles.
|
|
73
89
|
*/
|
|
74
90
|
readonly roles: Signal<string[]>;
|
|
91
|
+
/**
|
|
92
|
+
* Computed signal for the user's display name.
|
|
93
|
+
* Uses given_name + family_name if available, otherwise derives from the username.
|
|
94
|
+
*/
|
|
95
|
+
readonly displayName: Signal<string | null>;
|
|
75
96
|
constructor();
|
|
76
97
|
/**
|
|
77
98
|
* Checks if the current user has the specified role.
|
|
@@ -88,14 +109,61 @@ declare class AuthorizeService {
|
|
|
88
109
|
* @returns The current access token or null if not authenticated
|
|
89
110
|
*/
|
|
90
111
|
getAccessTokenSync(): string | null;
|
|
112
|
+
/**
|
|
113
|
+
* Checks if the user is allowed to access the specified tenant.
|
|
114
|
+
* Returns true if no allowed_tenants claims are present (backwards compatibility).
|
|
115
|
+
*/
|
|
116
|
+
isTenantAllowed(tenantId: string): boolean;
|
|
91
117
|
/**
|
|
92
118
|
* Initiates the login flow.
|
|
119
|
+
* @param tenantId Optional tenant ID. When provided, includes acr_values=tenant:{tenantId}
|
|
120
|
+
* so the identity server redirects to the correct tenant's login page.
|
|
121
|
+
*/
|
|
122
|
+
login(tenantId?: string): void;
|
|
123
|
+
/**
|
|
124
|
+
* Forces re-authentication for a different tenant by clearing the local
|
|
125
|
+
* OAuth session and reloading the page. On reload, the guard will see
|
|
126
|
+
* isAuthenticated=false and call login(tenantId) with the correct acr_values.
|
|
127
|
+
*
|
|
128
|
+
* This is used when the current token's tenant_id does not match the
|
|
129
|
+
* route's tenantId (e.g., navigating from octosystem to meshtest).
|
|
130
|
+
*/
|
|
131
|
+
/**
|
|
132
|
+
* Returns true if the switch was initiated, false if skipped (loop prevention).
|
|
93
133
|
*/
|
|
94
|
-
|
|
134
|
+
switchTenant(targetTenantId: string, targetUrl: string): boolean;
|
|
95
135
|
/**
|
|
96
|
-
*
|
|
136
|
+
* Returns the pending tenant switch target (if any) and clears it.
|
|
137
|
+
* Called by the guard to pass the correct tenantId to login() after a switchTenant reload.
|
|
138
|
+
*/
|
|
139
|
+
consumePendingTenantSwitch(): string | null;
|
|
140
|
+
/**
|
|
141
|
+
* Returns the tenant for which a switch was already attempted (if any) and clears it.
|
|
142
|
+
* Used by the guard to prevent infinite redirect loops: if the Identity Server
|
|
143
|
+
* issues a token for the wrong tenant even after a switch, we skip the second attempt.
|
|
144
|
+
*/
|
|
145
|
+
consumeSwitchAttempted(): string | null;
|
|
146
|
+
/**
|
|
147
|
+
* Logs out the current user by redirecting to the Identity Server's
|
|
148
|
+
* OIDC end_session_endpoint for proper Single Logout (SLO).
|
|
149
|
+
*
|
|
150
|
+
* We cannot rely on oauthService.logOut() for the redirect because it calls
|
|
151
|
+
* clearStorage() which may clear internal state before the redirect happens.
|
|
152
|
+
* Instead, we capture the logoutUrl and id_token first, then clear state,
|
|
153
|
+
* then manually redirect to the end_session_endpoint.
|
|
97
154
|
*/
|
|
98
155
|
logout(): void;
|
|
156
|
+
/**
|
|
157
|
+
* Updates the redirect URIs without performing a full re-initialization.
|
|
158
|
+
* Use this when the OAuth session is already established and only the
|
|
159
|
+
* redirect targets need to change (e.g., when switching tenants).
|
|
160
|
+
*/
|
|
161
|
+
updateRedirectUris(redirectUri: string, postLogoutRedirectUri: string): void;
|
|
162
|
+
/**
|
|
163
|
+
* Refreshes the access token and updates the allowed tenants signal.
|
|
164
|
+
* Call this after actions that change the user's tenant access (e.g., provisioning).
|
|
165
|
+
*/
|
|
166
|
+
refreshAccessToken(): Promise<void>;
|
|
99
167
|
/**
|
|
100
168
|
* Initializes the authorization service with the specified options.
|
|
101
169
|
*/
|
|
@@ -105,6 +173,17 @@ declare class AuthorizeService {
|
|
|
105
173
|
*/
|
|
106
174
|
uninitialize(): Promise<void>;
|
|
107
175
|
private loadUserAsync;
|
|
176
|
+
/**
|
|
177
|
+
* Decodes the JWT access token payload and extracts allowed_tenants claims.
|
|
178
|
+
* The claim can be a single string or an array of strings.
|
|
179
|
+
*/
|
|
180
|
+
private parseAllowedTenantsFromToken;
|
|
181
|
+
/**
|
|
182
|
+
* Decodes the JWT access token payload and extracts the tenant_id claim.
|
|
183
|
+
*/
|
|
184
|
+
private parseTenantIdFromToken;
|
|
185
|
+
private deriveDisplayNameFromUsername;
|
|
186
|
+
private deriveInitials;
|
|
108
187
|
/**
|
|
109
188
|
* Reloads the page. This method is protected to allow mocking in tests.
|
|
110
189
|
* @internal
|
|
@@ -140,6 +219,7 @@ declare const authorizeInterceptor: HttpInterceptorFn;
|
|
|
140
219
|
/**
|
|
141
220
|
* Handles authorization check for route activation.
|
|
142
221
|
* Redirects to login if not authenticated, or to home if user lacks required roles.
|
|
222
|
+
* Forces re-authentication when the token's tenant_id does not match the route's tenantId.
|
|
143
223
|
*
|
|
144
224
|
* @param route - The activated route snapshot containing route data
|
|
145
225
|
* @returns true if authorized, false otherwise
|