@medha-analytics/medhasso-auth 1.0.0
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 +297 -0
- package/fesm2022/medha-analytics-medhasso-auth.mjs +813 -0
- package/fesm2022/medha-analytics-medhasso-auth.mjs.map +1 -0
- package/index.d.ts +5 -0
- package/lib/guards/medhasso-auth.guard.d.ts +48 -0
- package/lib/interceptors/medhasso-auth.interceptor.d.ts +38 -0
- package/lib/interfaces/medhasso-config.interface.d.ts +120 -0
- package/lib/medhasso-auth.module.d.ts +24 -0
- package/lib/services/medhasso-auth.service.d.ts +96 -0
- package/lib/services/token-management.service.d.ts +113 -0
- package/lib/tokens/medhasso-config.token.d.ts +3 -0
- package/package.json +44 -0
- package/public-api.d.ts +7 -0
|
@@ -0,0 +1,813 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { InjectionToken, Inject, Injectable, NgModule } from '@angular/core';
|
|
3
|
+
import * as i1 from '@angular/common/http';
|
|
4
|
+
import { HTTP_INTERCEPTORS } from '@angular/common/http';
|
|
5
|
+
import { BehaviorSubject, timer, throwError, of } from 'rxjs';
|
|
6
|
+
import { tap, catchError, switchMap, map } from 'rxjs/operators';
|
|
7
|
+
import * as i2 from '@angular/router';
|
|
8
|
+
|
|
9
|
+
const MEDHASSO_CONFIG = new InjectionToken('MEDHASSO_CONFIG');
|
|
10
|
+
|
|
11
|
+
class TokenManagementService {
|
|
12
|
+
get tokenConfig() {
|
|
13
|
+
return this.config.tokenConfig || {};
|
|
14
|
+
}
|
|
15
|
+
get storageType() {
|
|
16
|
+
return this.tokenConfig.storageType || 'localStorage';
|
|
17
|
+
}
|
|
18
|
+
get idTokenKey() {
|
|
19
|
+
return this.tokenConfig.idTokenKey || 'idToken';
|
|
20
|
+
}
|
|
21
|
+
get refreshTokenKey() {
|
|
22
|
+
return this.tokenConfig.refreshTokenKey || 'refreshToken';
|
|
23
|
+
}
|
|
24
|
+
get refreshBeforeExpiryMinutes() {
|
|
25
|
+
return this.tokenConfig.refreshBeforeExpiryMinutes || 5;
|
|
26
|
+
}
|
|
27
|
+
get autoRefresh() {
|
|
28
|
+
return this.tokenConfig.autoRefresh !== false;
|
|
29
|
+
}
|
|
30
|
+
constructor(config, http) {
|
|
31
|
+
this.config = config;
|
|
32
|
+
this.http = http;
|
|
33
|
+
this.tokenSubject = new BehaviorSubject(null);
|
|
34
|
+
this.userSubject = new BehaviorSubject(null);
|
|
35
|
+
this.token$ = this.tokenSubject.asObservable();
|
|
36
|
+
this.user$ = this.userSubject.asObservable();
|
|
37
|
+
this.initializeFromStorage();
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Initialize token information from storage
|
|
41
|
+
*/
|
|
42
|
+
initializeFromStorage() {
|
|
43
|
+
const token = this.getStoredToken();
|
|
44
|
+
if (token) {
|
|
45
|
+
const tokenInfo = this.createTokenInfo(token);
|
|
46
|
+
this.tokenSubject.next(tokenInfo);
|
|
47
|
+
this.userSubject.next(this.extractUserFromToken(token));
|
|
48
|
+
if (this.autoRefresh && !tokenInfo.isExpired) {
|
|
49
|
+
this.setTokenRefreshInterval(tokenInfo);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Get stored token from configured storage
|
|
55
|
+
*/
|
|
56
|
+
getStoredToken() {
|
|
57
|
+
const storage = this.storageType === 'localStorage' ? localStorage : sessionStorage;
|
|
58
|
+
return storage.getItem(this.idTokenKey);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Get stored refresh token from configured storage
|
|
62
|
+
*/
|
|
63
|
+
getStoredRefreshToken() {
|
|
64
|
+
const storage = this.storageType === 'localStorage' ? localStorage : sessionStorage;
|
|
65
|
+
return storage.getItem(this.refreshTokenKey);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Store tokens in configured storage
|
|
69
|
+
*/
|
|
70
|
+
storeTokens(idToken, refreshToken) {
|
|
71
|
+
const storage = this.storageType === 'localStorage' ? localStorage : sessionStorage;
|
|
72
|
+
storage.setItem(this.idTokenKey, idToken);
|
|
73
|
+
if (refreshToken) {
|
|
74
|
+
storage.setItem(this.refreshTokenKey, refreshToken);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Clear stored tokens
|
|
79
|
+
*/
|
|
80
|
+
clearStoredTokens() {
|
|
81
|
+
const storage = this.storageType === 'localStorage' ? localStorage : sessionStorage;
|
|
82
|
+
storage.removeItem(this.idTokenKey);
|
|
83
|
+
storage.removeItem(this.refreshTokenKey);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Create token info object from JWT token
|
|
87
|
+
*/
|
|
88
|
+
createTokenInfo(token) {
|
|
89
|
+
const payload = this.decodeJwt(token);
|
|
90
|
+
const expiresAt = payload.exp * 1000; // Convert to milliseconds
|
|
91
|
+
const now = Date.now();
|
|
92
|
+
const refreshWindow = this.refreshBeforeExpiryMinutes * 60 * 1000;
|
|
93
|
+
return {
|
|
94
|
+
token,
|
|
95
|
+
payload,
|
|
96
|
+
expiresAt,
|
|
97
|
+
isExpired: now >= expiresAt,
|
|
98
|
+
needsRefresh: now >= (expiresAt - refreshWindow)
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Decode JWT token payload
|
|
103
|
+
*/
|
|
104
|
+
decodeJwt(token) {
|
|
105
|
+
try {
|
|
106
|
+
const parts = token.split('.');
|
|
107
|
+
if (parts.length !== 3) {
|
|
108
|
+
throw new Error('Invalid JWT token format');
|
|
109
|
+
}
|
|
110
|
+
const payload = JSON.parse(atob(parts[1].replace(/-/g, '+').replace(/_/g, '/')));
|
|
111
|
+
return payload;
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
console.error('Error decoding JWT token:', error);
|
|
115
|
+
throw new Error('Invalid JWT token');
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Extract user information from JWT token
|
|
120
|
+
*/
|
|
121
|
+
extractUserFromToken(token) {
|
|
122
|
+
const payload = this.decodeJwt(token);
|
|
123
|
+
return {
|
|
124
|
+
upn: payload.upn || payload.email,
|
|
125
|
+
name: payload.name,
|
|
126
|
+
roles: payload.roles || [],
|
|
127
|
+
...payload
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Set up automatic token refresh interval
|
|
132
|
+
*/
|
|
133
|
+
setTokenRefreshInterval(tokenInfo) {
|
|
134
|
+
this.clearTokenRefreshInterval();
|
|
135
|
+
if (!this.autoRefresh || tokenInfo.isExpired) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const remainingTime = tokenInfo.expiresAt - Date.now();
|
|
139
|
+
const refreshTime = remainingTime - (this.refreshBeforeExpiryMinutes * 60 * 1000);
|
|
140
|
+
if (refreshTime > 0) {
|
|
141
|
+
this.refreshTokenInterval = timer(refreshTime).subscribe(() => {
|
|
142
|
+
this.refreshToken().subscribe();
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
// Token needs immediate refresh
|
|
147
|
+
this.refreshToken().subscribe();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Clear token refresh interval
|
|
152
|
+
*/
|
|
153
|
+
clearTokenRefreshInterval() {
|
|
154
|
+
if (this.refreshTokenInterval) {
|
|
155
|
+
this.refreshTokenInterval.unsubscribe();
|
|
156
|
+
this.refreshTokenInterval = null;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Get current token information
|
|
161
|
+
*/
|
|
162
|
+
getTokenInfo() {
|
|
163
|
+
return this.tokenSubject.value;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Get current user information
|
|
167
|
+
*/
|
|
168
|
+
getCurrentUser() {
|
|
169
|
+
return this.userSubject.value;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Check if user is authenticated
|
|
173
|
+
*/
|
|
174
|
+
isAuthenticated() {
|
|
175
|
+
const tokenInfo = this.getTokenInfo();
|
|
176
|
+
return tokenInfo !== null && !tokenInfo.isExpired;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Check if user has specific role
|
|
180
|
+
*/
|
|
181
|
+
hasRole(role) {
|
|
182
|
+
const user = this.getCurrentUser();
|
|
183
|
+
return user?.roles?.includes(role) || false;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Check if user is admin
|
|
187
|
+
*/
|
|
188
|
+
isAdmin() {
|
|
189
|
+
return this.hasRole('admin') || this.hasRole('administrator');
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Set tokens (typically called after successful authentication)
|
|
193
|
+
*/
|
|
194
|
+
setTokens(idToken, refreshToken) {
|
|
195
|
+
this.storeTokens(idToken, refreshToken);
|
|
196
|
+
const tokenInfo = this.createTokenInfo(idToken);
|
|
197
|
+
const user = this.extractUserFromToken(idToken);
|
|
198
|
+
this.tokenSubject.next(tokenInfo);
|
|
199
|
+
this.userSubject.next(user);
|
|
200
|
+
if (this.autoRefresh) {
|
|
201
|
+
this.setTokenRefreshInterval(tokenInfo);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Refresh the current token
|
|
206
|
+
*/
|
|
207
|
+
refreshToken() {
|
|
208
|
+
const refreshToken = this.getStoredRefreshToken();
|
|
209
|
+
if (!refreshToken) {
|
|
210
|
+
return throwError(() => new Error('No refresh token available'));
|
|
211
|
+
}
|
|
212
|
+
return this.http.post(`${this.config.refreshUrl}/api/refresh-token`, { refreshToken })
|
|
213
|
+
.pipe(tap((response) => {
|
|
214
|
+
if (response.id_token) {
|
|
215
|
+
this.setTokens(response.id_token, response.refresh_token);
|
|
216
|
+
}
|
|
217
|
+
}), catchError(error => {
|
|
218
|
+
console.error('Token refresh failed:', error);
|
|
219
|
+
this.logout();
|
|
220
|
+
return throwError(() => error);
|
|
221
|
+
}));
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Logout user and clear all tokens
|
|
225
|
+
*/
|
|
226
|
+
logout() {
|
|
227
|
+
this.clearTokenRefreshInterval();
|
|
228
|
+
this.clearStoredTokens();
|
|
229
|
+
this.tokenSubject.next(null);
|
|
230
|
+
this.userSubject.next(null);
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Redirect to MedHasso SSO for authentication
|
|
234
|
+
*/
|
|
235
|
+
redirectToSSO(returnUrl) {
|
|
236
|
+
const currentUrl = returnUrl || window.location.href;
|
|
237
|
+
const ssoUrl = `${this.config.ssoUrl}?url=${encodeURIComponent(currentUrl)}&applicationName=${encodeURIComponent(this.config.applicationName)}`;
|
|
238
|
+
window.location.href = ssoUrl;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Handle authentication callback (typically from SSO redirect)
|
|
242
|
+
*/
|
|
243
|
+
handleAuthCallback(params) {
|
|
244
|
+
if (params.idToken) {
|
|
245
|
+
this.setTokens(params.idToken, params.refreshToken);
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Get authorization header value
|
|
252
|
+
*/
|
|
253
|
+
getAuthorizationHeader() {
|
|
254
|
+
const tokenInfo = this.getTokenInfo();
|
|
255
|
+
if (tokenInfo && !tokenInfo.isExpired) {
|
|
256
|
+
const prefix = this.config.interceptorConfig?.authHeaderPrefix || 'Bearer';
|
|
257
|
+
return `${prefix} ${tokenInfo.token}`;
|
|
258
|
+
}
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Cleanup on service destruction
|
|
263
|
+
*/
|
|
264
|
+
ngOnDestroy() {
|
|
265
|
+
this.clearTokenRefreshInterval();
|
|
266
|
+
}
|
|
267
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: TokenManagementService, deps: [{ token: MEDHASSO_CONFIG }, { token: i1.HttpClient }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
268
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: TokenManagementService, providedIn: 'root' }); }
|
|
269
|
+
}
|
|
270
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: TokenManagementService, decorators: [{
|
|
271
|
+
type: Injectable,
|
|
272
|
+
args: [{
|
|
273
|
+
providedIn: 'root'
|
|
274
|
+
}]
|
|
275
|
+
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
276
|
+
type: Inject,
|
|
277
|
+
args: [MEDHASSO_CONFIG]
|
|
278
|
+
}] }, { type: i1.HttpClient }] });
|
|
279
|
+
|
|
280
|
+
class MedHassoAuthInterceptor {
|
|
281
|
+
constructor(config, tokenService) {
|
|
282
|
+
this.config = config;
|
|
283
|
+
this.tokenService = tokenService;
|
|
284
|
+
}
|
|
285
|
+
intercept(request, next) {
|
|
286
|
+
// Check if interceptor is enabled
|
|
287
|
+
if (this.config.interceptorConfig?.enabled === false) {
|
|
288
|
+
return next.handle(request);
|
|
289
|
+
}
|
|
290
|
+
// Check if request should be excluded
|
|
291
|
+
if (this.shouldExcludeRequest(request)) {
|
|
292
|
+
return next.handle(request);
|
|
293
|
+
}
|
|
294
|
+
// Check if request should be included (if includeUrls is specified)
|
|
295
|
+
if (!this.shouldIncludeRequest(request)) {
|
|
296
|
+
return next.handle(request);
|
|
297
|
+
}
|
|
298
|
+
// Add authorization header
|
|
299
|
+
const modifiedRequest = this.addAuthorizationHeader(request);
|
|
300
|
+
return next.handle(modifiedRequest).pipe(catchError((error) => {
|
|
301
|
+
// Handle 401 Unauthorized errors
|
|
302
|
+
if (error.status === 401) {
|
|
303
|
+
return this.handle401Error(request, next);
|
|
304
|
+
}
|
|
305
|
+
// Handle 403 Forbidden errors
|
|
306
|
+
if (error.status === 403) {
|
|
307
|
+
console.warn('MedHasso Auth: Access forbidden (403)', error);
|
|
308
|
+
}
|
|
309
|
+
return throwError(() => error);
|
|
310
|
+
}));
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Check if request URL should be excluded from authentication
|
|
314
|
+
*/
|
|
315
|
+
shouldExcludeRequest(request) {
|
|
316
|
+
const excludeUrls = this.config.interceptorConfig?.excludeUrls || [];
|
|
317
|
+
return excludeUrls.some(url => this.matchesUrl(request.url, url));
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Check if request URL should be included for authentication
|
|
321
|
+
* If includeUrls is not specified, all requests are included (unless excluded)
|
|
322
|
+
*/
|
|
323
|
+
shouldIncludeRequest(request) {
|
|
324
|
+
const includeUrls = this.config.interceptorConfig?.includeUrls;
|
|
325
|
+
// If no includeUrls specified, include all requests
|
|
326
|
+
if (!includeUrls || includeUrls.length === 0) {
|
|
327
|
+
return true;
|
|
328
|
+
}
|
|
329
|
+
// If includeUrls specified, only include matching requests
|
|
330
|
+
return includeUrls.some(url => this.matchesUrl(request.url, url));
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Check if request URL matches the pattern
|
|
334
|
+
*/
|
|
335
|
+
matchesUrl(requestUrl, pattern) {
|
|
336
|
+
// Support wildcards and exact matches
|
|
337
|
+
if (pattern.includes('*')) {
|
|
338
|
+
const regex = new RegExp(pattern.replace(/\*/g, '.*'));
|
|
339
|
+
return regex.test(requestUrl);
|
|
340
|
+
}
|
|
341
|
+
return requestUrl.includes(pattern);
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Add authorization header to request
|
|
345
|
+
*/
|
|
346
|
+
addAuthorizationHeader(request) {
|
|
347
|
+
const authHeader = this.tokenService.getAuthorizationHeader();
|
|
348
|
+
if (!authHeader) {
|
|
349
|
+
return request;
|
|
350
|
+
}
|
|
351
|
+
return request.clone({
|
|
352
|
+
setHeaders: {
|
|
353
|
+
Authorization: authHeader
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Handle 401 Unauthorized errors by attempting token refresh
|
|
359
|
+
*/
|
|
360
|
+
handle401Error(request, next) {
|
|
361
|
+
const tokenInfo = this.tokenService.getTokenInfo();
|
|
362
|
+
// If no token or token is expired, redirect to SSO
|
|
363
|
+
if (!tokenInfo || tokenInfo.isExpired) {
|
|
364
|
+
this.redirectToSSO();
|
|
365
|
+
return throwError(() => new Error('Authentication required'));
|
|
366
|
+
}
|
|
367
|
+
// If token exists but request failed, try to refresh
|
|
368
|
+
return this.tokenService.refreshToken().pipe(switchMap(() => {
|
|
369
|
+
// Retry the original request with new token
|
|
370
|
+
const retryRequest = this.addAuthorizationHeader(request);
|
|
371
|
+
return next.handle(retryRequest);
|
|
372
|
+
}), catchError((refreshError) => {
|
|
373
|
+
// If refresh fails, redirect to SSO
|
|
374
|
+
console.error('MedHasso Auth: Token refresh failed', refreshError);
|
|
375
|
+
this.redirectToSSO();
|
|
376
|
+
return throwError(() => refreshError);
|
|
377
|
+
}));
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Redirect to SSO for authentication
|
|
381
|
+
*/
|
|
382
|
+
redirectToSSO() {
|
|
383
|
+
// Add a small delay to prevent multiple redirects
|
|
384
|
+
setTimeout(() => {
|
|
385
|
+
this.tokenService.redirectToSSO();
|
|
386
|
+
}, 100);
|
|
387
|
+
}
|
|
388
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: MedHassoAuthInterceptor, deps: [{ token: MEDHASSO_CONFIG }, { token: TokenManagementService }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
389
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: MedHassoAuthInterceptor }); }
|
|
390
|
+
}
|
|
391
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: MedHassoAuthInterceptor, decorators: [{
|
|
392
|
+
type: Injectable
|
|
393
|
+
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
394
|
+
type: Inject,
|
|
395
|
+
args: [MEDHASSO_CONFIG]
|
|
396
|
+
}] }, { type: TokenManagementService }] });
|
|
397
|
+
|
|
398
|
+
class MedHassoAuthService {
|
|
399
|
+
constructor(config, tokenService, http) {
|
|
400
|
+
this.config = config;
|
|
401
|
+
this.tokenService = tokenService;
|
|
402
|
+
this.http = http;
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Get current authentication status
|
|
406
|
+
*/
|
|
407
|
+
isAuthenticated() {
|
|
408
|
+
return this.tokenService.isAuthenticated();
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Get current user information
|
|
412
|
+
*/
|
|
413
|
+
getCurrentUser() {
|
|
414
|
+
return this.tokenService.getCurrentUser();
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Get current user as observable
|
|
418
|
+
*/
|
|
419
|
+
getCurrentUser$() {
|
|
420
|
+
return this.tokenService.user$;
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Check if user has specific role
|
|
424
|
+
*/
|
|
425
|
+
hasRole(role) {
|
|
426
|
+
return this.tokenService.hasRole(role);
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Check if user has any of the specified roles
|
|
430
|
+
*/
|
|
431
|
+
hasAnyRole(roles) {
|
|
432
|
+
return roles.some(role => this.tokenService.hasRole(role));
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Check if user has all of the specified roles
|
|
436
|
+
*/
|
|
437
|
+
hasAllRoles(roles) {
|
|
438
|
+
return roles.every(role => this.tokenService.hasRole(role));
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Check if user is admin
|
|
442
|
+
*/
|
|
443
|
+
isAdmin() {
|
|
444
|
+
return this.tokenService.isAdmin();
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Login user (redirect to SSO)
|
|
448
|
+
*/
|
|
449
|
+
login(returnUrl) {
|
|
450
|
+
this.tokenService.redirectToSSO(returnUrl);
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Logout user
|
|
454
|
+
*/
|
|
455
|
+
logout() {
|
|
456
|
+
this.tokenService.logout();
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Refresh authentication token
|
|
460
|
+
*/
|
|
461
|
+
refreshToken() {
|
|
462
|
+
return this.tokenService.refreshToken();
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Check access permissions for a simple key (if applicable)
|
|
466
|
+
*/
|
|
467
|
+
checkAccess(simpleKey, backendUrl) {
|
|
468
|
+
const url = backendUrl || this.getBackendUrl();
|
|
469
|
+
if (!url) {
|
|
470
|
+
return of(null);
|
|
471
|
+
}
|
|
472
|
+
return this.http.get(`${url}/auth/guard?simple_key=${simpleKey}`)
|
|
473
|
+
.pipe(catchError(error => {
|
|
474
|
+
console.error('Access check failed:', error);
|
|
475
|
+
return of(null);
|
|
476
|
+
}));
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Check if user is admin via backend API (if applicable)
|
|
480
|
+
*/
|
|
481
|
+
checkIsAdmin(backendUrl) {
|
|
482
|
+
const user = this.getCurrentUser();
|
|
483
|
+
const url = backendUrl || this.getBackendUrl();
|
|
484
|
+
if (!user?.upn || !url) {
|
|
485
|
+
return of(false);
|
|
486
|
+
}
|
|
487
|
+
return this.http.get(`${url}/isAdmin?email=${user.upn}`)
|
|
488
|
+
.pipe(map((response) => !!response), catchError(() => of(false)));
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Get backend URL from config (helper method)
|
|
492
|
+
*/
|
|
493
|
+
getBackendUrl() {
|
|
494
|
+
// This would need to be configured or passed in
|
|
495
|
+
// For now, return null as it's application-specific
|
|
496
|
+
return null;
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* Get token information
|
|
500
|
+
*/
|
|
501
|
+
getTokenInfo() {
|
|
502
|
+
return this.tokenService.getTokenInfo();
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Get token as observable
|
|
506
|
+
*/
|
|
507
|
+
getToken$() {
|
|
508
|
+
return this.tokenService.token$;
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Handle authentication callback
|
|
512
|
+
*/
|
|
513
|
+
handleAuthCallback(params) {
|
|
514
|
+
return this.tokenService.handleAuthCallback(params);
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* Get user email
|
|
518
|
+
*/
|
|
519
|
+
getUserEmail() {
|
|
520
|
+
return this.getCurrentUser()?.upn || null;
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* Get user display name
|
|
524
|
+
*/
|
|
525
|
+
getUserDisplayName() {
|
|
526
|
+
return this.getCurrentUser()?.name || null;
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* Get all user roles
|
|
530
|
+
*/
|
|
531
|
+
getUserRoles() {
|
|
532
|
+
return this.getCurrentUser()?.roles || [];
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Get authorization header value
|
|
536
|
+
*/
|
|
537
|
+
getAuthorizationHeader() {
|
|
538
|
+
return this.tokenService.getAuthorizationHeader();
|
|
539
|
+
}
|
|
540
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: MedHassoAuthService, deps: [{ token: MEDHASSO_CONFIG }, { token: TokenManagementService }, { token: i1.HttpClient }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
541
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: MedHassoAuthService, providedIn: 'root' }); }
|
|
542
|
+
}
|
|
543
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: MedHassoAuthService, decorators: [{
|
|
544
|
+
type: Injectable,
|
|
545
|
+
args: [{
|
|
546
|
+
providedIn: 'root'
|
|
547
|
+
}]
|
|
548
|
+
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
549
|
+
type: Inject,
|
|
550
|
+
args: [MEDHASSO_CONFIG]
|
|
551
|
+
}] }, { type: TokenManagementService }, { type: i1.HttpClient }] });
|
|
552
|
+
|
|
553
|
+
class MedHassoAuthModule {
|
|
554
|
+
/**
|
|
555
|
+
* Configure the MedHasso authentication module with required settings
|
|
556
|
+
*/
|
|
557
|
+
static forRoot(config) {
|
|
558
|
+
return {
|
|
559
|
+
ngModule: MedHassoAuthModule,
|
|
560
|
+
providers: [
|
|
561
|
+
{
|
|
562
|
+
provide: MEDHASSO_CONFIG,
|
|
563
|
+
useValue: this.mergeWithDefaults(config)
|
|
564
|
+
},
|
|
565
|
+
TokenManagementService,
|
|
566
|
+
MedHassoAuthService,
|
|
567
|
+
{
|
|
568
|
+
provide: HTTP_INTERCEPTORS,
|
|
569
|
+
useClass: MedHassoAuthInterceptor,
|
|
570
|
+
multi: true
|
|
571
|
+
}
|
|
572
|
+
]
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* Configure the MedHasso authentication module for feature modules
|
|
577
|
+
*/
|
|
578
|
+
static forChild() {
|
|
579
|
+
return {
|
|
580
|
+
ngModule: MedHassoAuthModule,
|
|
581
|
+
providers: []
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* Provide MedHasso authentication with standalone components
|
|
586
|
+
*/
|
|
587
|
+
static forStandalone(config) {
|
|
588
|
+
return [
|
|
589
|
+
{
|
|
590
|
+
provide: MEDHASSO_CONFIG,
|
|
591
|
+
useValue: this.mergeWithDefaults(config)
|
|
592
|
+
},
|
|
593
|
+
TokenManagementService,
|
|
594
|
+
MedHassoAuthService,
|
|
595
|
+
{
|
|
596
|
+
provide: HTTP_INTERCEPTORS,
|
|
597
|
+
useClass: MedHassoAuthInterceptor,
|
|
598
|
+
multi: true
|
|
599
|
+
}
|
|
600
|
+
];
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* Merge user configuration with sensible defaults
|
|
604
|
+
*/
|
|
605
|
+
static mergeWithDefaults(config) {
|
|
606
|
+
const defaultConfig = {
|
|
607
|
+
tokenConfig: {
|
|
608
|
+
storageType: 'localStorage',
|
|
609
|
+
idTokenKey: 'idToken',
|
|
610
|
+
refreshTokenKey: 'refreshToken',
|
|
611
|
+
refreshBeforeExpiryMinutes: 5,
|
|
612
|
+
autoRefresh: true,
|
|
613
|
+
...config.tokenConfig
|
|
614
|
+
},
|
|
615
|
+
interceptorConfig: {
|
|
616
|
+
enabled: true,
|
|
617
|
+
authHeaderPrefix: 'Bearer',
|
|
618
|
+
excludeUrls: [],
|
|
619
|
+
includeUrls: [],
|
|
620
|
+
...config.interceptorConfig
|
|
621
|
+
},
|
|
622
|
+
securityConfig: {
|
|
623
|
+
httpsOnly: true,
|
|
624
|
+
secureCookies: true,
|
|
625
|
+
additionalCspSources: [],
|
|
626
|
+
...config.securityConfig
|
|
627
|
+
}
|
|
628
|
+
};
|
|
629
|
+
return {
|
|
630
|
+
...config,
|
|
631
|
+
...defaultConfig,
|
|
632
|
+
tokenConfig: {
|
|
633
|
+
...defaultConfig.tokenConfig,
|
|
634
|
+
...config.tokenConfig
|
|
635
|
+
},
|
|
636
|
+
interceptorConfig: {
|
|
637
|
+
...defaultConfig.interceptorConfig,
|
|
638
|
+
...config.interceptorConfig
|
|
639
|
+
},
|
|
640
|
+
securityConfig: {
|
|
641
|
+
...defaultConfig.securityConfig,
|
|
642
|
+
...config.securityConfig
|
|
643
|
+
}
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: MedHassoAuthModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
647
|
+
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.14", ngImport: i0, type: MedHassoAuthModule }); }
|
|
648
|
+
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: MedHassoAuthModule }); }
|
|
649
|
+
}
|
|
650
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: MedHassoAuthModule, decorators: [{
|
|
651
|
+
type: NgModule,
|
|
652
|
+
args: [{
|
|
653
|
+
declarations: [],
|
|
654
|
+
imports: [],
|
|
655
|
+
exports: []
|
|
656
|
+
}]
|
|
657
|
+
}] });
|
|
658
|
+
|
|
659
|
+
class MedHassoAuthGuard {
|
|
660
|
+
constructor(tokenService, router) {
|
|
661
|
+
this.tokenService = tokenService;
|
|
662
|
+
this.router = router;
|
|
663
|
+
}
|
|
664
|
+
canActivate(route) {
|
|
665
|
+
return this.checkAuthentication(route);
|
|
666
|
+
}
|
|
667
|
+
canActivateChild(childRoute) {
|
|
668
|
+
return this.checkAuthentication(childRoute);
|
|
669
|
+
}
|
|
670
|
+
checkAuthentication(route) {
|
|
671
|
+
// Check for authentication callback parameters
|
|
672
|
+
if (this.handleAuthCallback(route)) {
|
|
673
|
+
return of(true);
|
|
674
|
+
}
|
|
675
|
+
// Check if user is already authenticated
|
|
676
|
+
if (this.tokenService.isAuthenticated()) {
|
|
677
|
+
return this.refreshTokenIfNeeded().pipe(map(() => true), catchError(() => {
|
|
678
|
+
this.tokenService.redirectToSSO();
|
|
679
|
+
return of(false);
|
|
680
|
+
}));
|
|
681
|
+
}
|
|
682
|
+
// Not authenticated, redirect to SSO
|
|
683
|
+
this.tokenService.redirectToSSO();
|
|
684
|
+
return of(false);
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* Handle authentication callback from MedHasso SSO
|
|
688
|
+
*/
|
|
689
|
+
handleAuthCallback(route) {
|
|
690
|
+
const queryParams = route.queryParams;
|
|
691
|
+
// Check for AIRO redirect flow
|
|
692
|
+
if (queryParams['redirectapp'] === 'airo' && queryParams['idToken']) {
|
|
693
|
+
return this.tokenService.handleAuthCallback({
|
|
694
|
+
idToken: queryParams['idToken'],
|
|
695
|
+
refreshToken: queryParams['refreshToken']
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
// Check for standard authentication callback
|
|
699
|
+
if (queryParams['idToken'] || queryParams['token']) {
|
|
700
|
+
return this.tokenService.handleAuthCallback({
|
|
701
|
+
idToken: queryParams['idToken'] || queryParams['token'],
|
|
702
|
+
refreshToken: queryParams['refreshToken']
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
return false;
|
|
706
|
+
}
|
|
707
|
+
/**
|
|
708
|
+
* Refresh token if needed
|
|
709
|
+
*/
|
|
710
|
+
refreshTokenIfNeeded() {
|
|
711
|
+
const tokenInfo = this.tokenService.getTokenInfo();
|
|
712
|
+
if (tokenInfo?.needsRefresh) {
|
|
713
|
+
return this.tokenService.refreshToken();
|
|
714
|
+
}
|
|
715
|
+
return of(null);
|
|
716
|
+
}
|
|
717
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: MedHassoAuthGuard, deps: [{ token: TokenManagementService }, { token: i2.Router }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
718
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: MedHassoAuthGuard, providedIn: 'root' }); }
|
|
719
|
+
}
|
|
720
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: MedHassoAuthGuard, decorators: [{
|
|
721
|
+
type: Injectable,
|
|
722
|
+
args: [{
|
|
723
|
+
providedIn: 'root'
|
|
724
|
+
}]
|
|
725
|
+
}], ctorParameters: () => [{ type: TokenManagementService }, { type: i2.Router }] });
|
|
726
|
+
/**
|
|
727
|
+
* Role-based authentication guard
|
|
728
|
+
*/
|
|
729
|
+
class MedHassoRoleGuard {
|
|
730
|
+
constructor(tokenService, router) {
|
|
731
|
+
this.tokenService = tokenService;
|
|
732
|
+
this.router = router;
|
|
733
|
+
}
|
|
734
|
+
canActivate(route) {
|
|
735
|
+
return this.checkRole(route);
|
|
736
|
+
}
|
|
737
|
+
canActivateChild(childRoute) {
|
|
738
|
+
return this.checkRole(childRoute);
|
|
739
|
+
}
|
|
740
|
+
checkRole(route) {
|
|
741
|
+
const requiredRoles = route.data['roles'];
|
|
742
|
+
const requiredRole = route.data['role'];
|
|
743
|
+
if (!this.tokenService.isAuthenticated()) {
|
|
744
|
+
this.tokenService.redirectToSSO();
|
|
745
|
+
return false;
|
|
746
|
+
}
|
|
747
|
+
// Check multiple roles (OR condition)
|
|
748
|
+
if (requiredRoles && requiredRoles.length > 0) {
|
|
749
|
+
const hasAnyRole = requiredRoles.some(role => this.tokenService.hasRole(role));
|
|
750
|
+
if (!hasAnyRole) {
|
|
751
|
+
return this.router.createUrlTree(['/unauthorized']);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
// Check single role
|
|
755
|
+
if (requiredRole && !this.tokenService.hasRole(requiredRole)) {
|
|
756
|
+
return this.router.createUrlTree(['/unauthorized']);
|
|
757
|
+
}
|
|
758
|
+
return true;
|
|
759
|
+
}
|
|
760
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: MedHassoRoleGuard, deps: [{ token: TokenManagementService }, { token: i2.Router }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
761
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: MedHassoRoleGuard, providedIn: 'root' }); }
|
|
762
|
+
}
|
|
763
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: MedHassoRoleGuard, decorators: [{
|
|
764
|
+
type: Injectable,
|
|
765
|
+
args: [{
|
|
766
|
+
providedIn: 'root'
|
|
767
|
+
}]
|
|
768
|
+
}], ctorParameters: () => [{ type: TokenManagementService }, { type: i2.Router }] });
|
|
769
|
+
/**
|
|
770
|
+
* Admin-only authentication guard
|
|
771
|
+
*/
|
|
772
|
+
class MedHassoAdminGuard {
|
|
773
|
+
constructor(tokenService, router) {
|
|
774
|
+
this.tokenService = tokenService;
|
|
775
|
+
this.router = router;
|
|
776
|
+
}
|
|
777
|
+
canActivate() {
|
|
778
|
+
return this.checkAdmin();
|
|
779
|
+
}
|
|
780
|
+
canActivateChild() {
|
|
781
|
+
return this.checkAdmin();
|
|
782
|
+
}
|
|
783
|
+
checkAdmin() {
|
|
784
|
+
if (!this.tokenService.isAuthenticated()) {
|
|
785
|
+
this.tokenService.redirectToSSO();
|
|
786
|
+
return false;
|
|
787
|
+
}
|
|
788
|
+
if (!this.tokenService.isAdmin()) {
|
|
789
|
+
return this.router.createUrlTree(['/unauthorized']);
|
|
790
|
+
}
|
|
791
|
+
return true;
|
|
792
|
+
}
|
|
793
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: MedHassoAdminGuard, deps: [{ token: TokenManagementService }, { token: i2.Router }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
794
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: MedHassoAdminGuard, providedIn: 'root' }); }
|
|
795
|
+
}
|
|
796
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: MedHassoAdminGuard, decorators: [{
|
|
797
|
+
type: Injectable,
|
|
798
|
+
args: [{
|
|
799
|
+
providedIn: 'root'
|
|
800
|
+
}]
|
|
801
|
+
}], ctorParameters: () => [{ type: TokenManagementService }, { type: i2.Router }] });
|
|
802
|
+
|
|
803
|
+
/*
|
|
804
|
+
* Public API Surface of medhasso-auth
|
|
805
|
+
*/
|
|
806
|
+
// Module
|
|
807
|
+
|
|
808
|
+
/**
|
|
809
|
+
* Generated bundle index. Do not edit.
|
|
810
|
+
*/
|
|
811
|
+
|
|
812
|
+
export { MEDHASSO_CONFIG, MedHassoAdminGuard, MedHassoAuthGuard, MedHassoAuthInterceptor, MedHassoAuthModule, MedHassoAuthService, MedHassoRoleGuard, TokenManagementService };
|
|
813
|
+
//# sourceMappingURL=medha-analytics-medhasso-auth.mjs.map
|