@acontplus/ng-auth 1.1.2 → 1.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,15 +1,15 @@
1
- import { UserRepository, BaseUseCase, LoggingService, TOKEN_PROVIDER } from '@acontplus/ng-infrastructure';
1
+ import { BaseUseCase, TOKEN_PROVIDER, LoggingService } from '@acontplus/ng-infrastructure';
2
2
  export { TOKEN_PROVIDER } from '@acontplus/ng-infrastructure';
3
3
  import * as i0 from '@angular/core';
4
4
  import { inject, PLATFORM_ID, Injectable, NgZone, signal, input, computed, ViewEncapsulation, ChangeDetectionStrategy, Component } from '@angular/core';
5
5
  import { Router } from '@angular/router';
6
+ import { of, tap, catchError, throwError, firstValueFrom, map, from } from 'rxjs';
6
7
  import * as i1 from '@angular/common';
7
8
  import { isPlatformBrowser, DOCUMENT, CommonModule } from '@angular/common';
8
9
  import { jwtDecode } from 'jwt-decode';
9
10
  import { ENVIRONMENT, AUTH_API } from '@acontplus/ng-config';
10
- import { catchError, switchMap } from 'rxjs/operators';
11
- import { throwError, firstValueFrom, map, from, of, tap, catchError as catchError$1 } from 'rxjs';
12
- import { HttpClient } from '@angular/common/http';
11
+ import { HttpClient, HttpContextToken } from '@angular/common/http';
12
+ import { catchError as catchError$1, switchMap } from 'rxjs/operators';
13
13
  import * as i2 from '@angular/forms';
