@meshmakers/shared-auth 3.3.33 → 3.3.380
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/README.md +205 -13
- package/fesm2022/meshmakers-shared-auth-login-ui.mjs +127 -0
- package/fesm2022/meshmakers-shared-auth-login-ui.mjs.map +1 -0
- package/fesm2022/meshmakers-shared-auth.mjs +336 -240
- package/fesm2022/meshmakers-shared-auth.mjs.map +1 -1
- package/package.json +27 -5
- package/types/meshmakers-shared-auth-login-ui.d.ts +42 -0
- package/types/meshmakers-shared-auth.d.ts +222 -0
- package/index.d.ts +0 -118
|
@@ -1,14 +1,7 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject,
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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.
|
|
59
|
-
if (
|
|
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.
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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?.
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
return this.
|
|
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
|
-
|
|
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 (
|
|
182
|
+
if (this._isInitializing()) {
|
|
125
183
|
return;
|
|
126
184
|
}
|
|
127
|
-
if (
|
|
185
|
+
if (this._isInitialized()) {
|
|
128
186
|
console.debug("AuthorizeService::initialize::alreadyInitialized");
|
|
129
187
|
return;
|
|
130
188
|
}
|
|
131
|
-
this._isInitializing.
|
|
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.
|
|
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.
|
|
214
|
+
this._sessionLoading.set(true);
|
|
157
215
|
await this.oauthService.refreshToken();
|
|
158
216
|
}
|
|
159
|
-
this._isInitialized.
|
|
217
|
+
this._isInitialized.set(true);
|
|
160
218
|
console.debug("AuthorizeService::initialize::done");
|
|
161
219
|
}
|
|
162
220
|
finally {
|
|
163
|
-
this._isInitializing.
|
|
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 (
|
|
230
|
+
if (this._isInitializing()) {
|
|
170
231
|
return;
|
|
171
232
|
}
|
|
172
|
-
if (!
|
|
233
|
+
if (!this._isInitialized()) {
|
|
173
234
|
console.debug("AuthorizeService::uninitialize::alreadyUninitialized");
|
|
174
235
|
return;
|
|
175
236
|
}
|
|
176
237
|
try {
|
|
177
|
-
this._isInitializing.
|
|
238
|
+
this._isInitializing.set(true);
|
|
178
239
|
this.oauthService.stopAutomaticRefresh();
|
|
179
240
|
this.authorizeOptions = null;
|
|
180
|
-
this._isInitialized.
|
|
241
|
+
this._isInitialized.set(false);
|
|
181
242
|
console.debug("AuthorizeService::uninitialize::done");
|
|
182
243
|
}
|
|
183
244
|
finally {
|
|
184
|
-
this._isInitializing.
|
|
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.
|
|
258
|
+
this._userInitials.set(initials);
|
|
198
259
|
}
|
|
199
260
|
else {
|
|
200
|
-
this._userInitials.
|
|
261
|
+
this._userInitials.set(user.name.charAt(0) + user.name.charAt(1));
|
|
201
262
|
}
|
|
202
263
|
const accessToken = this.oauthService.getAccessToken();
|
|
203
|
-
this._user.
|
|
204
|
-
this._accessToken.
|
|
205
|
-
this._isAuthenticated.
|
|
206
|
-
this._sessionLoading.
|
|
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
|
-
|
|
210
|
-
|
|
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: "
|
|
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
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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
|
-
|
|
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
|
-
|
|
312
|
+
// It's a relative url like /api/Products
|
|
313
|
+
if (/^\/[^/].*/.test(req.url)) {
|
|
272
314
|
return true;
|
|
273
315
|
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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
|
-
|
|
290
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: AuthorizeGuard });
|
|
330
|
+
return false;
|
|
291
331
|
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
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
|
-
//
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
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
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
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 {
|
|
491
|
+
export { AuthorizeOptions, AuthorizeService, Roles, authorizeChildGuard, authorizeDeactivateGuard, authorizeGuard, authorizeInterceptor, authorizeMatchGuard, provideMmSharedAuth };
|
|
396
492
|
//# sourceMappingURL=meshmakers-shared-auth.mjs.map
|