@eac-arch/infrastructure-security 1.0.1 → 1.0.6

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 CHANGED
@@ -1,63 +1,84 @@
1
- # EacArchInfrastructureSecurity
2
-
3
- This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 21.1.0.
4
-
5
- ## Code scaffolding
6
-
7
- Angular CLI includes powerful code scaffolding tools. To generate a new component, run:
8
-
9
- ```bash
10
- ng generate component component-name
11
- ```
12
-
13
- For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run:
14
-
15
- ```bash
16
- ng generate --help
17
- ```
18
-
19
- ## Building
20
-
21
- To build the library, run:
22
-
23
- ```bash
24
- ng build eac-arch-infrastructure-security
25
- ```
26
-
27
- This command will compile your project, and the build artifacts will be placed in the `dist/` directory.
28
-
29
- ### Publishing the Library
30
-
31
- Once the project is built, you can publish your library by following these steps:
32
-
33
- 1. Navigate to the `dist` directory:
34
- ```bash
35
- cd dist/eac-arch-infrastructure-security
36
- ```
37
-
38
- 2. Run the `npm publish` command to publish your library to the npm registry:
39
- ```bash
40
- npm publish
41
- ```
42
-
43
- ## Running unit tests
44
-
45
- To execute unit tests with the [Karma](https://karma-runner.github.io) test runner, use the following command:
46
-
47
- ```bash
48
- ng test
49
- ```
50
-
51
- ## Running end-to-end tests
52
-
53
- For end-to-end (e2e) testing, run:
54
-
55
- ```bash
56
- ng e2e
57
- ```
58
-
59
- Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs.
60
-
61
- ## Additional Resources
62
-
63
- For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page.
1
+ # @eac-arch/infrastructure-security
2
+
3
+ Angular security library based on OAuth2/OIDC with guards, interceptor, and callback flow.
4
+
5
+ ## Purpose
6
+
7
+ `@eac-arch/infrastructure-security` encapsulates authentication and authorization concerns:
8
+
9
+ - OIDC bootstrap via APP_INITIALIZER
10
+ - token lifecycle and profile resolution
11
+ - route guards for authentication and authorization
12
+ - automatic bearer token injection for configured API endpoints
13
+ - callback component/state utilities for code flow redirects
14
+
15
+ ## Main Capabilities
16
+
17
+ - `provideSecurity()`
18
+ : Registers OAuth client, `SecurityService`, and startup initializer.
19
+ - `SecurityService`
20
+ : Exposes auth status, token, roles/scopes checks, profile, login/logout.
21
+ - `authGuard`
22
+ : Blocks unauthenticated routes and triggers login flow.
23
+ - `authzGuard`
24
+ : Evaluates route-level role/scope requirements.
25
+ - `authInterceptor`
26
+ : Adds bearer token for URLs configured through `SECURITY.SECURE_API_KEYS` in runtime config.
27
+ - `OidcCallback`
28
+ : Handles post-login callback and safe redirect restore.
29
+ - OAuth state utilities
30
+ : `stateEncode`, `stateDecode`, `stateDecodeFromUrlParam`, `safeRel`.
31
+
32
+ ## Public API
33
+
34
+ ```ts
35
+ export type { IdpConfig, UserProfile, SecurityConfig };
36
+ export { SecurityService, provideSecurity };
37
+ export { authGuard, authzGuard, authInterceptor };
38
+ export { OidcCallback };
39
+ export { stateEncode, stateDecode, stateDecodeFromUrlParam, safeRel };
40
+ ```
41
+
42
+ ## Runtime Config Requirements
43
+
44
+ Expected config keys include:
45
+
46
+ - `IDP` (issuer, client id, redirect URIs, scopes)
47
+ - `SECURITY.SECURE_API_KEYS` (list of keys that resolve to secure API base URLs)
48
+
49
+ ## Dependencies
50
+
51
+ - `@eac-arch/infrastructure-config`
52
+ - `angular-oauth2-oidc`
53
+ - Angular core/common/router
54
+ - `tslib`
55
+
56
+ ## Development
57
+
58
+ From `eac-arch-infrastructure-security`:
59
+
60
+ ```bash
61
+ npm install
62
+ npm run build
63
+ npm test
64
+ ```
65
+
66
+
67
+ ## Created By
68
+
69
+ **Erick Arostegui Cunza**
70
+ Enterprise Solutions Architect
71
+
72
+ Professional Profile:
73
+ - LinkedIn: https://www.linkedin.com/in/erick-arostegui-cunza/
74
+
75
+ Articles:
76
+ - Medium: https://medium.com/@scorpius86
77
+
78
+ Channels and Media:
79
+ - Facebook: https://www.facebook.com/Erick.Arostegui.Cunza
80
+ - TikTok: https://www.tiktok.com/@erick_arostegui_cunza
81
+ - Instagram: https://www.instagram.com/erickarosteguicunza/
82
+ - Spotify: https://open.spotify.com/show/2JQxlxcRg7k7cJ1hB52Ge5
83
+
84
+ Created by Erick Arostegui Cunza.
@@ -1,30 +1,612 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Component } from '@angular/core';
2
+ import { signal, computed, makeEnvironmentProviders, PLATFORM_ID, DestroyRef, APP_INITIALIZER, inject, Component } from '@angular/core';
3
+ import { startWith } from 'rxjs';
4
+ import { OAuthErrorEvent, OAuthService, provideOAuthClient } from 'angular-oauth2-oidc';
5
+ import { isPlatformBrowser } from '@angular/common';
6
+ import { Router } from '@angular/router';
7
+ import { ConfigService } from '@eac-arch/infrastructure-config';
3
8
 