14
14
  import { FormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
15
15
  import { MatCard, MatCardHeader, MatCardTitle, MatCardContent, MatCardFooter } from '@angular/material/card';
@@ -19,6 +19,9 @@ import { MatIcon } from '@angular/material/icon';
19
19
  import { MatButton, MatAnchor } from '@angular/material/button';
20
20
  import { MatCheckbox } from '@angular/material/checkbox';
21
21
 
22
+ class AuthRepository {
23
+ }
24
+
22
25
  class TokenRepository {
23
26
  environment = inject(ENVIRONMENT);
24
27
  platformId = inject(PLATFORM_ID);
@@ -127,286 +130,81 @@ class TokenRepository {
127
130
  const refreshTokenInLocalStorage = localStorage.getItem(this.environment.refreshTokenKey);
128
131
  return !!(tokenInLocalStorage || refreshTokenInLocalStorage);
129
132
  }
130
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: TokenRepository, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
131
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: TokenRepository, providedIn: 'root' });
132
- }
133
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: TokenRepository, decorators: [{
134
- type: Injectable,
135
- args: [{
136
- providedIn: 'root',
137
- }]
138
- }] });
139
-
140
- /**
141
- * Service to manage URL redirection after authentication
142
- * Stores the intended URL when session is lost and redirects to it after successful login
143
- * SSR-compatible by checking platform before accessing sessionStorage
144
- */
145
- class UrlRedirectService {
146
- REDIRECT_URL_KEY = 'acp_redirect_url';
147
- EXCLUDED_ROUTES = [
148
- '/login',
149
- '/auth',
150
- '/register',
151
- '/forgot-password',
152
- '/reset-password',
153
- ];
154
- router = inject(Router);
155
- platformId = inject(PLATFORM_ID);
156
- document = inject(DOCUMENT);
157
- /**
158
- * Stores the current URL for later redirection
159
- * @param url - The URL to store (defaults to current URL)
160
- */
161
- storeIntendedUrl(url) {
162
- // Only store in browser environment
163
- if (!this.isBrowser()) {
164
- return;
165
- }
166
- const urlToStore = url || this.router.url;
167
- // Don't store authentication-related routes
168
- if (this.isExcludedRoute(urlToStore)) {
169
- return;
170
- }
171
- // Don't store URLs with query parameters that might contain sensitive data
172
- const urlWithoutParams = urlToStore.split('?')[0];
173
- this.getSessionStorage()?.setItem(this.REDIRECT_URL_KEY, urlWithoutParams);
174
- }
175
- /**
176
- * Gets the stored intended URL
177
- * @returns The stored URL or null if none exists
178
- */
179
- getIntendedUrl() {
180
- if (!this.isBrowser()) {
181
- return null;
182
- }
183
- return this.getSessionStorage()?.getItem(this.REDIRECT_URL_KEY) || null;
184
- }
185
- /**
186
- * Redirects to the stored URL and clears it from storage
187
- * @param defaultRoute - The default route to navigate to if no URL is stored
188
- */
189
- redirectToIntendedUrl(defaultRoute = '/') {
190
- const intendedUrl = this.getIntendedUrl();
191
- if (intendedUrl && !this.isExcludedRoute(intendedUrl)) {
192
- this.clearIntendedUrl();
193
- this.router.navigateByUrl(intendedUrl);
194
- }
195
- else {
196
- this.router.navigate([defaultRoute]);
197
- }
198
- }
199
- /**
200
- * Clears the stored intended URL
201
- */
202
- clearIntendedUrl() {
203
- if (!this.isBrowser()) {
204
- return;
205
- }
206
- this.getSessionStorage()?.removeItem(this.REDIRECT_URL_KEY);
207
- }
208
- /**
209
- * Checks if a URL should be excluded from redirection
210
- * @param url - The URL to check
211
- * @returns True if the URL should be excluded
212
- */
213
- isExcludedRoute(url) {
214
- return this.EXCLUDED_ROUTES.some(route => url.includes(route));
215
- }
216
- /**
217
- * Stores the current URL if it's not an excluded route
218
- * Useful for guards and interceptors
219
- */
220
- storeCurrentUrlIfAllowed() {
221
- const currentUrl = this.router.url;
222
- if (!this.isExcludedRoute(currentUrl)) {
223
- this.storeIntendedUrl(currentUrl);
224
- }
225
- }
226
- /**
227
- * Checks if we're running in a browser environment
228
- * @returns True if running in browser, false if SSR
229
- */
230
- isBrowser() {
231
- return isPlatformBrowser(this.platformId);
232
- }
233
- /**
234
- * Safely gets sessionStorage reference
235
- * @returns sessionStorage object or null if not available
236
- */
237
- getSessionStorage() {
238
- if (!this.isBrowser()) {
133
+ getUserData() {
134
+ const token = this.getToken();
135
+ if (!token) {
239
136
  return null;
240
137
  }
241
138
  try {
242
- return this.document.defaultView?.sessionStorage || null;
139
+ const decodedToken = jwtDecode(token);
140
+ const email = decodedToken['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress'] ??
141
+ decodedToken['email'] ??
142
+ decodedToken['sub'] ??
143
+ decodedToken['user_id'];
144
+ const displayName = decodedToken['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname'] ??
145
+ decodedToken['displayName'] ??
146
+ decodedToken['display_name'] ??
147
+ decodedToken['name'] ??
148
+ decodedToken['given_name'];
149
+ const name = decodedToken['name'] ?? displayName;
150
+ if (!email) {
151
+ return null;
152
+ }
153
+ const userData = {
154
+ email: email.toString(),
155
+ displayName: displayName?.toString() ?? 'Unknown User',
156
+ name: name?.toString(),
157
+ roles: this.extractArrayField(decodedToken, ['roles', 'role']),
158
+ permissions: this.extractArrayField(decodedToken, ['permissions', 'perms']),
159
+ tenantId: decodedToken['tenantId']?.toString() ??
160
+ decodedToken['tenant_id']?.toString() ??
161
+ decodedToken['tenant']?.toString(),
162
+ companyId: decodedToken['companyId']?.toString() ??
163
+ decodedToken['company_id']?.toString() ??
164
+ decodedToken['organizationId']?.toString() ??
165
+ decodedToken['org_id']?.toString(),
166
+ locale: decodedToken['locale']?.toString(),
167
+ timezone: decodedToken['timezone']?.toString() ?? decodedToken['tz']?.toString(),
168
+ };
169
+ return userData;
243
170
  }
244
171
  catch {
245
- // Handle cases where sessionStorage might be disabled
246
172
  return null;
247
173
  }
248
174
  }
249
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: UrlRedirectService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
250
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: UrlRedirectService, providedIn: 'root' });
251
- }
252
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: UrlRedirectService, decorators: [{
253
- type: Injectable,
254
- args: [{
255
- providedIn: 'root',
256
- }]
257
- }] });
258
-
259
- const authGuard = (_route, state) => {
260
- const tokenRepository = inject(TokenRepository);
261
- const router = inject(Router);
262
- const urlRedirectService = inject(UrlRedirectService);
263
- const environment = inject(ENVIRONMENT);
264
- if (tokenRepository.isAuthenticated()) {
265
- return true;
266
- }
267
- // Store the current URL for redirection after login
268
- urlRedirectService.storeIntendedUrl(state.url);
269
- // Redirect to login page (configurable via environment)
270
- router.navigate([environment.loginRoute]);
271
- return false;
272
- };
273
-
274
- /**
275
- * Interceptor that handles authentication errors and manages URL redirection
276
- * Captures the current URL when a 401 error occurs and redirects to login
277
- */
278
- const authRedirectInterceptor = (req, next) => {
279
- const router = inject(Router);
280
- const urlRedirectService = inject(UrlRedirectService);
281
- const tokenRepository = inject(TokenRepository);
282
- const environment = inject(ENVIRONMENT);
283
- return next(req).pipe(catchError((error) => {
284
- // Handle 401 Unauthorized errors
285
- if (error.status === 401) {
286
- // Only store and redirect if user was previously authenticated
287
- // This prevents redirect loops and handles session expiry scenarios
288
- if (tokenRepository.isAuthenticated()) {
289
- // Store the current URL for redirection after re-authentication
290
- urlRedirectService.storeCurrentUrlIfAllowed();
291
- // Navigate to login page
292
- router.navigate([environment.loginRoute]);
293
- }
294
- }
295
- // Re-throw the error so other error handlers can process it
296
- return throwError(() => error);
297
- }));
298
- };
299
-
300
- // src/lib/domain/models/auth.ts
301
-
302
- // src/lib/domain/models/index.ts
303
-
304
- class AuthRepository {
305
- }
306
-
307
- // src/lib/domain/repositories/index.ts
308
-
309
- // src/lib/domain/index.ts
310
-
311
- // src/lib/services/csrf.service.ts
312
- class CsrfService {
313
- http = inject(HttpClient);
314
- csrfToken = null;
315
175
  /**
316
- * Get CSRF token, fetching it if not available
176
+ * Extract array field from decoded token, trying multiple possible field names
317
177
  */
318
- async getCsrfToken() {
319
- if (this.csrfToken) {
320
- return this.csrfToken;
321
- }
322
- try {
323
- this.csrfToken = await firstValueFrom(this.http
324
- .get('/csrf-token')
325
- .pipe(map(response => response.csrfToken)));
326
- return this.csrfToken || '';
327
- }
328
- catch {
329
- // If CSRF endpoint fails, return empty token
330
- // Server should handle missing CSRF tokens appropriately
331
- return '';
178
+ extractArrayField(decodedToken, fieldNames) {
179
+ for (const fieldName of fieldNames) {
180
+ const value = decodedToken[fieldName];
181
+ if (Array.isArray(value)) {
182
+ return value.map(v => v.toString());
183
+ }
184
+ if (typeof value === 'string') {
185
+ // Handle comma-separated string values
186
+ return value
187
+ .split(',')
188
+ .map(v => v.trim())
189
+ .filter(v => v.length > 0);
190
+ }
332
191
  }
192
+ return undefined;
333
193
  }
334
- /**
335
- * Clear stored CSRF token (useful on logout)
336
- */
337
- clearCsrfToken() {
338
- this.csrfToken = null;
339
- }
340
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: CsrfService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
341
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: CsrfService, providedIn: 'root' });
342
- }
343
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: CsrfService, decorators: [{
344
- type: Injectable,
345
- args: [{
346
- providedIn: 'root',
347
- }]
348
- }] });
349
-
350
- // src/lib/data/repositories/auth-http.repository.ts
351
- function getDeviceInfo() {
352
- return `${navigator.platform ?? 'Unknown'} - ${navigator.userAgent}`;
353
- }
354
- class AuthHttpRepository extends AuthRepository {
355
- http = inject(HttpClient);
356
- csrfService = inject(CsrfService);
357
- URL = `${AUTH_API.ACCOUNT}`;
358
- login(request) {
359
- return from(this.csrfService.getCsrfToken()).pipe(switchMap(csrfToken => this.http.post(`${this.URL}login`, request, {
360
- headers: {
361
- 'Device-Info': getDeviceInfo(),
362
- 'X-CSRF-Token': csrfToken,
363
- },
364
- withCredentials: true,
365
- })));
366
- }
367
- register(request) {
368
- return from(this.csrfService.getCsrfToken()).pipe(switchMap(csrfToken => this.http.post(`${this.URL}register`, request, {
369
- headers: {
370
- 'Device-Info': getDeviceInfo(),
371
- 'X-CSRF-Token': csrfToken,
372
- },
373
- withCredentials: true,
374
- })));
375
- }
376
- refreshToken(request) {
377
- return from(this.csrfService.getCsrfToken()).pipe(switchMap(csrfToken => this.http.post(`${this.URL}refresh`, request, {
378
- headers: {
379
- 'Device-Info': getDeviceInfo(),
380
- 'X-CSRF-Token': csrfToken,
381
- },
382
- withCredentials: true,
383
- })));
384
- }
385
- logout(email, refreshToken) {
386
- return from(this.csrfService.getCsrfToken()).pipe(switchMap(csrfToken => this.http.post(`${this.URL}logout`, { email, refreshToken: refreshToken || undefined }, {
387
- headers: { 'X-CSRF-Token': csrfToken },
388
- withCredentials: true, // Ensure cookies are sent
389
- })));
390
- }
391
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: AuthHttpRepository, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
392
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: AuthHttpRepository, providedIn: 'root' });
194
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: TokenRepository, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
195
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: TokenRepository, providedIn: 'root' });
393
196
  }
