@meshmakers/shared-auth 3.3.34 → 3.3.390

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,14 +1,7 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, Injectable, Component, NgModule } from '@angular/core';
3
- import { BehaviorSubject, firstValueFrom } from 'rxjs';
4
- import { filter, map } from 'rxjs/operators';
5
- import * as i1$1 from 'angular-oauth2-oidc';
6
- import { OAuthService, OAuthModule } from 'angular-oauth2-oidc';
7
- import * as i1 from '@angular/common';
8
- import { CommonModule } from '@angular/common';
9
- import * as i2 from '@angular/router';
10
- import { Router, RouterLink } from '@angular/router';
11
- import { HttpClientModule } from '@angular/common/http';
2
+ import { inject, signal, computed, Injectable, makeEnvironmentProviders } from '@angular/core';
3
+ import { OAuthService, provideOAuthClient } from 'angular-oauth2-oidc';
4
+ import { Router } from '@angular/router';
12
5
 
13
6
  class AuthorizeOptions {
14
7
  wellKnownServiceUris;
@@ -24,20 +17,52 @@ class AuthorizeOptions {
24
17
  scope;
25
18
  showDebugInformation;
26
19
  sessionChecksEnabled;
27
- // Use popup flow for Office Add-Ins to avoid iframe issues
28
- usePopupFlow;
29
20
  }
30
21
  class AuthorizeService {
31
22
  oauthService = inject(OAuthService);
32
- _isAuthenticated = new BehaviorSubject(false);
33
- _issuer = new BehaviorSubject(null);
34
- _accessToken = new BehaviorSubject(null);
35
- _user = new BehaviorSubject(null);
36
- _userInitials = new BehaviorSubject(null);
37
- _isInitialized = new BehaviorSubject(false);
38
- _isInitializing = new BehaviorSubject(false);
39
- _sessionLoading = new BehaviorSubject(false);
23
+ // =============================================================================
24
+ // INTERNAL STATE (Writable Signals)
25
+ // =============================================================================
26
+ _isAuthenticated = signal(false, ...(ngDevMode ? [{ debugName: "_isAuthenticated" }] : []));
27
+ _issuer = signal(null, ...(ngDevMode ? [{ debugName: "_issuer" }] : []));
28
+ _accessToken = signal(null, ...(ngDevMode ? [{ debugName: "_accessToken" }] : []));
29
+ _user = signal(null, ...(ngDevMode ? [{ debugName: "_user" }] : []));
30
+ _userInitials = signal(null, ...(ngDevMode ? [{ debugName: "_userInitials" }] : []));
31
+ _isInitialized = signal(false, ...(ngDevMode ? [{ debugName: "_isInitialized" }] : []));
32
+ _isInitializing = signal(false, ...(ngDevMode ? [{ debugName: "_isInitializing" }] : []));
33
+ _sessionLoading = signal(false, ...(ngDevMode ? [{ debugName: "_sessionLoading" }] : []));
40
34
  authorizeOptions = null;
35
+ // =============================================================================
36
+ // PUBLIC API (Readonly Signals) - NEW API
37
+ // =============================================================================
38
+ /**
39
+ * Signal indicating whether the user is currently authenticated.
40
+ */
41
+ isAuthenticated = this._isAuthenticated.asReadonly();
42
+ /**
43
+ * Signal containing the issuer URL.
44
+ */
45
+ issuer = this._issuer.asReadonly();
46
+ /**
47
+ * Signal containing the current access token.
48
+ */
49
+ accessToken = this._accessToken.asReadonly();
50
+ /**
51
+ * Signal containing the current user information.
52
+ */
53
+ user = this._user.asReadonly();
54
+ /**
55
+ * Computed signal containing the user's initials (e.g., "JD" for John Doe).
56
+ */
57
+ userInitials = this._userInitials.asReadonly();
58
+ /**
59
+ * Signal indicating whether the session is currently loading.
60
+ */
61
+ sessionLoading = this._sessionLoading.asReadonly();
62
+ /**
63
+ * Computed signal containing the user's roles.
64
+ */
65
+ roles = computed(() => this._user()?.role ?? [], ...(ngDevMode ? [{ debugName: "roles" }] : []));
41
66
  constructor() {
42
67
  console.debug("AuthorizeService::created");
43
68
  this.oauthService.discoveryDocumentLoaded$.subscribe((_) => {
@@ -46,89 +71,122 @@ class AuthorizeService {
46
71
  this.oauthService.events.subscribe((e) => {
47
72
  console.debug("oauth/oidc event", e);
48
73
  });
49
- this.oauthService.events.pipe(filter((e) => e.type === "session_terminated")).subscribe((_) => {
50
- console.debug("Your session has been terminated!");
51
- this._accessToken.next(null);
52
- this._user.next(null);
53
- this._isAuthenticated.next(false);
54
- });
55
- this.oauthService.events.pipe(filter((e) => e.type === "token_received")).subscribe(async (_) => {
56
- await this.loadUserAsync();
74
+ this.oauthService.events
75
+ .pipe((source) => source)
76
+ .subscribe((e) => {
77
+ if (e.type === "session_terminated") {
78
+ console.debug("Your session has been terminated!");
79
+ this._accessToken.set(null);
80
+ this._user.set(null);
81
+ this._isAuthenticated.set(false);
82
+ // Reload the page to trigger the auth flow and redirect to login
83
+ this.reloadPage();
84
+ }
57
85
  });
58
- this.oauthService.events.pipe(filter((e) => e.type === "session_unchanged")).subscribe(async (_) => {
59
- if (this._user.value == null) {
86
+ this.oauthService.events.subscribe(async (e) => {
87
+ if (e.type === "token_received") {
60
88
  await this.loadUserAsync();
61
89
  }
62
90
  });
63
- this.oauthService.events.pipe(filter((e) => e.type === "logout")).subscribe((_) => {
64
- this._accessToken.next(null);
65
- this._user.next(null);
66
- this._isAuthenticated.next(false);
91
+ this.oauthService.events.subscribe(async (e) => {
92
+ if (e.type === "session_unchanged") {
93
+ if (this._user() == null) {
94
+ await this.loadUserAsync();
95
+ }
96
+ }
67
97
  });
98
+ this.oauthService.events.subscribe((e) => {
99
+ if (e.type === "logout") {
100
+ console.debug("AuthorizeService: Logout event received");
101
+ this._accessToken.set(null);
102
+ this._user.set(null);
103
+ this._isAuthenticated.set(false);
104
+ // Reload the page to trigger the auth flow and redirect to login
105
+ this.reloadPage();
106
+ }
107
+ });
108
+ // Listen for storage events from other tabs (e.g., SLO logout callback)
109
+ // This enables immediate cross-tab logout detection
110
+ window.addEventListener('storage', (event) => {
111
+ console.debug("AuthorizeService: Storage event received", event.key, event.newValue);
112
+ // Check if access_token was removed (logout in another tab)
113
+ // Note: OAuth library may set to empty string or null when clearing
114
+ if (event.key === 'access_token' && (event.newValue === null || event.newValue === '') && this._isAuthenticated()) {
115
+ console.debug("AuthorizeService: Access token removed in another tab - logging out and reloading");
116
+ this._accessToken.set(null);
117
+ this._user.set(null);
118
+ this._isAuthenticated.set(false);
119
+ // Reload the page to trigger the auth flow and redirect to login
120
+ this.reloadPage();
121
+ }
122
+ });
123
+ // Also listen for BroadcastChannel messages for cross-tab logout
124
+ // This is more reliable than storage events for iframe-based SLO
125
+ if (typeof BroadcastChannel !== 'undefined') {
126
+ console.debug("AuthorizeService: Setting up BroadcastChannel listener for 'octo-auth-logout'");
127
+ const logoutChannel = new BroadcastChannel('octo-auth-logout');
128
+ logoutChannel.onmessage = (event) => {
129
+ console.debug("AuthorizeService: BroadcastChannel message received", event.data);
130
+ if (event.data?.type === 'logout' && this._isAuthenticated()) {
131
+ console.debug("AuthorizeService: Logout broadcast received - reloading");
132
+ this._accessToken.set(null);
133
+ this._user.set(null);
134
+ this._isAuthenticated.set(false);
135
+ this.reloadPage();
136
+ }
137
+ };
138
+ }
139
+ else {
140
+ console.warn("AuthorizeService: BroadcastChannel not supported in this browser");
141
+ }
68
142
  }
143
+ /**
144
+ * Checks if the current user has the specified role.
145
+ */
69
146
  isInRole(role) {
70
- return this._user?.value?.role?.includes(role) ?? false;
71
- }
72
- getRoles() {
73
- return this.user.pipe(map((u) => (u?.role != null ? u.role : new Array())));
147
+ return this._user()?.role?.includes(role) ?? false;
74
148
  }
149
+ /**
150
+ * Gets the configured service URIs that should receive the authorization token.
151
+ */
75
152
  getServiceUris() {
76
153
  return this.authorizeOptions?.wellKnownServiceUris ?? null;
77
154
  }
78
- get issuer() {
79
- return this._issuer;
80
- }
81
- get isAuthenticated() {
82
- return this._isAuthenticated;
83
- }
84
- get sessionLoading() {
85
- return this._sessionLoading;
86
- }
87
- get accessToken() {
88
- return this._accessToken;
89
- }
90
- get user() {
91
- return this._user;
92
- }
93
- get userInitials() {
94
- return this._userInitials;
155
+ /**
156
+ * Gets the current access token synchronously.
157
+ * Use this for functional interceptors that need immediate access to the token.
158
+ *
159
+ * @returns The current access token or null if not authenticated
160
+ */
161
+ getAccessTokenSync() {
162
+ return this._accessToken();
95
163
  }
164
+ /**
165
+ * Initiates the login flow.
166
+ */
96
167
  login() {
97
- if (this.authorizeOptions?.usePopupFlow) {
98
- this.loginWithPopup();
99
- }
100
- else {
101
- this.oauthService.initImplicitFlow();
102
- }
103
- }
104
- loginWithPopup() {
105
- // Initiate login flow and get the authorization URL
106
- this.oauthService.initLoginFlow();
107
- // For popup flow, we need to handle the callback differently
108
- // The popup will redirect back, and we need to process the code
109
- window.addEventListener('storage', (e) => {
110
- if (e.key === 'oauth_code_received') {
111
- // Process the authentication after popup closes
112
- this.oauthService.tryLoginCodeFlow().then(() => {
113
- localStorage.removeItem('oauth_code_received');
114
- });
115
- }
116
- });
168
+ this.oauthService.initImplicitFlow();
117
169
  }
170
+ /**
171
+ * Logs out the current user.
172
+ */
118
173
  logout() {
119
174
  this.oauthService.logOut(false);
120
175
  }
176
+ /**
177
+ * Initializes the authorization service with the specified options.
178
+ */
121
179
  async initialize(authorizeOptions) {
122
180
  console.debug("AuthorizeService::initialize::started");
123
181
  await this.uninitialize();
124
- if (await firstValueFrom(this._isInitializing)) {
182
+ if (this._isInitializing()) {
125
183
  return;
126
184
  }
127
- if (await firstValueFrom(this._isInitialized)) {
185
+ if (this._isInitialized()) {
128
186
  console.debug("AuthorizeService::initialize::alreadyInitialized");
129
187
  return;
130
188
  }
131
- this._isInitializing.next(true);
189
+ this._isInitializing.set(true);
132
190
  try {
133
191
  const config = {
134
192
  responseType: "code",
@@ -149,39 +207,42 @@ class AuthorizeService {
149
207
  await this.oauthService.loadDiscoveryDocumentAndTryLogin();
150
208
  console.debug("AuthorizeService::initialize::setupAutomaticSilentRefresh");
151
209
  this.oauthService.setupAutomaticSilentRefresh();
152
- this._issuer.next(authorizeOptions.issuer ?? null);
210
+ this._issuer.set(authorizeOptions.issuer ?? null);
153
211
  if (this.oauthService.hasValidIdToken()) {
154
212
  // if the idToken is still valid, we can use the session
155
213
  console.debug("AuthorizeService::initialize::hasValidIdToken");
156
- this._sessionLoading.next(true);
214
+ this._sessionLoading.set(true);
157
215
  await this.oauthService.refreshToken();
158
216
  }
159
- this._isInitialized.next(true);
217
+ this._isInitialized.set(true);
160
218
  console.debug("AuthorizeService::initialize::done");
161
219
  }
162
220
  finally {
163
- this._isInitializing.next(false);
221
+ this._isInitializing.set(false);
164
222
  }
165
223
  console.debug("AuthorizeService::initialize::completed");
166
224
  }
225
+ /**
226
+ * Uninitializes the authorization service.
227
+ */
167
228
  async uninitialize() {
168
229
  console.debug("AuthorizeService::uninitialize::started");
169
- if (await firstValueFrom(this._isInitializing)) {
230
+ if (this._isInitializing()) {
170
231
  return;
171
232
  }
172
- if (!await firstValueFrom(this._isInitialized)) {
233
+ if (!this._isInitialized()) {
173
234
  console.debug("AuthorizeService::uninitialize::alreadyUninitialized");
174
235
  return;
175
236
  }
176
237
  try {
177
- this._isInitializing.next(true);
238
+ this._isInitializing.set(true);
178
239
  this.oauthService.stopAutomaticRefresh();
179
240
  this.authorizeOptions = null;
180
- this._isInitialized.next(false);
241
+ this._isInitialized.set(false);
181
242
  console.debug("AuthorizeService::uninitialize::done");
182
243
  }
183
244
  finally {
184
- this._isInitializing.next(false);
245
+ this._isInitializing.set(false);
185
246
  }
186
247
  console.debug("AuthorizeService::uninitialize::completed");
187
248
  }
@@ -194,22 +255,29 @@ class AuthorizeService {
194
255
  const user = claims;
195
256
  if (user.family_name && user.given_name) {
196
257
  const initials = user.given_name.charAt(0) + user.family_name.charAt(0);
197
- this._userInitials.next(initials);
258
+ this._userInitials.set(initials);
198
259
  }
199
260
  else {
200
- this._userInitials.next(user.name.charAt(0) + user.name.charAt(1));
261
+ this._userInitials.set(user.name.charAt(0) + user.name.charAt(1));
201
262
  }
202
263
  const accessToken = this.oauthService.getAccessToken();
203
- this._user.next(user);
204
- this._accessToken.next(accessToken);
205
- this._isAuthenticated.next(true);
206
- this._sessionLoading.next(false);
264
+ this._user.set(user);
265
+ this._accessToken.set(accessToken);
266
+ this._isAuthenticated.set(true);
267
+ this._sessionLoading.set(false);
207
268
  console.debug("AuthorizeService::loadUserAsync::done");
208
269
  }
209
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AuthorizeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
210
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AuthorizeService });
270
+ /**
271
+ * Reloads the page. This method is protected to allow mocking in tests.
272
+ * @internal
273
+ */
274
+ reloadPage() {
275
+ window.location.reload();
276
+ }
277
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AuthorizeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
278
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AuthorizeService });
211
279
  }
212
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AuthorizeService, decorators: [{
280
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AuthorizeService, decorators: [{
213
281
  type: Injectable
214
282
  }], ctorParameters: () => [] });
215
283
 
@@ -225,172 +293,200 @@ var Roles;
225
293
  Roles["Development"] = "Development";
226
294
  })(Roles || (Roles = {}));
227
295
 
228
- class LoginMenuComponent {
229
- authorizeService = inject(AuthorizeService);
230
- isAuthenticated;
231
- userName;
232
- constructor() {
233
- this.isAuthenticated = this.authorizeService.isAuthenticated;
234
- this.userName = this.authorizeService.user.pipe(map((u) => u?.name ?? null));
235
- }
236
- ngOnInit() {
237
- const isIFrame = window.self !== window.top;
238
- console.log('app-login-menu::created');
239
- this.isAuthenticated.subscribe((x) => {
240
- console.log(`isAuthenticated changed to ${x} (iframe ${isIFrame})`);
241
- });
242
- }
243
- login() {
244
- this.authorizeService.login();
245
- }
246
- logout() {
247
- this.authorizeService.logout();
248
- }
249
- register() { }
250
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: LoginMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
251
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.12", type: LoginMenuComponent, isStandalone: false, selector: "mm-login-menu", ngImport: i0, template: "<ul *ngIf=\"isAuthenticated | async\" class=\"navbar-nav\">\n <li class=\"nav-item dropdown\">\n <a aria-expanded=\"false\" aria-haspopup=\"true\" class=\"nav-link dropdown-toggle\" data-toggle=\"dropdown\" href=\"#\"\n id=\"navbarDropdownLogin\" role=\"button\">\n {{ userName | async }} <b class=\"caret\"></b>\n </a>\n <div aria-labelledby=\"navbarDropdown\" class=\"dropdown-menu\">\n <!--<a class=\"dropdown-item\" asp-action=\"Index\" asp-area=\"Authentication\" asp-controller=\"Grants\">Client Application Access</a>-->\n <!--<a class=\"dropdown-item\" [routerLink]='[\"/authentication/profile\"]' title=\"Manage\">Manage</a>-->\n <!--<a class=\"dropdown-item\" asp-action=\"Index\" asp-area=\"Authentication\" asp-controller=\"Diagnostics\">Diagnostics</a>-->\n <div class=\"dropdown-divider\"></div>\n <a (click)='logout()' class=\"dropdown-item\" routerLink=\"\" title=\"Logout\">Logout</a>\n </div>\n </li>\n</ul>\n<ul *ngIf=\"!(isAuthenticated | async)\" class=\"navbar-nav\">\n <li class=\"nav-item\">\n <a (click)='register()' class=\"nav-link\" routerLink=\"\">Register</a>\n </li>\n <li class=\"nav-item\">\n <a (click)='login()' class=\"nav-link\" routerLink=\"\">Login</a>\n </li>\n</ul>\n", styles: [""], dependencies: [{ kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }] });
252
- }
253
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: LoginMenuComponent, decorators: [{
254
- type: Component,
255
- args: [{ selector: 'mm-login-menu', standalone: false, template: "<ul *ngIf=\"isAuthenticated | async\" class=\"navbar-nav\">\n <li class=\"nav-item dropdown\">\n <a aria-expanded=\"false\" aria-haspopup=\"true\" class=\"nav-link dropdown-toggle\" data-toggle=\"dropdown\" href=\"#\"\n id=\"navbarDropdownLogin\" role=\"button\">\n {{ userName | async }} <b class=\"caret\"></b>\n </a>\n <div aria-labelledby=\"navbarDropdown\" class=\"dropdown-menu\">\n <!--<a class=\"dropdown-item\" asp-action=\"Index\" asp-area=\"Authentication\" asp-controller=\"Grants\">Client Application Access</a>-->\n <!--<a class=\"dropdown-item\" [routerLink]='[\"/authentication/profile\"]' title=\"Manage\">Manage</a>-->\n <!--<a class=\"dropdown-item\" asp-action=\"Index\" asp-area=\"Authentication\" asp-controller=\"Diagnostics\">Diagnostics</a>-->\n <div class=\"dropdown-divider\"></div>\n <a (click)='logout()' class=\"dropdown-item\" routerLink=\"\" title=\"Logout\">Logout</a>\n </div>\n </li>\n</ul>\n<ul *ngIf=\"!(isAuthenticated | async)\" class=\"navbar-nav\">\n <li class=\"nav-item\">\n <a (click)='register()' class=\"nav-link\" routerLink=\"\">Register</a>\n </li>\n <li class=\"nav-item\">\n <a (click)='login()' class=\"nav-link\" routerLink=\"\">Login</a>\n </li>\n</ul>\n" }]
256
- }], ctorParameters: () => [] });
257
-
258
- class AuthorizeGuard {
259
- authorizeService = inject(AuthorizeService);
260
- router = inject(Router);
261
- canActivate(next, state) {
262
- const url = state.url;
263
- return this.handleAuthorization(next, url);
264
- }
265
- canActivateChild(next, state) {
266
- return this.canActivate(next, state);
296
+ // =============================================================================
297
+ // URL MATCHING UTILITIES
298
+ // =============================================================================
299
+ /**
300
+ * Checks if the request URL is from the same origin as the application.
301
+ */
302
+ function isSameOriginUrl(req) {
303
+ // It's an absolute url with the same origin.
304
+ if (req.url.startsWith(`${window.location.origin}/`)) {
305
+ return true;
267
306
  }
268
- canDeactivate(_component, _currentRoute, _currentState, _nextState) {
307
+ // It's a protocol relative url with the same origin.
308
+ // For example: //www.example.com/api/Products
309
+ if (req.url.startsWith(`//${window.location.host}/`)) {
269
310
  return true;
270
311
  }
271
- canLoad(_route, _segments) {
312
+ // It's a relative url like /api/Products
313
+ if (/^\/[^/].*/.test(req.url)) {
272
314
  return true;
273
315
  }
274
- async handleAuthorization(route, _url) {
275
- const isAuthenticated = await firstValueFrom(this.authorizeService.isAuthenticated);
276
- if (isAuthenticated) {
277
- const userRoles = await firstValueFrom(this.authorizeService.getRoles());
278
- if (route.data['roles'] && !route.data['roles'].some((role) => userRoles.includes(role))) {
279
- await this.router.navigate(['']);
280
- return false;
316
+ // It's an absolute or protocol relative url that doesn't have the same origin.
317
+ return false;
318
+ }
319
+ /**
320
+ * Checks if the request URL matches any of the known service URIs.
321
+ */
322
+ function isKnownServiceUri(req, serviceUris) {
323
+ if (serviceUris != null) {
324
+ for (const serviceUri of serviceUris) {
325
+ if (req.url.startsWith(serviceUri)) {
326
+ return true;
281
327
  }
282
- return true;
283
328
  }
284
- else {
285
- this.authorizeService.login();
286
- }
287
- return false;
288
329
  }
289
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AuthorizeGuard, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
290
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AuthorizeGuard });
330
+ return false;
291
331
  }
292
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AuthorizeGuard, decorators: [{
293
- type: Injectable
294
- }] });
295
-
296
- class AuthorizeInterceptor {
297
- authorize = inject(AuthorizeService);
298
- accessToken;
299
- constructor() {
300
- const authorize = this.authorize;
301
- this.accessToken = null;
302
- authorize.accessToken.subscribe((value) => (this.accessToken = value));
332
+ // =============================================================================
333
+ // FUNCTIONAL INTERCEPTOR (RECOMMENDED)
334
+ // =============================================================================
335
+ /**
336
+ * Functional HTTP interceptor that adds Bearer token to authorized requests.
337
+ *
338
+ * Adds the Authorization header to requests that are either:
339
+ * - Same-origin requests (relative URLs or same host)
340
+ * - Requests to known service URIs configured in AuthorizeOptions
341
+ *
342
+ * @example
343
+ * ```typescript
344
+ * // app.config.ts
345
+ * import { provideHttpClient, withInterceptors } from '@angular/common/http';
346
+ * import { authorizeInterceptor } from '@meshmakers/shared-auth';
347
+ *
348
+ * export const appConfig: ApplicationConfig = {
349
+ * providers: [
350
+ * provideHttpClient(withInterceptors([authorizeInterceptor])),
351
+ * provideMmSharedAuth(),
352
+ * ]
353
+ * };
354
+ * ```
355
+ */
356
+ const authorizeInterceptor = (req, next) => {
357
+ const authorizeService = inject(AuthorizeService);
358
+ const token = authorizeService.getAccessTokenSync();
359
+ const serviceUris = authorizeService.getServiceUris();
360
+ if (token && (isSameOriginUrl(req) || isKnownServiceUri(req, serviceUris))) {
361
+ req = req.clone({
362
+ setHeaders: {
363
+ Authorization: `Bearer ${token}`
364
+ }
365
+ });
303
366
  }
304
- static isSameOriginUrl(req) {
305
- // It's an absolute url with the same origin.
306
- if (req.url.startsWith(`${window.location.origin}/`)) {
307
- return true;
308
- }
309
- // It's a protocol relative url with the same origin.
310
- // For example: //www.example.com/api/Products
311
- if (req.url.startsWith(`//${window.location.host}/`)) {
312
- return true;
313
- }
314
- // It's a relative url like /api/Products
315
- if (/^\/[^/].*/.test(req.url)) {
316
- return true;
317
- }
318
- // It's an absolute or protocol relative url that
319
- // doesn't have the same origin.
367
+ return next(req);
368
+ };
369
+
370
+ /**
371
+ * Handles authorization check for route activation.
372
+ * Redirects to login if not authenticated, or to home if user lacks required roles.
373
+ *
374
+ * @param route - The activated route snapshot containing route data
375
+ * @returns true if authorized, false otherwise
376
+ *
377
+ * @example
378
+ * ```typescript
379
+ * // Route without role requirements
380
+ * { path: 'dashboard', component: DashboardComponent, canActivate: [authorizeGuard] }
381
+ *
382
+ * // Route with role requirements
383
+ * { path: 'admin', component: AdminComponent, canActivate: [authorizeGuard], data: { roles: ['AdminPanelManagement'] } }
384
+ * ```
385
+ */
386
+ const authorizeGuard = async (route) => {
387
+ const authorizeService = inject(AuthorizeService);
388
+ const router = inject(Router);
389
+ // Use signal directly (synchronous)
390
+ const isAuthenticated = authorizeService.isAuthenticated();
391
+ if (!isAuthenticated) {
392
+ authorizeService.login();
320
393
  return false;
321
394
  }
322
- // Checks if there is an access_token available in the authorize service
323
- // and adds it to the request in case it's targeted at the same origin as the
324
- intercept(req, next) {
325
- return this.processRequestWithToken(this.accessToken, req, next);
326
- }
327
- // single page application.
328
- processRequestWithToken(token, req, next) {
329
- if (!!token && (AuthorizeInterceptor.isSameOriginUrl(req) || this.isKnownServiceUri(req))) {
330
- req = req.clone({
331
- setHeaders: {
332
- Authorization: `Bearer ${token}`
333
- }
334
- });
335
- }
336
- return next.handle(req);
337
- }
338
- isKnownServiceUri(req) {
339
- const serviceUris = this.authorize.getServiceUris();
340
- if (serviceUris != null) {
341
- for (const serviceUri of serviceUris) {
342
- if (req.url.startsWith(`${serviceUri}`)) {
343
- return true;
344
- }
345
- }
346
- }
347
- // It's an absolute or protocol relative url that
348
- // doesn't have the same origin.
395
+ // Use roles signal directly (synchronous)
396
+ const userRoles = authorizeService.roles();
397
+ const requiredRoles = route.data['roles'];
398
+ if (requiredRoles && !requiredRoles.some(role => userRoles.includes(role))) {
399
+ await router.navigate(['']);
349
400
  return false;
350
401
  }
351
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AuthorizeInterceptor, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
352
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AuthorizeInterceptor });
353
- }
354
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AuthorizeInterceptor, decorators: [{
355
- type: Injectable
356
- }], ctorParameters: () => [] });
357
-
358
- class SharedAuthModule {
359
- static forRoot(authorizeOptions) {
360
- return {
361
- ngModule: SharedAuthModule,
362
- providers: [
363
- {
364
- provide: AuthorizeOptions,
365
- useValue: authorizeOptions
366
- },
367
- AuthorizeService,
368
- AuthorizeInterceptor,
369
- AuthorizeGuard
370
- ]
371
- };
402
+ return true;
403
+ };
404
+ /**
405
+ * Guard for child routes. Delegates to authorizeGuard.
406
+ *
407
+ * @example
408
+ * ```typescript
409
+ * {
410
+ * path: 'parent',
411
+ * canActivateChild: [authorizeChildGuard],
412
+ * children: [
413
+ * { path: 'child', component: ChildComponent, data: { roles: ['RequiredRole'] } }
414
+ * ]
415
+ * }
416
+ * ```
417
+ */
418
+ const authorizeChildGuard = authorizeGuard;
419
+ /**
420
+ * Guard for lazy-loaded routes. Checks if user is authenticated.
421
+ * Replaces the deprecated canLoad guard.
422
+ *
423
+ * @example
424
+ * ```typescript
425
+ * {
426
+ * path: 'lazy',
427
+ * loadChildren: () => import('./lazy/lazy.routes'),
428
+ * canMatch: [authorizeMatchGuard]
429
+ * }
430
+ * ```
431
+ */
432
+ const authorizeMatchGuard = () => {
433
+ const authorizeService = inject(AuthorizeService);
434
+ // Use signal directly (synchronous)
435
+ const isAuthenticated = authorizeService.isAuthenticated();
436
+ if (!isAuthenticated) {
437
+ authorizeService.login();
438
+ return false;
372
439
  }
373
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: SharedAuthModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
374
- static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.12", ngImport: i0, type: SharedAuthModule, declarations: [LoginMenuComponent], imports: [CommonModule, HttpClientModule, i1$1.OAuthModule, RouterLink], exports: [LoginMenuComponent] });
375
- static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: SharedAuthModule, imports: [CommonModule, HttpClientModule, OAuthModule.forRoot()] });
376
- }
377
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: SharedAuthModule, decorators: [{
378
- type: NgModule,
379
- args: [{
380
- declarations: [LoginMenuComponent],
381
- exports: [LoginMenuComponent],
382
- providers: [],
383
- imports: [CommonModule, HttpClientModule, OAuthModule.forRoot(), RouterLink]
384
- }]
385
- }] });
440
+ return true;
441
+ };
442
+ /**
443
+ * Guard that always allows deactivation.
444
+ * Use this as a placeholder or override in specific routes.
445
+ *
446
+ * @example
447
+ * ```typescript
448
+ * { path: 'form', component: FormComponent, canDeactivate: [authorizeDeactivateGuard] }
449
+ * ```
450
+ */
451
+ const authorizeDeactivateGuard = () => true;
386
452
 
387
453
  /*
388
454
  * Public API Surface of shared-auth
389
455
  */
456
+ // UI Components (Kendo) - available via '@meshmakers/shared-auth/login-ui'
457
+ // import { LoginAppBarSectionComponent } from '@meshmakers/shared-auth/login-ui';
458
+ /**
459
+ * Provides all shared-auth dependencies.
460
+ *
461
+ * @example
462
+ * ```typescript
463
+ * // app.config.ts
464
+ * import { provideHttpClient, withInterceptors } from '@angular/common/http';
465
+ * import { provideMmSharedAuth, authorizeInterceptor } from '@meshmakers/shared-auth';
466
+ *
467
+ * export const appConfig: ApplicationConfig = {
468
+ * providers: [
469
+ * provideHttpClient(withInterceptors([authorizeInterceptor])),
470
+ * provideMmSharedAuth(),
471
+ * // ... other providers
472
+ * ]
473
+ * };
474
+ * ```
475
+ *
476
+ * @remarks
477
+ * Functional guards and interceptors don't need to be provided - they use inject() internally.
478
+ * For the functional interceptor, use `provideHttpClient(withInterceptors([authorizeInterceptor]))`.
479
+ */
480
+ function provideMmSharedAuth() {
481
+ return makeEnvironmentProviders([
482
+ provideOAuthClient(),
483
+ AuthorizeService
484
+ ]);
485
+ }
390
486
 
391
487
  /**
392
488
  * Generated bundle index. Do not edit.
393
489
  */
394
490
 
395
- export { AuthorizeGuard, AuthorizeInterceptor, AuthorizeOptions, AuthorizeService, LoginMenuComponent, Roles, SharedAuthModule };
491
+ export { AuthorizeOptions, AuthorizeService, Roles, authorizeChildGuard, authorizeDeactivateGuard, authorizeGuard, authorizeInterceptor, authorizeMatchGuard, provideMmSharedAuth };
396
492
  //# sourceMappingURL=meshmakers-shared-auth.mjs.map