4
- class EacArchInfrastructureSecurity {
5
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: EacArchInfrastructureSecurity, deps: [], target: i0.ɵɵFactoryTarget.Component });
6
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.1.3", type: EacArchInfrastructureSecurity, isStandalone: true, selector: "eac-arch-eac-arch-infrastructure-security", ngImport: i0, template: `
7
- <p>
8
- eac-arch-infrastructure-security works!
9
- </p>
10
- `, isInline: true, styles: [""] });
9
+ const DEFAULT_SEPARATOR = ';';
10
+ function stateEncode(obj) {
11
+ const s = JSON.stringify(obj);
12
+ return btoa(s).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
13
+ }
14
+ function stateDecode(s) {
15
+ try {
16
+ const pad = s.length % 4 ? '='.repeat(4 - (s.length % 4)) : '';
17
+ const b64 = s.replace(/-/g, '+').replace(/_/g, '/') + pad;
18
+ return JSON.parse(atob(b64));
19
+ }
20
+ catch {
21
+ return null;
22
+ }
23
+ }
24
+ function stateDecodeFromUrlParam(s) {
25
+ if (!s)
26
+ return null;
27
+ let decoded = s;
28
+ if (/%[0-9A-Fa-f]{2}/.test(decoded)) {
29
+ try {
30
+ decoded = decodeURIComponent(decoded);
31
+ }
32
+ catch {
33
+ /* ignore */
34
+ }
35
+ }
36
+ return stateDecode(decoded);
37
+ }
38
+ function safeRel(rel) {
39
+ let r = typeof rel === 'string' ? rel : '/';
40
+ if (!r.startsWith('/'))
41
+ r = '/' + r;
42
+ try {
43
+ const u = new URL(r, window.location.origin);
44
+ if (u.origin !== window.location.origin)
45
+ return '/';
46
+ }
47
+ catch {
48
+ return '/';
49
+ }
50
+ return r;
51
+ }
52
+
53
+ // Pure functions for extracting roles, scopes and user info from JWT/OIDC claims.
54
+ // Supports both Azure AD and Keycloak claim formats.
55
+ function extractRoles(claims) {
56
+ const out = new Set();
57
+ // Azure AD
58
+ if (Array.isArray(claims['roles']))
59
+ for (const r of claims['roles'])
60
+ out.add(String(r).toLowerCase());
61
+ if (Array.isArray(claims['groups']))
62
+ for (const g of claims['groups'])
63
+ out.add(String(g).toLowerCase());
64
+ // Keycloak
65
+ const realmAccess = claims['realm_access'];
66
+ if (Array.isArray(realmAccess?.roles))
67
+ for (const r of realmAccess.roles)
68
+ out.add(String(r).toLowerCase());
69
+ const ra = claims['resource_access'];
70
+ if (ra)
71
+ for (const k of Object.keys(ra))
72
+ ra[k]?.roles?.forEach((r) => out.add(String(r).toLowerCase()));
73
+ return Array.from(out);
74
+ }
75
+ function extractScopes(claims) {
76
+ // Azure AD uses 'scp', Keycloak uses 'scope'
77
+ const raw = (claims['scp'] ?? claims['scope']);
78
+ if (typeof raw !== 'string' || !raw)
79
+ return [];
80
+ return raw.trim().split(/\s+/).map((s) => s.toLowerCase());
81
+ }
82
+ function mapClaimsToUserinfo(claims) {
83
+ const first = (claims['given_name'] ?? claims['givenName'] ?? claims['firstName'] ?? null);
84
+ const last = (claims['family_name'] ?? claims['familyName'] ?? claims['lastName'] ?? null);
85
+ const display = (claims['name'] ?? [first, last].filter(Boolean).join(' ')) ||
86
+ claims['preferred_username'] ||
87
+ claims['email'] ||
88
+ claims['sub'] ||
89
+ '';
90
+ return {
91
+ sub: claims['sub'] ?? claims['oid'] ?? null,
92
+ name: display,
93
+ preferred_username: claims['preferred_username'] ?? claims['upn'] ?? null,
94
+ given_name: first,
95
+ family_name: last,
96
+ email: claims['email'] ?? null,
97
+ picture: claims['picture'] ?? null,
98
+ };
99
+ }
100
+ function decodeJwtPayload(jwt) {
101
+ if (!jwt)
102
+ return null;
103
+ const p = jwt.split('.')[1] ?? '';
104
+ try {
105
+ const pad = p.length % 4 ? '='.repeat(4 - (p.length % 4)) : '';
106
+ const b64 = p.replace(/-/g, '+').replace(/_/g, '/') + pad;
107
+ return JSON.parse(atob(b64));
108
+ }
109
+ catch {
110
+ return null;
111
+ }
112
+ }
113
+ function hasAny(have, required) {
114
+ if (!required?.length)
115
+ return true;
116
+ const set = new Set(have);
117
+ return required.some((r) => set.has(r.toLowerCase()));
118
+ }
119
+ function hasAll(have, required) {
120
+ if (!required?.length)
121
+ return true;
122
+ const set = new Set(have);
123
+ return required.every((r) => set.has(r.toLowerCase()));
124
+ }
125
+
126
+ // Handles Microsoft Graph API profile loading via refresh token exchange.
127
+ // Only active when the IDP issuer is Azure AD.
128
+ class GraphProfileLoader {
129
+ oauth;
130
+ getIdpConfig;
131
+ accessToken = signal(null, ...(ngDevMode ? [{ debugName: "accessToken" }] : []));
132
+ accessTokenExp = 0;
133
+ constructor(oauth, getIdpConfig) {
134
+ this.oauth = oauth;
135
+ this.getIdpConfig = getIdpConfig;
136
+ }
137
+ isAzure() {
138
+ const issuer = this.oauth?.issuer ||
139
+ this.getIdpConfig()?.ISSUER ||
140
+ '';
141
+ return /login\.microsoftonline\.com/i.test(issuer) || /sts\.windows\.net/i.test(issuer);
142
+ }
143
+ async loadProfile() {
144
+ const token = await this.ensureToken();
145
+ if (!token)
146
+ return null;
147
+ const resp = await fetch('https://graph.microsoft.com/v1.0/me?$select=displayName,givenName,surname,mail,userPrincipalName,id', { headers: { Authorization: `Bearer ${token}` } });
148
+ if (!resp.ok)
149
+ return null;
150
+ const me = (await resp.json());
151
+ return {
152
+ sub: me['id'] ?? null,
153
+ name: me['displayName'] ?? null,
154
+ preferred_username: me['userPrincipalName'] ?? null,
155
+ given_name: me['givenName'] ?? null,
156
+ family_name: me['surname'] ?? null,
157
+ email: me['mail'] ?? me['userPrincipalName'] ?? null,
158
+ };
159
+ }
160
+ async ensureToken() {
161
+ const cached = this.accessToken();
162
+ if (cached && Date.now() < this.accessTokenExp)
163
+ return cached;
164
+ return this.exchangeRefreshToken();
165
+ }
166
+ async exchangeRefreshToken() {
167
+ const refresh = this.getRefreshToken();
168
+ const tokenEndpoint = this.oauth.tokenEndpoint;
169
+ const clientId = this.oauth?.clientId || this.getIdpConfig()?.CLIENT_ID;
170
+ if (!refresh || !tokenEndpoint || !clientId)
171
+ return null;
172
+ const scopes = this.getGraphScopes();
173
+ const body = new URLSearchParams({
174
+ grant_type: 'refresh_token',
175
+ client_id: clientId,
176
+ refresh_token: refresh,
177
+ scope: scopes.join(' '),
178
+ });
179
+ const resp = await fetch(tokenEndpoint, {
180
+ method: 'POST',
181
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
182
+ body,
183
+ });
184
+ if (!resp.ok)
185
+ return null;
186
+ const json = (await resp.json());
187
+ const at = json['access_token'];
188
+ const expiresIn = Number(json['expires_in'] ?? 3600);
189
+ if (at) {
190
+ this.accessToken.set(at);
191
+ this.accessTokenExp = Date.now() + Math.max(0, (expiresIn - 60) * 1000);
192
+ try {
193
+ const rt = json['refresh_token'];
194
+ if (rt) {
195
+ const storage = this.oauth?.storage;
196
+ storage?.setItem('refresh_token', rt);
197
+ }
198
+ }
199
+ catch {
200
+ // ignore
201
+ }
202
+ return at;
203
+ }
204
+ return null;
205
+ }
206
+ getRefreshToken() {
207
+ try {
208
+ const o = this.oauth;
209
+ const rt = o.getRefreshToken?.() ?? o.storage?.getItem('refresh_token') ?? null;
210
+ return typeof rt === 'string' && rt ? rt : null;
211
+ }
212
+ catch {
213
+ return null;
214
+ }
215
+ }
216
+ getGraphScopes() {
217
+ const idp = this.getIdpConfig();
218
+ const scopes = idp?.GRAPH_SCOPES || 'https://graph.microsoft.com/User.Read openid profile';
219
+ return scopes.trim().split(/\s+/).filter(Boolean);
220
+ }
221
+ }
222
+
223
+ // Resolves and builds the user profile from OIDC claims, userinfo endpoint, and Graph API.
224
+ class ProfileResolver {
225
+ userinfo;
226
+ rolesSignal;
227
+ oauth;
228
+ graphLoader;
229
+ getPayload;
230
+ profile;
231
+ constructor(userinfo, rolesSignal, oauth, graphLoader, getPayload) {
232
+ this.userinfo = userinfo;
233
+ this.rolesSignal = rolesSignal;
234
+ this.oauth = oauth;
235
+ this.graphLoader = graphLoader;
236
+ this.getPayload = getPayload;
237
+ this.profile = computed(() => this.buildProfile(), ...(ngDevMode ? [{ debugName: "profile" }] : []));
238
+ }
239
+ async loadFromIdp() {
240
+ const roles = this.rolesSignal();
241
+ try {
242
+ const info = (await this.oauth.loadUserProfile());
243
+ const claims = this.resolveClaims();
244
+ let userinfo = { ...mapClaimsToUserinfo(claims), ...info };
245
+ if (this.graphLoader.isAzure()) {
246
+ const graphInfo = await this.graphLoader.loadProfile();
247
+ if (graphInfo)
248
+ userinfo = { ...userinfo, ...graphInfo };
249
+ }
250
+ userinfo['roles'] = Array.isArray(info?.['roles']) && info['roles'].length
251
+ ? info['roles'] : roles;
252
+ this.userinfo.set(userinfo);
253
+ }
254
+ catch {
255
+ await this.loadFallback(roles);
256
+ }
257
+ }
258
+ clear() {
259
+ this.userinfo.set(null);
260
+ }
261
+ async loadFallback(roles) {
262
+ if (this.graphLoader.isAzure()) {
263
+ try {
264
+ const graphInfo = await this.graphLoader.loadProfile();
265
+ if (graphInfo) {
266
+ const claims = this.resolveClaims();
267
+ this.userinfo.set({ ...mapClaimsToUserinfo(claims), ...graphInfo, roles });
268
+ return;
269
+ }
270
+ }
271
+ catch {
272
+ // Graph fallback failed
273
+ }
274
+ }
275
+ const claims = this.resolveClaims();
276
+ this.userinfo.set({ ...mapClaimsToUserinfo(claims), roles });
277
+ }
278
+ resolveClaims() {
279
+ return (this.oauth.getIdentityClaims() ?? this.getPayload()) ?? {};
280
+ }
281
+ buildProfile() {
282
+ const c = this.userinfo();
283
+ if (!c)
284
+ return null;
285
+ const first = (c['given_name'] ?? c['givenName'] ?? c['firstName'] ?? null);
286
+ const last = (c['family_name'] ?? c['familyName'] ?? c['lastName'] ?? null);
287
+ const display = (c['name'] ?? [first, last].filter(Boolean).join(' ')) ||
288
+ c['preferred_username'] || c['email'] || c['sub'] || '';
289
+ return {
290
+ sub: c['sub'] ?? null,
291
+ displayName: display,
292
+ firstName: first,
293
+ lastName: last,
294
+ email: c['email'] ?? null,
295
+ username: c['preferred_username'] ?? c['upn'] ?? null,
296
+ pictureUrl: c['picture'] ?? null,
297
+ roles: Array.isArray(c['roles']) ? c['roles'] : this.rolesSignal(),
298
+ raw: c,
299
+ };
300
+ }
301
+ }
302
+
303
+ // Manages token lifecycle: schedules automatic refresh before expiration
304
+ // and falls back to logout if refresh fails.
305
+ class TokenManager {
306
+ onExpired;
307
+ refreshTimer = null;
308
+ // Refresh 60 seconds before expiration to avoid using an expired token.
309
+ REFRESH_BEFORE_MS = 60_000;
310
+ constructor(onExpired) {
311
+ this.onExpired = onExpired;
312
+ }
313
+ scheduleAutoRefresh(oauth) {
314
+ this.clearSchedule();
315
+ const expMs = oauth.getAccessTokenExpiration?.();
316
+ if (!expMs || expMs <= 0)
317
+ return;
318
+ const delay = Math.max(0, expMs - Date.now() - this.REFRESH_BEFORE_MS);
319
+ this.refreshTimer = setTimeout(() => {
320
+ oauth.refreshToken()
321
+ .then(() => this.scheduleAutoRefresh(oauth))
322
+ .catch(() => this.onExpired());
323
+ }, delay);
324
+ }
325
+ clearSchedule() {
326
+ if (this.refreshTimer) {
327
+ clearTimeout(this.refreshTimer);
328
+ this.refreshTimer = null;
329
+ }
330
+ }
331
+ }
332
+
333
+ // Plain class (no @Injectable) - provided via useFactory with deps.
334
+ class SecurityService {
335
+ isBrowser;
336
+ oauth;
337
+ router;
338
+ getIdpConfig;
339
+ oauthEvt;
340
+ accessToken;
341
+ payload;
342
+ tokenManager;
343
+ profileResolver;
344
+ isLoggedIn;
345
+ profile;
346
+ rolesSignal;
347
+ scopesSignal;
348
+ constructor(isBrowser, oauth, router, getIdpConfig, destroyRef) {
349
+ this.isBrowser = isBrowser;
350
+ this.oauth = oauth;
351
+ this.router = router;
352
+ this.getIdpConfig = getIdpConfig;
353
+ this.tokenManager = new TokenManager(() => this.logout());
354
+ const graphLoader = new GraphProfileLoader(oauth, getIdpConfig);
355
+ this.oauthEvt = signal(null, ...(ngDevMode ? [{ debugName: "oauthEvt" }] : []));
356
+ this.accessToken = computed(() => (this.oauthEvt(), this.oauth.getAccessToken() ?? null), ...(ngDevMode ? [{ debugName: "accessToken" }] : []));
357
+ this.payload = computed(() => decodeJwtPayload(this.accessToken()), ...(ngDevMode ? [{ debugName: "payload" }] : []));
358
+ this.isLoggedIn = computed(() => (this.oauthEvt(), this.oauth.hasValidAccessToken()), ...(ngDevMode ? [{ debugName: "isLoggedIn" }] : []));
359
+ this.scopesSignal = computed(() => extractScopes(this.payload() ?? {}), ...(ngDevMode ? [{ debugName: "scopesSignal" }] : []));
360
+ this.rolesSignal = computed(() => {
361
+ const claims = this.payload() ?? this.oauth.getIdentityClaims() ?? {};
362
+ return extractRoles(claims);
363
+ }, ...(ngDevMode ? [{ debugName: "rolesSignal" }] : []));
364
+ const userinfo = signal(null, ...(ngDevMode ? [{ debugName: "userinfo" }] : []));
365
+ this.profileResolver = new ProfileResolver(userinfo, this.rolesSignal, oauth, graphLoader, () => this.payload());
366
+ this.profile = this.profileResolver.profile;
367
+ if (this.isBrowser) {
368
+ this.subscribeToOAuthEvents();
369
+ destroyRef.onDestroy(() => this.tokenManager.clearSchedule());
370
+ }
371
+ }
372
+ async init() {
373
+ if (!this.isBrowser)
374
+ return;
375
+ const idp = this.getIdpConfig();
376
+ if (!idp) {
377
+ console.warn('[SECURITY] No IDP config found, skipping init');
378
+ return;
379
+ }
380
+ // Force sessionStorage so tokens and nonce survive redirects.
381
+ // The DI override in provideSecurity() may not take effect due to
382
+ // module duplication with symlinked libraries, so we set it explicitly.
383
+ this.oauth.setStorage(sessionStorage);
384
+ const authConfig = this.buildAuthConfig(idp);
385
+ this.oauth.configure(authConfig);
386
+ try {
387
+ await this.oauth.loadDiscoveryDocumentAndTryLogin();
388
+ }
389
+ catch (err) {
390
+ console.error('[SECURITY] loadDiscoveryDocumentAndTryLogin failed:', err);
391
+ }
392
+ if (this.oauth.hasValidAccessToken()) {
393
+ this.tokenManager.scheduleAutoRefresh(this.oauth);
394
+ await this.profileResolver.loadFromIdp();
395
+ }
396
+ }
397
+ token() { return this.accessToken(); }
398
+ isAuthenticated() { return this.oauth.hasValidAccessToken(); }
399
+ roles() { return this.rolesSignal(); }
400
+ scopes() { return this.scopesSignal(); }
401
+ hasAnyRole(required) { return hasAny(this.rolesSignal(), required); }
402
+ hasAllRoles(required) { return hasAll(this.rolesSignal(), required); }
403
+ hasAnyScope(required) { return hasAny(this.scopesSignal(), required); }
404
+ login(target) {
405
+ const rel = target || this.router.url || '/';
406
+ this.oauth.initLoginFlow(stateEncode({ rel, t: Date.now() }));
407
+ }
408
+ logout() {
409
+ const idp = this.getIdpConfig();
410
+ this.oauth.logOut({ post_logout_redirect_uri: idp?.POST_LOGOUT_REDIRECT_URI });
411
+ }
412
+ // -- Private --
413
+ buildAuthConfig(idp) {
414
+ // Use the configured REDIRECT_URI path but resolve it against the current
415
+ // origin so that port changes in development (e.g. Vite picking a random
416
+ // port when 4200 is busy) don't break the nonce/PKCE validation.
417
+ const redirectUri = this.resolveRedirectUri(idp.REDIRECT_URI);
418
+ const postLogoutRedirectUri = this.resolveRedirectUri(idp.POST_LOGOUT_REDIRECT_URI);
419
+ return {
420
+ issuer: idp.ISSUER,
421
+ clientId: idp.CLIENT_ID,
422
+ redirectUri,
423
+ postLogoutRedirectUri,
424
+ responseType: idp.RESPONSE_TYPE || 'code',
425
+ scope: idp.SCOPE || 'openid profile email offline_access',
426
+ strictDiscoveryDocumentValidation: idp.STRICT_DISCOVERY_DOCUMENT_VALIDATION ?? true,
427
+ showDebugInformation: idp.SHOW_DEBUG_INFORMATION ?? false,
428
+ useSilentRefresh: false,
429
+ sessionChecksEnabled: false,
430
+ };
431
+ }
432
+ resolveRedirectUri(uri) {
433
+ if (!uri)
434
+ return undefined;
435
+ try {
436
+ const parsed = new URL(uri);
437
+ // Replace origin (scheme + host + port) with the actual runtime origin
438
+ return window.location.origin + parsed.pathname;
439
+ }
440
+ catch {
441
+ return uri;
442
+ }
443
+ }
444
+ subscribeToOAuthEvents() {
445
+ const evtSignal = this.oauthEvt;
446
+ this.oauth.events.pipe(startWith(null)).subscribe((e) => {
447
+ evtSignal.set(e);
448
+ if (!e)
449
+ return;
450
+ if (e instanceof OAuthErrorEvent) {
451
+ console.error('[SECURITY] OAuthErrorEvent:', e.type, e.params, e.reason);
452
+ this.tokenManager.clearSchedule();
453
+ this.profileResolver.clear();
454
+ return;
455
+ }
456
+ switch (e.type) {
457
+ case 'token_received':
458
+ this.tokenManager.scheduleAutoRefresh(this.oauth);
459
+ void this.profileResolver.loadFromIdp();
460
+ break;
461
+ case 'token_refreshed':
462
+ this.tokenManager.scheduleAutoRefresh(this.oauth);
463
+ void this.profileResolver.loadFromIdp();
464
+ break;
465
+ case 'logout':
466
+ this.tokenManager.clearSchedule();
467
+ this.profileResolver.clear();
468
+ break;
469
+ case 'session_terminated':
470
+ case 'token_refresh_error':
471
+ this.logout();
472
+ break;
473
+ default:
474
+ break;
475
+ }
476
+ });
477
+ }
478
+ }
479
+
480
+ // Provides SecurityService + OAuthService + APP_INITIALIZER for auth bootstrap.
481
+ // Uses provideOAuthClient() from angular-oauth2-oidc for OAuthService registration.
482
+ // IMPORTANT: The consumer app must set `preserveSymlinks: true` in angular.json
483
+ // to prevent Vite from bundling a duplicate copy of @angular/core from the
484
+ // library's node_modules (which would cause NG0203 at runtime).
485
+ function provideSecurity() {
486
+ return makeEnvironmentProviders([
487
+ provideOAuthClient(),
488
+ {
489
+ provide: SecurityService,
490
+ useFactory: (platformId, oauth, router, configService, destroyRef) => new SecurityService(isPlatformBrowser(platformId), oauth, router, () => configService.get('IDP') ?? null, destroyRef),
491
+ deps: [PLATFORM_ID, OAuthService, Router, ConfigService, DestroyRef],
492
+ },
493
+ {
494
+ provide: APP_INITIALIZER,
495
+ multi: true,
496
+ useFactory: (securityService) => () => securityService.init(),
497
+ deps: [SecurityService],
498
+ },
499
+ ]);
500
+ }
501
+ // Testing provider: provides a no-op SecurityService.
502
+ function provideSecurityTesting() {
503
+ return [
504
+ {
505
+ provide: SecurityService,
506
+ useFactory: (platformId, router) => new SecurityService(isPlatformBrowser(platformId), { events: { pipe: () => ({ subscribe: () => { } }) } }, router, () => null, { onDestroy: () => { } }),
507
+ deps: [PLATFORM_ID, Router],
508
+ },
509
+ ];
11
510
  }
12
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: EacArchInfrastructureSecurity, decorators: [{
13
- type: Component,
14
- args: [{ selector: 'eac-arch-eac-arch-infrastructure-security', imports: [], template: `
15
- <p>
16
- eac-arch-infrastructure-security works!
17
- </p>
18
- ` }]
19
- }] });
20
511
 
21
- /*
22
- * Public API Surface of eac-arch-infrastructure-security
512
+ const authGuard = (_route, state) => {
513
+ const platformId = inject(PLATFORM_ID);
514
+ const security = inject(SecurityService);
515
+ if (!isPlatformBrowser(platformId))
516
+ return false;
517
+ if (!security.isAuthenticated()) {
518
+ security.login(state.url);
519
+ return false;
520
+ }
521
+ return true;
522
+ };
523
+
524
+ const authzGuard = (route) => {
525
+ const router = inject(Router);
526
+ const authService = inject(SecurityService);
527
+ if (!authService.isAuthenticated()) {
528
+ return router.parseUrl('/');
529
+ }
530
+ const data = route.data?.['auth'] ?? {};
531
+ const any = data.any ?? [];
532
+ const all = data.all ?? [];
533
+ const scopesAny = data.scopesAny ?? [];
534
+ const ok = authService.hasAnyRole(any) &&
535
+ authService.hasAllRoles(all) &&
536
+ authService.hasAnyScope(scopesAny);
537
+ return ok || router.parseUrl('/security/forbidden');
538
+ };
539
+
540
+ const authInterceptor = (req, next) => {
541
+ const config = inject(ConfigService);
542
+ const security = inject(SecurityService);
543
+ const requestUrl = req.url ?? '';
544
+ if (!requestUrl) {
545
+ return next(req);
546
+ }
547
+ const token = security.token();
548
+ if (!token)
549
+ return next(req);
550
+ const keys = config.get('SECURITY')?.SECURE_API_KEYS ?? [];
551
+ const isSecure = keys
552
+ .map(key => config.get(key))
553
+ .filter((url) => !!url)
554
+ .some(url => requestUrl.startsWith(url));
555
+ if (isSecure) {
556
+ return next(req.clone({
557
+ setHeaders: { Authorization: `Bearer ${token}` },
558
+ }));
559
+ }
560
+ return next(req);
561
+ };
562
+
563
+ /**
564
+ * Landing component for the OAuth callback route.
565
+ *
566
+ * The APP_INITIALIZER (SecurityService.init) should have already
567
+ * exchanged the authorization code. If it failed for any reason
568
+ * this component retries the code exchange as a fallback.
23
569
  */
570
+ class OidcCallback {
571
+ oauth = inject(OAuthService);
572
+ router = inject(Router);
573
+ pid = inject(PLATFORM_ID);
574
+ async ngOnInit() {
575
+ if (!isPlatformBrowser(this.pid))
576
+ return;
577
+ // If init() already exchanged the code, just navigate.
578
+ // Otherwise retry the exchange here (fallback).
579
+ if (!this.oauth.hasValidAccessToken()) {
580
+ try {
581
+ await this.oauth.loadDiscoveryDocument();
582
+ await this.oauth.tryLoginCodeFlow();
583
+ }
584
+ catch (err) {
585
+ console.error('[SECURITY] OidcCallback code exchange failed:', err);
586
+ }
587
+ }
588
+ if (this.oauth.hasValidAccessToken()) {
589
+ const obj = (stateDecodeFromUrlParam(this.oauth.state) ?? {});
590
+ const rel = safeRel(obj['rel'] || '/');
591
+ window.history.replaceState({}, document.title, '/');
592
+ this.router.navigateByUrl(rel, { replaceUrl: true });
593
+ }
594
+ else {
595
+ console.warn('[SECURITY] No valid token after callback, redirecting home');
596
+ this.router.navigateByUrl('/', { replaceUrl: true });
597
+ }
598
+ }
599
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: OidcCallback, deps: [], target: i0.ɵɵFactoryTarget.Component });
600
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.1.3", type: OidcCallback, isStandalone: true, selector: "eac-oidc-callback", ngImport: i0, template: '', isInline: true, styles: [""] });
601
+ }
602
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: OidcCallback, decorators: [{
603
+ type: Component,
604
+ args: [{ selector: 'eac-oidc-callback', standalone: true, template: '' }]
605
+ }] });
24
606
 
25
607
  /**
26
608
  * Generated bundle index. Do not edit.
27
609
  */
28
610
 
29
- export { EacArchInfrastructureSecurity };
611
+ export { OidcCallback, SecurityService, authGuard, authInterceptor, authzGuard, provideSecurity, safeRel, stateDecode, stateDecodeFromUrlParam, stateEncode };
30
612
  //# sourceMappingURL=eac-arch-infrastructure-security.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"eac-arch-infrastructure-security.mjs","sources":["../../../projects/eac-arch-infrastructure-security/src/lib/eac-arch-infrastructure-security.ts","../../../projects/eac-arch-infrastructure-security/src/public-api.ts","../../../projects/eac-arch-infrastructure-security/src/eac-arch-infrastructure-security.ts"],"sourcesContent":["import { Component } from '@angular/core';\r\n\r\n@Component({\r\n selector: 'eac-arch-eac-arch-infrastructure-security',\r\n imports: [],\r\n template: `\r\n <p>\r\n eac-arch-infrastructure-security works!\r\n </p>\r\n `,\r\n styles: ``,\r\n})\r\nexport class EacArchInfrastructureSecurity {\r\n\r\n}\r\n","/*\r\n * Public API Surface of eac-arch-infrastructure-security\r\n */\r\n\r\nexport * from './lib/eac-arch-infrastructure-security';\r\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":";;;MAYa,6BAA6B,CAAA;uGAA7B,6BAA6B,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAA7B,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,6BAA6B,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,2CAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,EAP9B,CAAA;;;;AAIT,EAAA,CAAA,EAAA,QAAA,EAAA,IAAA,EAAA,MAAA,EAAA,CAAA,EAAA,CAAA,EAAA,CAAA;;2FAGU,6BAA6B,EAAA,UAAA,EAAA,CAAA;kBAVzC,SAAS;+BACE,2CAA2C,EAAA,OAAA,EAC5C,EAAE,EAAA,QAAA,EACD,CAAA;;;;AAIT,EAAA,CAAA,EAAA;;;ACTH;;AAEG;;ACFH;;AAEG;;;;"}
1
+ {"version":3,"file":"eac-arch-infrastructure-security.mjs","sources":["../../../projects/eac-arch-infrastructure-security/src/lib/utils/oauth-state.util.ts","../../../projects/eac-arch-infrastructure-security/src/lib/services/claims-parser.ts","../../../projects/eac-arch-infrastructure-security/src/lib/services/graph-profile-loader.ts","../../../projects/eac-arch-infrastructure-security/src/lib/services/profile-resolver.ts","../../../projects/eac-arch-infrastructure-security/src/lib/services/token-manager.ts","../../../projects/eac-arch-infrastructure-security/src/lib/services/security.service.ts","../../../projects/eac-arch-infrastructure-security/src/lib/security.provider.ts","../../../projects/eac-arch-infrastructure-security/src/lib/guards/auth.guard.ts","../../../projects/eac-arch-infrastructure-security/src/lib/guards/authz.guard.ts","../../../projects/eac-arch-infrastructure-security/src/lib/interceptors/auth.interceptor.ts","../../../projects/eac-arch-infrastructure-security/src/lib/components/oidc-callback.ts","../../../projects/eac-arch-infrastructure-security/src/eac-arch-infrastructure-security.ts"],"sourcesContent":["const DEFAULT_SEPARATOR = ';';\n\nexport function stateEncode(obj: unknown): string {\n const s = JSON.stringify(obj);\n return btoa(s).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/g, '');\n}\n\nexport function stateDecode(s: string): unknown | null {\n try {\n const pad = s.length % 4 ? '='.repeat(4 - (s.length % 4)) : '';\n const b64 = s.replace(/-/g, '+').replace(/_/g, '/') + pad;\n return JSON.parse(atob(b64));\n } catch {\n return null;\n }\n}\n\nexport function stateDecodeFromUrlParam(s?: string | null): Record<string, unknown> | null {\n if (!s) return null;\n let decoded = s;\n if (/%[0-9A-Fa-f]{2}/.test(decoded)) {\n try {\n decoded = decodeURIComponent(decoded);\n } catch {\n /* ignore */\n }\n }\n return stateDecode(decoded) as Record<string, unknown> | null;\n}\n\nexport function safeRel(rel: unknown): string {\n let r = typeof rel === 'string' ? rel : '/';\n if (!r.startsWith('/')) r = '/' + r;\n try {\n const u = new URL(r, window.location.origin);\n if (u.origin !== window.location.origin) return '/';\n } catch {\n return '/';\n }\n return r;\n}\n","// Pure functions for extracting roles, scopes and user info from JWT/OIDC claims.\n// Supports both Azure AD and Keycloak claim formats.\n\nexport function extractRoles(claims: Record<string, unknown>): string[] {\n const out = new Set<string>();\n\n // Azure AD\n if (Array.isArray(claims['roles']))\n for (const r of claims['roles'] as string[]) out.add(String(r).toLowerCase());\n if (Array.isArray(claims['groups']))\n for (const g of claims['groups'] as string[]) out.add(String(g).toLowerCase());\n\n // Keycloak\n const realmAccess = claims['realm_access'] as { roles?: string[] } | undefined;\n if (Array.isArray(realmAccess?.roles))\n for (const r of realmAccess!.roles!) out.add(String(r).toLowerCase());\n\n const ra = claims['resource_access'] as Record<string, { roles?: string[] } | undefined> | undefined;\n if (ra)\n for (const k of Object.keys(ra)) ra[k]?.roles?.forEach((r: string) => out.add(String(r).toLowerCase()));\n\n return Array.from(out);\n}\n\nexport function extractScopes(claims: Record<string, unknown>): string[] {\n // Azure AD uses 'scp', Keycloak uses 'scope'\n const raw = (claims['scp'] ?? claims['scope']) as string | undefined;\n if (typeof raw !== 'string' || !raw) return [];\n return raw.trim().split(/\\s+/).map((s: string) => s.toLowerCase());\n}\n\nexport function mapClaimsToUserinfo(claims: Record<string, unknown>): Record<string, unknown> {\n const first = (claims['given_name'] ?? claims['givenName'] ?? claims['firstName'] ?? null) as string | null;\n const last = (claims['family_name'] ?? claims['familyName'] ?? claims['lastName'] ?? null) as string | null;\n const display =\n ((claims['name'] as string) ?? [first, last].filter(Boolean).join(' ')) ||\n (claims['preferred_username'] as string) ||\n (claims['email'] as string) ||\n (claims['sub'] as string) ||\n '';\n return {\n sub: claims['sub'] ?? claims['oid'] ?? null,\n name: display,\n preferred_username: claims['preferred_username'] ?? claims['upn'] ?? null,\n given_name: first,\n family_name: last,\n email: claims['email'] ?? null,\n picture: claims['picture'] ?? null,\n };\n}\n\nexport function decodeJwtPayload(jwt: string | null): Record<string, unknown> | null {\n if (!jwt) return null;\n const p = jwt.split('.')[1] ?? '';\n try {\n const pad = p.length % 4 ? '='.repeat(4 - (p.length % 4)) : '';\n const b64 = p.replace(/-/g, '+').replace(/_/g, '/') + pad;\n return JSON.parse(atob(b64));\n } catch {\n return null;\n }\n}\n\nexport function hasAny(have: string[], required: string[]): boolean {\n if (!required?.length) return true;\n const set = new Set(have);\n return required.some((r) => set.has(r.toLowerCase()));\n}\n\nexport function hasAll(have: string[], required: string[]): boolean {\n if (!required?.length) return true;\n const set = new Set(have);\n return required.every((r) => set.has(r.toLowerCase()));\n}\n","import { signal } from '@angular/core';\nimport { OAuthService } from 'angular-oauth2-oidc';\nimport { IdpConfig } from '../models/idp-config';\n\n// Handles Microsoft Graph API profile loading via refresh token exchange.\n// Only active when the IDP issuer is Azure AD.\nexport class GraphProfileLoader {\n private readonly accessToken = signal<string | null>(null);\n private accessTokenExp = 0;\n\n constructor(\n private readonly oauth: OAuthService,\n private readonly getIdpConfig: () => IdpConfig | null,\n ) {}\n\n isAzure(): boolean {\n const issuer =\n (this.oauth as unknown as { issuer?: string })?.issuer ||\n this.getIdpConfig()?.ISSUER ||\n '';\n return /login\\.microsoftonline\\.com/i.test(issuer) || /sts\\.windows\\.net/i.test(issuer);\n }\n\n async loadProfile(): Promise<Record<string, unknown> | null> {\n const token = await this.ensureToken();\n if (!token) return null;\n\n const resp = await fetch(\n 'https://graph.microsoft.com/v1.0/me?$select=displayName,givenName,surname,mail,userPrincipalName,id',\n { headers: { Authorization: `Bearer ${token}` } },\n );\n\n if (!resp.ok) return null;\n\n const me = (await resp.json()) as Record<string, unknown>;\n return {\n sub: me['id'] ?? null,\n name: me['displayName'] ?? null,\n preferred_username: me['userPrincipalName'] ?? null,\n given_name: me['givenName'] ?? null,\n family_name: me['surname'] ?? null,\n email: me['mail'] ?? me['userPrincipalName'] ?? null,\n };\n }\n\n private async ensureToken(): Promise<string | null> {\n const cached = this.accessToken();\n if (cached && Date.now() < this.accessTokenExp) return cached;\n return this.exchangeRefreshToken();\n }\n\n private async exchangeRefreshToken(): Promise<string | null> {\n const refresh = this.getRefreshToken();\n const tokenEndpoint = this.oauth.tokenEndpoint;\n const clientId =\n (this.oauth as unknown as { clientId?: string })?.clientId || this.getIdpConfig()?.CLIENT_ID;\n\n if (!refresh || !tokenEndpoint || !clientId) return null;\n\n const scopes = this.getGraphScopes();\n const body = new URLSearchParams({\n grant_type: 'refresh_token',\n client_id: clientId,\n refresh_token: refresh,\n scope: scopes.join(' '),\n });\n\n const resp = await fetch(tokenEndpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body,\n });\n\n if (!resp.ok) return null;\n\n const json = (await resp.json()) as Record<string, unknown>;\n const at = json['access_token'] as string | undefined;\n const expiresIn = Number(json['expires_in'] ?? 3600);\n\n if (at) {\n this.accessToken.set(at);\n this.accessTokenExp = Date.now() + Math.max(0, (expiresIn - 60) * 1000);\n\n try {\n const rt = json['refresh_token'] as string | undefined;\n if (rt) {\n const storage = (this.oauth as unknown as { storage?: { setItem(k: string, v: string): void } })?.storage;\n storage?.setItem('refresh_token', rt);\n }\n } catch {\n // ignore\n }\n\n return at;\n }\n\n return null;\n }\n\n private getRefreshToken(): string | null {\n try {\n const o = this.oauth as unknown as {\n getRefreshToken?: () => string;\n storage?: { getItem(key: string): string | null };\n };\n const rt = o.getRefreshToken?.() ?? o.storage?.getItem('refresh_token') ?? null;\n return typeof rt === 'string' && rt ? rt : null;\n } catch {\n return null;\n }\n }\n\n private getGraphScopes(): string[] {\n const idp = this.getIdpConfig();\n const scopes = idp?.GRAPH_SCOPES || 'https://graph.microsoft.com/User.Read openid profile';\n return scopes.trim().split(/\\s+/).filter(Boolean);\n }\n}\n","import { computed, Signal, WritableSignal } from '@angular/core';\nimport { OAuthService } from 'angular-oauth2-oidc';\n\nimport { UserProfile } from '../models/user-profile';\nimport { mapClaimsToUserinfo } from './claims-parser';\nimport { GraphProfileLoader } from './graph-profile-loader';\n\n// Resolves and builds the user profile from OIDC claims, userinfo endpoint, and Graph API.\nexport class ProfileResolver {\n readonly profile: Signal<UserProfile | null>;\n\n constructor(\n private readonly userinfo: WritableSignal<Record<string, unknown> | null>,\n private readonly rolesSignal: Signal<string[]>,\n private readonly oauth: OAuthService,\n private readonly graphLoader: GraphProfileLoader,\n private readonly getPayload: () => Record<string, unknown> | null,\n ) {\n this.profile = computed(() => this.buildProfile());\n }\n\n async loadFromIdp(): Promise<void> {\n const roles = this.rolesSignal();\n try {\n const info = (await this.oauth.loadUserProfile()) as Record<string, unknown>;\n const claims = this.resolveClaims();\n let userinfo = { ...mapClaimsToUserinfo(claims), ...info } as Record<string, unknown>;\n\n if (this.graphLoader.isAzure()) {\n const graphInfo = await this.graphLoader.loadProfile();\n if (graphInfo) userinfo = { ...userinfo, ...graphInfo };\n }\n\n userinfo['roles'] = Array.isArray(info?.['roles']) && (info['roles'] as unknown[]).length\n ? info['roles'] : roles;\n\n this.userinfo.set(userinfo);\n } catch {\n await this.loadFallback(roles);\n }\n }\n\n clear(): void {\n this.userinfo.set(null);\n }\n\n private async loadFallback(roles: string[]): Promise<void> {\n if (this.graphLoader.isAzure()) {\n try {\n const graphInfo = await this.graphLoader.loadProfile();\n if (graphInfo) {\n const claims = this.resolveClaims();\n this.userinfo.set({ ...mapClaimsToUserinfo(claims), ...graphInfo, roles });\n return;\n }\n } catch {\n // Graph fallback failed\n }\n }\n\n const claims = this.resolveClaims();\n this.userinfo.set({ ...mapClaimsToUserinfo(claims), roles });\n }\n\n private resolveClaims(): Record<string, unknown> {\n return ((this.oauth.getIdentityClaims() as Record<string, unknown>) ?? this.getPayload()) ?? {};\n }\n\n private buildProfile(): UserProfile | null {\n const c = this.userinfo();\n if (!c) return null;\n\n const first = (c['given_name'] ?? c['givenName'] ?? c['firstName'] ?? null) as string | null;\n const last = (c['family_name'] ?? c['familyName'] ?? c['lastName'] ?? null) as string | null;\n const display = ((c['name'] as string) ?? [first, last].filter(Boolean).join(' ')) ||\n (c['preferred_username'] as string) || (c['email'] as string) || (c['sub'] as string) || '';\n\n return {\n sub: (c['sub'] as string) ?? null,\n displayName: display,\n firstName: first,\n lastName: last,\n email: (c['email'] as string) ?? null,\n username: (c['preferred_username'] as string) ?? (c['upn'] as string) ?? null,\n pictureUrl: (c['picture'] as string) ?? null,\n roles: Array.isArray(c['roles']) ? (c['roles'] as string[]) : this.rolesSignal(),\n raw: c,\n };\n }\n}\n","import { OAuthService } from 'angular-oauth2-oidc';\n\n// Manages token lifecycle: schedules automatic refresh before expiration\n// and falls back to logout if refresh fails.\nexport class TokenManager {\n private refreshTimer: ReturnType<typeof setTimeout> | null = null;\n\n // Refresh 60 seconds before expiration to avoid using an expired token.\n private readonly REFRESH_BEFORE_MS = 60_000;\n\n constructor(private readonly onExpired: () => void) {}\n\n scheduleAutoRefresh(oauth: OAuthService): void {\n this.clearSchedule();\n\n const expMs = oauth.getAccessTokenExpiration?.();\n if (!expMs || expMs <= 0) return;\n\n const delay = Math.max(0, expMs - Date.now() - this.REFRESH_BEFORE_MS);\n\n this.refreshTimer = setTimeout(() => {\n oauth.refreshToken()\n .then(() => this.scheduleAutoRefresh(oauth))\n .catch(() => this.onExpired());\n }, delay);\n }\n\n clearSchedule(): void {\n if (this.refreshTimer) {\n clearTimeout(this.refreshTimer);\n this.refreshTimer = null;\n }\n }\n}\n","import { computed, signal, Signal } from '@angular/core';\nimport { Router } from '@angular/router';\nimport { startWith } from 'rxjs';\n\nimport { AuthConfig, OAuthErrorEvent, OAuthEvent, OAuthService } from 'angular-oauth2-oidc';\n\nimport { IdpConfig } from '../models/idp-config';\nimport { UserProfile } from '../models/user-profile';\n\nimport { stateEncode } from '../utils/oauth-state.util';\nimport { decodeJwtPayload, extractRoles, extractScopes, hasAll, hasAny } from './claims-parser';\nimport { GraphProfileLoader } from './graph-profile-loader';\nimport { ProfileResolver } from './profile-resolver';\nimport { TokenManager } from './token-manager';\n\n// Plain class (no @Injectable) - provided via useFactory with deps.\nexport class SecurityService {\n private readonly oauthEvt: Signal<OAuthEvent | null>;\n private readonly accessToken: Signal<string | null>;\n private readonly payload: Signal<Record<string, unknown> | null>;\n private readonly tokenManager: TokenManager;\n private readonly profileResolver: ProfileResolver;\n\n readonly isLoggedIn: Signal<boolean>;\n readonly profile: Signal<UserProfile | null>;\n readonly rolesSignal: Signal<string[]>;\n readonly scopesSignal: Signal<string[]>;\n\n constructor(\n private readonly isBrowser: boolean,\n private readonly oauth: OAuthService,\n private readonly router: Router,\n private readonly getIdpConfig: () => IdpConfig | null,\n destroyRef: { onDestroy: (fn: () => void) => void },\n ) {\n this.tokenManager = new TokenManager(() => this.logout());\n const graphLoader = new GraphProfileLoader(oauth, getIdpConfig);\n\n this.oauthEvt = signal<OAuthEvent | null>(null);\n this.accessToken = computed(() => (this.oauthEvt(), this.oauth.getAccessToken() ?? null));\n this.payload = computed(() => decodeJwtPayload(this.accessToken()));\n this.isLoggedIn = computed(() => (this.oauthEvt(), this.oauth.hasValidAccessToken()));\n this.scopesSignal = computed(() => extractScopes(this.payload() ?? {}));\n this.rolesSignal = computed(() => {\n const claims = this.payload() ?? (this.oauth.getIdentityClaims() as Record<string, unknown>) ?? {};\n return extractRoles(claims);\n });\n\n const userinfo = signal<Record<string, unknown> | null>(null);\n this.profileResolver = new ProfileResolver(userinfo, this.rolesSignal, oauth, graphLoader, () => this.payload());\n this.profile = this.profileResolver.profile;\n\n if (this.isBrowser) {\n this.subscribeToOAuthEvents();\n destroyRef.onDestroy(() => this.tokenManager.clearSchedule());\n }\n }\n\n async init(): Promise<void> {\n if (!this.isBrowser) return;\n\n const idp = this.getIdpConfig();\n if (!idp) {\n console.warn('[SECURITY] No IDP config found, skipping init');\n return;\n }\n\n // Force sessionStorage so tokens and nonce survive redirects.\n // The DI override in provideSecurity() may not take effect due to\n // module duplication with symlinked libraries, so we set it explicitly.\n this.oauth.setStorage(sessionStorage);\n\n const authConfig = this.buildAuthConfig(idp);\n this.oauth.configure(authConfig);\n\n try {\n await this.oauth.loadDiscoveryDocumentAndTryLogin();\n } catch (err) {\n console.error('[SECURITY] loadDiscoveryDocumentAndTryLogin failed:', err);\n }\n\n if (this.oauth.hasValidAccessToken()) {\n this.tokenManager.scheduleAutoRefresh(this.oauth);\n await this.profileResolver.loadFromIdp();\n }\n }\n\n token(): string | null { return this.accessToken(); }\n isAuthenticated(): boolean { return this.oauth.hasValidAccessToken(); }\n roles(): string[] { return this.rolesSignal(); }\n scopes(): string[] { return this.scopesSignal(); }\n hasAnyRole(required: string[]): boolean { return hasAny(this.rolesSignal(), required); }\n hasAllRoles(required: string[]): boolean { return hasAll(this.rolesSignal(), required); }\n hasAnyScope(required: string[]): boolean { return hasAny(this.scopesSignal(), required); }\n\n login(target?: string): void {\n const rel = target || this.router.url || '/';\n this.oauth.initLoginFlow(stateEncode({ rel, t: Date.now() }));\n }\n\n logout(): void {\n const idp = this.getIdpConfig();\n this.oauth.logOut({ post_logout_redirect_uri: idp?.POST_LOGOUT_REDIRECT_URI });\n }\n\n // -- Private --\n\n private buildAuthConfig(idp: IdpConfig): AuthConfig {\n // Use the configured REDIRECT_URI path but resolve it against the current\n // origin so that port changes in development (e.g. Vite picking a random\n // port when 4200 is busy) don't break the nonce/PKCE validation.\n const redirectUri = this.resolveRedirectUri(idp.REDIRECT_URI);\n const postLogoutRedirectUri = this.resolveRedirectUri(idp.POST_LOGOUT_REDIRECT_URI);\n\n return {\n issuer: idp.ISSUER,\n clientId: idp.CLIENT_ID,\n redirectUri,\n postLogoutRedirectUri,\n responseType: idp.RESPONSE_TYPE || 'code',\n scope: idp.SCOPE || 'openid profile email offline_access',\n strictDiscoveryDocumentValidation: idp.STRICT_DISCOVERY_DOCUMENT_VALIDATION ?? true,\n showDebugInformation: idp.SHOW_DEBUG_INFORMATION ?? false,\n useSilentRefresh: false,\n sessionChecksEnabled: false,\n };\n }\n\n private resolveRedirectUri(uri: string | undefined): string | undefined {\n if (!uri) return undefined;\n try {\n const parsed = new URL(uri);\n // Replace origin (scheme + host + port) with the actual runtime origin\n return window.location.origin + parsed.pathname;\n } catch {\n return uri;\n }\n }\n\n private subscribeToOAuthEvents(): void {\n const evtSignal = this.oauthEvt as ReturnType<typeof signal<OAuthEvent | null>>;\n\n this.oauth.events.pipe(startWith(null)).subscribe((e) => {\n evtSignal.set(e);\n if (!e) return;\n\n if (e instanceof OAuthErrorEvent) {\n console.error('[SECURITY] OAuthErrorEvent:', e.type, (e as OAuthErrorEvent).params, (e as OAuthErrorEvent).reason);\n this.tokenManager.clearSchedule();\n this.profileResolver.clear();\n return;\n }\n\n switch (e.type) {\n case 'token_received':\n this.tokenManager.scheduleAutoRefresh(this.oauth);\n void this.profileResolver.loadFromIdp();\n break;\n case 'token_refreshed':\n this.tokenManager.scheduleAutoRefresh(this.oauth);\n void this.profileResolver.loadFromIdp();\n break;\n case 'logout':\n this.tokenManager.clearSchedule();\n this.profileResolver.clear();\n break;\n case 'session_terminated':\n case 'token_refresh_error':\n this.logout();\n break;\n default:\n break;\n }\n });\n }\n\n}\n","import { APP_INITIALIZER, DestroyRef, EnvironmentProviders, makeEnvironmentProviders, PLATFORM_ID, Provider } from '@angular/core';\nimport { isPlatformBrowser } from '@angular/common';\nimport { Router } from '@angular/router';\nimport { OAuthService, provideOAuthClient } from 'angular-oauth2-oidc';\nimport { ConfigService } from '@eac-arch/infrastructure-config';\n\nimport { SecurityService } from './services/security.service';\nimport { IdpConfig } from './models/idp-config';\n\n// Provides SecurityService + OAuthService + APP_INITIALIZER for auth bootstrap.\n// Uses provideOAuthClient() from angular-oauth2-oidc for OAuthService registration.\n// IMPORTANT: The consumer app must set `preserveSymlinks: true` in angular.json\n// to prevent Vite from bundling a duplicate copy of @angular/core from the\n// library's node_modules (which would cause NG0203 at runtime).\nexport function provideSecurity(): EnvironmentProviders {\n return makeEnvironmentProviders([\n provideOAuthClient(),\n\n {\n provide: SecurityService,\n useFactory: (\n platformId: object,\n oauth: OAuthService,\n router: Router,\n configService: ConfigService,\n destroyRef: DestroyRef,\n ) =>\n new SecurityService(\n isPlatformBrowser(platformId),\n oauth,\n router,\n () => configService.get<IdpConfig>('IDP') ?? null,\n destroyRef,\n ),\n deps: [PLATFORM_ID, OAuthService, Router, ConfigService, DestroyRef],\n },\n\n {\n provide: APP_INITIALIZER,\n multi: true,\n useFactory: (securityService: SecurityService) => () => securityService.init(),\n deps: [SecurityService],\n },\n ]);\n}\n\n// Testing provider: provides a no-op SecurityService.\nexport function provideSecurityTesting(): Provider[] {\n return [\n {\n provide: SecurityService,\n useFactory: (platformId: object, router: Router) =>\n new SecurityService(\n isPlatformBrowser(platformId),\n { events: { pipe: () => ({ subscribe: () => {} }) } } as unknown as OAuthService,\n router,\n () => null,\n { onDestroy: () => {} },\n ),\n deps: [PLATFORM_ID, Router],\n },\n ];\n}\n","import { inject, PLATFORM_ID } from '@angular/core';\nimport { isPlatformBrowser } from '@angular/common';\nimport { CanActivateFn } from '@angular/router';\nimport { SecurityService } from '../services/security.service';\n\nexport const authGuard: CanActivateFn = (_route, state) => {\n const platformId = inject(PLATFORM_ID);\n const security = inject(SecurityService);\n\n if (!isPlatformBrowser(platformId)) return false;\n\n if (!security.isAuthenticated()) {\n security.login(state.url);\n return false;\n }\n\n return true;\n};\n","import { inject } from '@angular/core';\nimport { CanActivateFn, Router } from '@angular/router';\nimport { SecurityService } from '../services/security.service';\n\nexport const authzGuard: CanActivateFn = (route) => {\n const router = inject(Router);\n const authService = inject(SecurityService);\n\n if (!authService.isAuthenticated()) {\n return router.parseUrl('/');\n }\n\n const data = (route.data as Record<string, unknown>)?.['auth'] as {\n any?: string[];\n all?: string[];\n scopesAny?: string[];\n } | undefined ?? {};\n\n const any: string[] = data.any ?? [];\n const all: string[] = data.all ?? [];\n const scopesAny: string[] = data.scopesAny ?? [];\n\n const ok =\n authService.hasAnyRole(any) &&\n authService.hasAllRoles(all) &&\n authService.hasAnyScope(scopesAny);\n\n return ok || router.parseUrl('/security/forbidden');\n};\n","import { HttpInterceptorFn } from '@angular/common/http';\nimport { inject } from '@angular/core';\nimport { SecurityService } from '../services/security.service';\nimport { ConfigService } from '@eac-arch/infrastructure-config';\nimport { SecurityConfig } from '../models/security-options';\n\nexport const authInterceptor: HttpInterceptorFn = (req, next) => {\n const config = inject(ConfigService);\n const security = inject(SecurityService);\n const requestUrl = req.url ?? '';\n\n if (!requestUrl) {\n return next(req);\n }\n\n const token = security.token();\n if (!token) return next(req);\n\n const keys = config.get<SecurityConfig>('SECURITY')?.SECURE_API_KEYS ?? [];\n\n const isSecure = keys\n .map(key => config.get<string>(key))\n .filter((url): url is string => !!url)\n .some(url => requestUrl.startsWith(url));\n\n if (isSecure) {\n return next(req.clone({\n setHeaders: { Authorization: `Bearer ${token}` },\n }));\n }\n\n return next(req);\n};\n","import { isPlatformBrowser } from '@angular/common';\nimport { Component, inject, OnInit, PLATFORM_ID } from '@angular/core';\nimport { Router } from '@angular/router';\nimport { OAuthService } from 'angular-oauth2-oidc';\n\nimport { safeRel, stateDecodeFromUrlParam } from '../utils/oauth-state.util';\n\n/**\n * Landing component for the OAuth callback route.\n *\n * The APP_INITIALIZER (SecurityService.init) should have already\n * exchanged the authorization code. If it failed for any reason\n * this component retries the code exchange as a fallback.\n */\n@Component({\n selector: 'eac-oidc-callback',\n standalone: true,\n template: '',\n styles: '',\n})\nexport class OidcCallback implements OnInit {\n private readonly oauth = inject(OAuthService);\n private readonly router = inject(Router);\n private readonly pid = inject(PLATFORM_ID);\n\n async ngOnInit(): Promise<void> {\n if (!isPlatformBrowser(this.pid)) return;\n\n // If init() already exchanged the code, just navigate.\n // Otherwise retry the exchange here (fallback).\n if (!this.oauth.hasValidAccessToken()) {\n try {\n await this.oauth.loadDiscoveryDocument();\n await this.oauth.tryLoginCodeFlow();\n } catch (err) {\n console.error('[SECURITY] OidcCallback code exchange failed:', err);\n }\n }\n\n if (this.oauth.hasValidAccessToken()) {\n const obj = (stateDecodeFromUrlParam(this.oauth.state) ?? {}) as Record<string, unknown>;\n const rel = safeRel(obj['rel'] || '/');\n window.history.replaceState({}, document.title, '/');\n this.router.navigateByUrl(rel, { replaceUrl: true });\n } else {\n console.warn('[SECURITY] No valid token after callback, redirecting home');\n this.router.navigateByUrl('/', { replaceUrl: true });\n }\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,12 +1,15 @@
1
1
  {
2
2
  "name": "@eac-arch/infrastructure-security",
3
- "version": "1.0.1",
3
+ "version": "1.0.6",
4
4
  "peerDependencies": {
5
5
  "@angular/common": "^21.1.0",
6
- "@angular/core": "^21.1.0"
6
+ "@angular/core": "^21.1.0",
7
+ "@angular/router": "^21.1.0",
8
+ "angular-oauth2-oidc": ">=17.0.0"
7
9
  },
8
10
  "dependencies": {
9
- "tslib": "^2.3.0"
11
+ "tslib": "^2.3.0",
12
+ "@eac-arch/infrastructure-config": "1.0.6"
10
13
  },
11
14
  "sideEffects": false,
12
15
  "module": "fesm2022/eac-arch-infrastructure-security.mjs",
@@ -20,4 +23,4 @@
20
23
  "default": "./fesm2022/eac-arch-infrastructure-security.mjs"
21
24
  }
22
25
  }
23
- }
26
+ }
@@ -1,8 +1,99 @@
1
1
  import * as i0 from '@angular/core';
2
+ import { Signal, EnvironmentProviders, OnInit } from '@angular/core';
3
+ import { Router, CanActivateFn } from '@angular/router';
4
+ import { OAuthService } from 'angular-oauth2-oidc';
5
+ import { HttpInterceptorFn } from '@angular/common/http';
2
6
 
3
- declare class EacArchInfrastructureSecurity {
4
- static ɵfac: i0.ɵɵFactoryDeclaration<EacArchInfrastructureSecurity, never>;
5
- static ɵcmp: i0.ɵɵComponentDeclaration<EacArchInfrastructureSecurity, "eac-arch-eac-arch-infrastructure-security", never, {}, {}, never, never, true, never>;
7
+ interface IdpConfig {
8
+ ISSUER: string;
9
+ CLIENT_ID: string;
10
+ REDIRECT_URI?: string;
11
+ POST_LOGOUT_REDIRECT_URI?: string;
12
+ RESPONSE_TYPE?: 'code' | 'token';
13
+ SCOPE?: string;
14
+ TOKEN_ENDPOINT?: string;
15
+ STRICT_DISCOVERY_DOCUMENT_VALIDATION?: boolean;
16
+ SHOW_DEBUG_INFORMATION?: boolean;
17
+ REQUIRE_HTTPS?: boolean;
18
+ GRAPH_SCOPES?: string;
6
19
  }
7
20
 
8
- export { EacArchInfrastructureSecurity };
21
+ interface UserProfile {
22
+ sub: string | null;
23
+ displayName: string;
24
+ firstName?: string | null;
25
+ lastName?: string | null;
26
+ email?: string | null;
27
+ username?: string | null;
28
+ pictureUrl?: string | null;
29
+ roles?: string[];
30
+ raw?: unknown;
31
+ }
32
+
33
+ interface SecurityConfig {
34
+ SECURE_API_KEYS?: string[];
35
+ }
36
+
37
+ declare class SecurityService {
38
+ private readonly isBrowser;
39
+ private readonly oauth;
40
+ private readonly router;
41
+ private readonly getIdpConfig;
42
+ private readonly oauthEvt;
43
+ private readonly accessToken;
44
+ private readonly payload;
45
+ private readonly tokenManager;
46
+ private readonly profileResolver;
47
+ readonly isLoggedIn: Signal<boolean>;
48
+ readonly profile: Signal<UserProfile | null>;
49
+ readonly rolesSignal: Signal<string[]>;
50
+ readonly scopesSignal: Signal<string[]>;
51
+ constructor(isBrowser: boolean, oauth: OAuthService, router: Router, getIdpConfig: () => IdpConfig | null, destroyRef: {
52
+ onDestroy: (fn: () => void) => void;
53
+ });
54
+ init(): Promise<void>;
55
+ token(): string | null;
56
+ isAuthenticated(): boolean;
57
+ roles(): string[];
58
+ scopes(): string[];
59
+ hasAnyRole(required: string[]): boolean;
60
+ hasAllRoles(required: string[]): boolean;
61
+ hasAnyScope(required: string[]): boolean;
62
+ login(target?: string): void;
63
+ logout(): void;
64
+ private buildAuthConfig;
65
+ private resolveRedirectUri;
66
+ private subscribeToOAuthEvents;
67
+ }
68
+
69
+ declare function provideSecurity(): EnvironmentProviders;
70
+
71
+ declare const authGuard: CanActivateFn;
72
+
73
+ declare const authzGuard: CanActivateFn;
74
+
75
+ declare const authInterceptor: HttpInterceptorFn;
76
+
77
+ /**
78
+ * Landing component for the OAuth callback route.
79
+ *
80
+ * The APP_INITIALIZER (SecurityService.init) should have already
81
+ * exchanged the authorization code. If it failed for any reason
82
+ * this component retries the code exchange as a fallback.
83
+ */
84
+ declare class OidcCallback implements OnInit {
85
+ private readonly oauth;
86
+ private readonly router;
87
+ private readonly pid;
88
+ ngOnInit(): Promise<void>;
89
+ static ɵfac: i0.ɵɵFactoryDeclaration<OidcCallback, never>;
90
+ static ɵcmp: i0.ɵɵComponentDeclaration<OidcCallback, "eac-oidc-callback", never, {}, {}, never, never, true, never>;
91
+ }
92
+
93
+ declare function stateEncode(obj: unknown): string;
94
+ declare function stateDecode(s: string): unknown | null;
95
+ declare function stateDecodeFromUrlParam(s?: string | null): Record<string, unknown> | null;
96
+ declare function safeRel(rel: unknown): string;
97
+
98
+ export { OidcCallback, SecurityService, authGuard, authInterceptor, authzGuard, provideSecurity, safeRel, stateDecode, stateDecodeFromUrlParam, stateEncode };
99
+ export type { IdpConfig, SecurityConfig, UserProfile };