394
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: AuthHttpRepository, decorators: [{
197
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: TokenRepository, decorators: [{
395
198
  type: Injectable,
396
199
  args: [{
397
200
  providedIn: 'root',
398
201
  }]
399
202
  }] });
400
203
 
401
- // src/lib/data/repositories/index.ts
402
-
403
- // src/lib/data/index.ts
404
-
405
204
  // src/lib/presentation/stores/auth.store.ts
406
205
  class AuthStore {
407
206
  authRepository = inject(AuthRepository);
408
207
  tokenRepository = inject(TokenRepository);
409
- userRepository = inject(UserRepository);
410
208
  router = inject(Router);
411
209
  ngZone = inject(NgZone);
412
210
  // Authentication state signals
@@ -432,7 +230,7 @@ class AuthStore {
432
230
  const isAuthenticated = this.tokenRepository.isAuthenticated();
433
231
  this._isAuthenticated.set(isAuthenticated);
434
232
  if (isAuthenticated) {
435
- const userData = this.userRepository.getCurrentUser();
233
+ const userData = this.tokenRepository.getUserData();
436
234
  this._user.set(userData);
437
235
  this.scheduleTokenRefresh();
438
236
  }
@@ -494,7 +292,7 @@ class AuthStore {
494
292
  if (this.refreshInProgress$) {
495
293
  return this.refreshInProgress$;
496
294
  }
497
- const userData = this.userRepository.getCurrentUser();
295
+ const userData = this.tokenRepository.getUserData();
498
296
  const refreshToken = this.tokenRepository.getRefreshToken();
499
297
  if (!userData?.email || !refreshToken) {
500
298
  this.logout();
@@ -507,7 +305,7 @@ class AuthStore {
507
305
  })
508
306
  .pipe(tap(tokens => {
509
307
  this.setAuthenticated(tokens);
510
- }), catchError$1(() => {
308
+ }), catchError(() => {
511
309
  this.logout();
512
310
  return of(null);
513
311
  }), tap({
@@ -526,7 +324,7 @@ class AuthStore {
526
324
  setAuthenticated(tokens, rememberMe = false) {
527
325
  this.tokenRepository.saveTokens(tokens, rememberMe);
528
326
  this._isAuthenticated.set(true);
529
- const userData = this.userRepository.getCurrentUser();
327
+ const userData = this.tokenRepository.getUserData();
530
328
  this._user.set(userData);
531
329
  this.scheduleTokenRefresh();
532
330
  }
@@ -610,13 +408,132 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImpor
610
408
  }]
611
409
  }], ctorParameters: () => [] });
612
410
 
613
- // src/lib/application/use-cases/login.use-case.ts
614
- class LoginUseCase extends BaseUseCase {
615
- authRepository = inject(AuthRepository);
616
- authStore = inject(AuthStore);
617
- router = inject(Router);
618
- urlRedirectService = inject(UrlRedirectService);
619
- execute(request) {
411
+ /**
412
+ * Service to manage URL redirection after authentication
413
+ * Stores the intended URL when session is lost and redirects to it after successful login
414
+ * SSR-compatible by checking platform before accessing sessionStorage
415
+ */
416
+ class UrlRedirectService {
417
+ REDIRECT_URL_KEY = 'acp_redirect_url';
418
+ EXCLUDED_ROUTES = [
419
+ '/login',
420
+ '/auth',
421
+ '/register',
422
+ '/forgot-password',
423
+ '/reset-password',
424
+ ];
425
+ router = inject(Router);
426
+ platformId = inject(PLATFORM_ID);
427
+ document = inject(DOCUMENT);
428
+ /**
429
+ * Stores the current URL for later redirection
430
+ * @param url - The URL to store (defaults to current URL)
431
+ */
432
+ storeIntendedUrl(url) {
433
+ // Only store in browser environment
434
+ if (!this.isBrowser()) {
435
+ return;
436
+ }
437
+ const urlToStore = url || this.router.url;
438
+ // Don't store authentication-related routes
439
+ if (this.isExcludedRoute(urlToStore)) {
440
+ return;
441
+ }
442
+ // Don't store URLs with query parameters that might contain sensitive data
443
+ const urlWithoutParams = urlToStore.split('?')[0];
444
+ this.getSessionStorage()?.setItem(this.REDIRECT_URL_KEY, urlWithoutParams);
445
+ }
446
+ /**
447
+ * Gets the stored intended URL
448
+ * @returns The stored URL or null if none exists
449
+ */
450
+ getIntendedUrl() {
451
+ if (!this.isBrowser()) {
452
+ return null;
453
+ }
454
+ return this.getSessionStorage()?.getItem(this.REDIRECT_URL_KEY) || null;
455
+ }
456
+ /**
457
+ * Redirects to the stored URL and clears it from storage
458
+ * @param defaultRoute - The default route to navigate to if no URL is stored
459
+ */
460
+ redirectToIntendedUrl(defaultRoute = '/') {
461
+ const intendedUrl = this.getIntendedUrl();
462
+ if (intendedUrl && !this.isExcludedRoute(intendedUrl)) {
463
+ this.clearIntendedUrl();
464
+ this.router.navigateByUrl(intendedUrl);
465
+ }
466
+ else {
467
+ this.router.navigate([defaultRoute]);
468
+ }
469
+ }
470
+ /**
471
+ * Clears the stored intended URL
472
+ */
473
+ clearIntendedUrl() {
474
+ if (!this.isBrowser()) {
475
+ return;
476
+ }
477
+ this.getSessionStorage()?.removeItem(this.REDIRECT_URL_KEY);
478
+ }
479
+ /**
480
+ * Checks if a URL should be excluded from redirection
481
+ * @param url - The URL to check
482
+ * @returns True if the URL should be excluded
483
+ */
484
+ isExcludedRoute(url) {
485
+ return this.EXCLUDED_ROUTES.some(route => url.includes(route));
486
+ }
487
+ /**
488
+ * Stores the current URL if it's not an excluded route
489
+ * Useful for guards and interceptors
490
+ */
491
+ storeCurrentUrlIfAllowed() {
492
+ const currentUrl = this.router.url;
493
+ if (!this.isExcludedRoute(currentUrl)) {
494
+ this.storeIntendedUrl(currentUrl);
495
+ }
496
+ }
497
+ /**
498
+ * Checks if we're running in a browser environment
499
+ * @returns True if running in browser, false if SSR
500
+ */
501
+ isBrowser() {
502
+ return isPlatformBrowser(this.platformId);
503
+ }
504
+ /**
505
+ * Safely gets sessionStorage reference
506
+ * @returns sessionStorage object or null if not available
507
+ */
508
+ getSessionStorage() {
509
+ if (!this.isBrowser()) {
510
+ return null;
511
+ }
512
+ try {
513
+ return this.document.defaultView?.sessionStorage || null;
514
+ }
515
+ catch {
516
+ // Handle cases where sessionStorage might be disabled
517
+ return null;
518
+ }
519
+ }
520
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: UrlRedirectService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
521
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: UrlRedirectService, providedIn: 'root' });
522
+ }
523
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: UrlRedirectService, decorators: [{
524
+ type: Injectable,
525
+ args: [{
526
+ providedIn: 'root',
527
+ }]
528
+ }] });
529
+
530
+ // src/lib/application/use-cases/login.use-case.ts
531
+ class LoginUseCase extends BaseUseCase {
532
+ authRepository = inject(AuthRepository);
533
+ authStore = inject(AuthStore);
534
+ router = inject(Router);
535
+ urlRedirectService = inject(UrlRedirectService);
536
+ execute(request) {
620
537
  return this.authRepository.login(request).pipe(tap(tokens => {
621
538
  // Set authentication state with rememberMe preference
622
539
  this.authStore.setAuthenticated(tokens, request.rememberMe ?? false);
@@ -660,11 +577,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImpor
660
577
  // src/lib/application/use-cases/refresh-token.use-case.ts
661
578
  class RefreshTokenUseCase extends BaseUseCase {
662
579
  authRepository = inject(AuthRepository);
663
- userRepository = inject(UserRepository);
664
580
  tokenRepository = inject(TokenRepository);
665
581
  authStore = inject(AuthStore);
666
582
  execute() {
667
- const userData = this.userRepository.getCurrentUser();
583
+ const userData = this.tokenRepository.getUserData();
668
584
  const refreshToken = this.tokenRepository.getRefreshToken();
669
585
  if (!userData?.email || !refreshToken || refreshToken.trim().length === 0) {
670
586
  const error = new Error('No refresh token or email available');
@@ -680,7 +596,7 @@ class RefreshTokenUseCase extends BaseUseCase {
680
596
  const rememberMe = this.tokenRepository.isRememberMeEnabled();
681
597
  // Update authentication state
682
598
  this.authStore.setAuthenticated(tokens, rememberMe);
683
- }), catchError$1(error => {
599
+ }), catchError(error => {
684
600
  // Don't logout here, let the interceptor handle it
685
601
  return throwError(() => error);
686
602
  }));
@@ -698,11 +614,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImpor
698
614
  // src/lib/application/use-cases/logout.use-case.ts
699
615
  class LogoutUseCase extends BaseUseCase {
700
616
  authRepository = inject(AuthRepository);
701
- userRepository = inject(UserRepository);
702
617
  tokenRepository = inject(TokenRepository);
703
618
  authStore = inject(AuthStore);
704
619
  execute() {
705
- const userData = this.userRepository.getCurrentUser();
620
+ const userData = this.tokenRepository.getUserData();
706
621
  const refreshToken = this.tokenRepository.getRefreshToken();
707
622
  if (userData?.email && refreshToken && refreshToken.length > 0) {
708
623
  return this.authRepository
@@ -730,6 +645,202 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImpor
730
645
 
731
646
  // src/lib/application/index.ts
732
647
 
648
+ // src/lib/data/repositories/auth-http.repository.ts
649
+ function getDeviceInfo() {
650
+ return `${navigator.platform ?? 'Unknown'} - ${navigator.userAgent}`;
651
+ }
652
+ class AuthHttpRepository extends AuthRepository {
653
+ http = inject(HttpClient);
654
+ URL = `${AUTH_API.AUTH}`;
655
+ login(request) {
656
+ return this.http.post(`${this.URL}login`, request, {
657
+ headers: {
658
+ 'Device-Info': getDeviceInfo(),
659
+ },
660
+ withCredentials: true,
661
+ });
662
+ }
663
+ register(request) {
664
+ return this.http.post(`${this.URL}register`, request, {
665
+ headers: {
666
+ 'Device-Info': getDeviceInfo(),
667
+ },
668
+ withCredentials: true,
669
+ });
670
+ }
671
+ refreshToken(request) {
672
+ return this.http.post(`${this.URL}refresh`, request, {
673
+ headers: {
674
+ 'Device-Info': getDeviceInfo(),
675
+ },
676
+ withCredentials: true,
677
+ });
678
+ }
679
+ logout(email, refreshToken) {
680
+ return this.http.post(`${this.URL}logout`, { email, refreshToken: refreshToken || undefined }, {
681
+ headers: {},
682
+ withCredentials: true, // Ensure cookies are sent
683
+ });
684
+ }
685
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: AuthHttpRepository, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
686
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: AuthHttpRepository, providedIn: 'root' });
687
+ }
688
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: AuthHttpRepository, decorators: [{
689
+ type: Injectable,
690
+ args: [{
691
+ providedIn: 'root',
692
+ }]
693
+ }] });
694
+
695
+ // src/lib/data/repositories/index.ts
696
+
697
+ // src/lib/data/index.ts
698
+
699
+ // src/lib/domain/models/auth.ts
700
+
701
+ // src/lib/domain/models/index.ts
702
+
703
+ // src/lib/domain/repositories/index.ts
704
+
705
+ // src/lib/domain/index.ts
706
+
707
+ const authGuard = (_route, state) => {
708
+ const tokenRepository = inject(TokenRepository);
709
+ const router = inject(Router);
710
+ const urlRedirectService = inject(UrlRedirectService);
711
+ const environment = inject(ENVIRONMENT);
712
+ if (tokenRepository.isAuthenticated()) {
713
+ return true;
714
+ }
715
+ // Store the current URL for redirection after login
716
+ urlRedirectService.storeIntendedUrl(state.url);
717
+ // Redirect to login page (configurable via environment)
718
+ router.navigate([`/${environment.loginRoute}`]);
719
+ return false;
720
+ };
721
+
722
+ /**
723
+ * Interceptor that handles authentication errors and manages URL redirection
724
+ * Captures the current URL when a 401 error occurs and redirects to login
725
+ */
726
+ const authRedirectInterceptor = (req, next) => {
727
+ const router = inject(Router);
728
+ const urlRedirectService = inject(UrlRedirectService);
729
+ const tokenRepository = inject(TokenRepository);
730
+ const environment = inject(ENVIRONMENT);
731
+ return next(req).pipe(catchError$1((error) => {
732
+ // Handle 401 Unauthorized errors
733
+ if (error.status === 401) {
734
+ // Only store and redirect if user was previously authenticated
735
+ // This prevents redirect loops and handles session expiry scenarios
736
+ if (tokenRepository.isAuthenticated()) {
737
+ // Store the current URL for redirection after re-authentication
738
+ urlRedirectService.storeCurrentUrlIfAllowed();
739
+ // Navigate to login page
740
+ router.navigate([`/${environment.loginRoute}`]);
741
+ }
742
+ }
743
+ // Re-throw the error so other error handlers can process it
744
+ return throwError(() => error);
745
+ }));
746
+ };
747
+
748
+ // src/lib/services/csrf.service.ts
749
+ class CsrfService {
750
+ http = inject(HttpClient);
751
+ csrfToken = null;
752
+ /**
753
+ * Get CSRF token, fetching it if not available
754
+ */
755
+ async getCsrfToken() {
756
+ if (this.csrfToken) {
757
+ return this.csrfToken;
758
+ }
759
+ try {
760
+ this.csrfToken = await firstValueFrom(this.http
761
+ .get('csrf-token')
762
+ .pipe(map(response => response.csrfToken)));
763
+ return this.csrfToken || '';
764
+ }
765
+ catch {
766
+ // If CSRF endpoint fails, return empty token
767
+ // Server should handle missing CSRF tokens appropriately
768
+ return '';
769
+ }
770
+ }
771
+ /**
772
+ * Clear stored CSRF token (useful on logout)
773
+ */
774
+ clearCsrfToken() {
775
+ this.csrfToken = null;
776
+ }
777
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: CsrfService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
778
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: CsrfService, providedIn: 'root' });
779
+ }
780
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: CsrfService, decorators: [{
781
+ type: Injectable,
782
+ args: [{
783
+ providedIn: 'root',
784
+ }]
785
+ }] });
786
+
787
+ // A token to use with HttpContext for skipping CSRF token addition on specific requests.
788
+ const SKIP_CSRF = new HttpContextToken(() => false);
789
+ /**
790
+ * HTTP interceptor that automatically adds CSRF tokens to state-changing requests
791
+ * Only applies to requests to the same origin to avoid leaking tokens to external APIs
792
+ */
793
+ const csrfInterceptor = (req, next) => {
794
+ const csrfService = inject(CsrfService);
795
+ // Check if CSRF should be skipped for this request
796
+ const skipCsrf = req.context.get(SKIP_CSRF);
797
+ if (skipCsrf) {
798
+ return next(req);
799
+ }
800
+ // Only add CSRF token to state-changing requests (POST, PUT, PATCH, DELETE)
801
+ const isStateChangingMethod = ['POST', 'PUT', 'PATCH', 'DELETE'].includes(req.method.toUpperCase());
802
+ // Only add CSRF token to same-origin requests
803
+ const isSameOrigin = isRequestToSameOrigin(req);
804
+ if (isStateChangingMethod && isSameOrigin) {
805
+ return from(csrfService.getCsrfToken()).pipe(switchMap(csrfToken => {
806
+ const modifiedReq = req.clone({
807
+ setHeaders: {
808
+ 'X-CSRF-Token': csrfToken,
809
+ },
810
+ });
811
+ return next(modifiedReq);
812
+ }));
813
+ }
814
+ // For non-state-changing requests or external requests, proceed without modification
815
+ return next(req);
816
+ };
817
+ /**
818
+ * Checks if the request is going to the same origin as the current application
819
+ */
820
+ function isRequestToSameOrigin(req) {
821
+ try {
822
+ const requestUrl = new URL(req.url, window.location.origin);
823
+ return requestUrl.origin === window.location.origin;
824
+ }
825
+ catch {
826
+ // If URL parsing fails, assume it's not same origin for security
827
+ return false;
828
+ }
829
+ }
830
+
831
+ const authProviders = [
832
+ {
833
+ provide: AuthRepository,
834
+ useClass: AuthHttpRepository,
835
+ },
836
+ {
837
+ provide: TOKEN_PROVIDER,
838
+ useClass: TokenRepository,
839
+ },
840
+ ];
841
+
842
+ // src/lib/providers/index.ts
843
+
733
844
  // src/lib/presentation/stores/index.ts
734
845
 
735
846
  // src/lib/presentation/components/login/login.component.ts
@@ -856,22 +967,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImpor
856
967
 
857
968
  // src/lib/presentation/index.ts
858
969
 
859
- const authProviders = [
860
- {
861
- provide: AuthRepository,
862
- useClass: AuthHttpRepository,
863
- },
864
- {
865
- provide: TOKEN_PROVIDER,
866
- useClass: TokenRepository,
867
- },
868
- ];
869
-
870
- // src/lib/providers/index.ts
871
-
872
970
  /**
873
971
  * Generated bundle index. Do not edit.
874
972
  */
875
973
 
876
- export { AuthHttpRepository, AuthRepository, AuthStore, LoginComponent, LoginUseCase, LogoutUseCase, RefreshTokenUseCase, RegisterUseCase, TokenRepository, UrlRedirectService, authGuard, authProviders, authRedirectInterceptor };
974
+ export { AuthHttpRepository, AuthRepository, AuthStore, CsrfService, LoginComponent, LoginUseCase, LogoutUseCase, RefreshTokenUseCase, RegisterUseCase, SKIP_CSRF, TokenRepository, UrlRedirectService, authGuard, authProviders, authRedirectInterceptor, csrfInterceptor };
877
975
  //# sourceMappingURL=acontplus-ng-auth.mjs.map