@acontplus/ng-auth 1.1.1 → 1.1.3

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 } from '@acontplus/ng-infrastructure';
1
+ import { UserRepository, 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
- import { inject, PLATFORM_ID, Injectable, NgZone, signal, input, computed, ChangeDetectionStrategy, Component } from '@angular/core';
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, 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);
@@ -28,7 +31,7 @@ class TokenRepository {
28
31
  this.setRefreshToken(tokens.refreshToken, rememberMe);
29
32
  }
30
33
  }
31
- getAccessToken() {
34
+ getToken() {
32
35
  if (!isPlatformBrowser(this.platformId)) {
33
36
  return null;
34
37
  }
@@ -74,7 +77,7 @@ class TokenRepository {
74
77
  sessionStorage.removeItem(this.environment.refreshTokenKey);
75
78
  }
76
79
  isAuthenticated() {
77
- const accessToken = this.getAccessToken();
80
+ const accessToken = this.getToken();
78
81
  if (!accessToken) {
79
82
  return false;
80
83
  }
@@ -89,7 +92,7 @@ class TokenRepository {
89
92
  }
90
93
  }
91
94
  needsRefresh() {
92
- const accessToken = this.getAccessToken();
95
+ const accessToken = this.getToken();
93
96
  if (!accessToken) {
94
97
  return false;
95
98
  }
@@ -105,7 +108,7 @@ class TokenRepository {
105
108
  }
106
109
  }
107
110
  getTokenPayload() {
108
- const token = this.getAccessToken();
111
+ const token = this.getToken();
109
112
  if (!token)
110
113
  return null;
111
114
  try {
@@ -137,289 +140,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImpor
137
140
  }]
138
141
  }] });
139
142
 
140
- class AuthTokenService {
141
- tokenRepository = inject(TokenRepository);
142
- getToken() {
143
- return this.tokenRepository.getAccessToken();
144
- }
145
- isAuthenticated() {
146
- return this.tokenRepository.isAuthenticated();
147
- }
148
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: AuthTokenService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
149
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: AuthTokenService, providedIn: 'root' });
150
- }
151
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: AuthTokenService, decorators: [{
152
- type: Injectable,
153
- args: [{ providedIn: 'root' }]
154
- }] });
155
-
156
- /**
157
- * Service to manage URL redirection after authentication
158
- * Stores the intended URL when session is lost and redirects to it after successful login
159
- * SSR-compatible by checking platform before accessing sessionStorage
160
- */
161
- class UrlRedirectService {
162
- REDIRECT_URL_KEY = 'acp_redirect_url';
163
- EXCLUDED_ROUTES = [
164
- '/login',
165
- '/auth',
166
- '/register',
167
- '/forgot-password',
168
- '/reset-password',
169
- ];
170
- router = inject(Router);
171
- platformId = inject(PLATFORM_ID);
172
- document = inject(DOCUMENT);
173
- /**
174
- * Stores the current URL for later redirection
175
- * @param url - The URL to store (defaults to current URL)
176
- */
177
- storeIntendedUrl(url) {
178
- // Only store in browser environment
179
- if (!this.isBrowser()) {
180
- return;
181
- }
182
- const urlToStore = url || this.router.url;
183
- // Don't store authentication-related routes
184
- if (this.isExcludedRoute(urlToStore)) {
185
- return;
186
- }
187
- // Don't store URLs with query parameters that might contain sensitive data
188
- const urlWithoutParams = urlToStore.split('?')[0];
189
- this.getSessionStorage()?.setItem(this.REDIRECT_URL_KEY, urlWithoutParams);
190
- }
191
- /**
192
- * Gets the stored intended URL
193
- * @returns The stored URL or null if none exists
194
- */
195
- getIntendedUrl() {
196
- if (!this.isBrowser()) {
197
- return null;
198
- }
199
- return this.getSessionStorage()?.getItem(this.REDIRECT_URL_KEY) || null;
200
- }
201
- /**
202
- * Redirects to the stored URL and clears it from storage
203
- * @param defaultRoute - The default route to navigate to if no URL is stored
204
- */
205
- redirectToIntendedUrl(defaultRoute = '/') {
206
- const intendedUrl = this.getIntendedUrl();
207
- if (intendedUrl && !this.isExcludedRoute(intendedUrl)) {
208
- this.clearIntendedUrl();
209
- this.router.navigateByUrl(intendedUrl);
210
- }
211
- else {
212
- this.router.navigate([defaultRoute]);
213
- }
214
- }
215
- /**
216
- * Clears the stored intended URL
217
- */
218
- clearIntendedUrl() {
219
- if (!this.isBrowser()) {
220
- return;
221
- }
222
- this.getSessionStorage()?.removeItem(this.REDIRECT_URL_KEY);
223
- }
224
- /**
225
- * Checks if a URL should be excluded from redirection
226
- * @param url - The URL to check
227
- * @returns True if the URL should be excluded
228
- */
229
- isExcludedRoute(url) {
230
- return this.EXCLUDED_ROUTES.some(route => url.includes(route));
231
- }
232
- /**
233
- * Stores the current URL if it's not an excluded route
234
- * Useful for guards and interceptors
235
- */
236
- storeCurrentUrlIfAllowed() {
237
- const currentUrl = this.router.url;
238
- if (!this.isExcludedRoute(currentUrl)) {
239
- this.storeIntendedUrl(currentUrl);
240
- }
241
- }
242
- /**
243
- * Checks if we're running in a browser environment
244
- * @returns True if running in browser, false if SSR
245
- */
246
- isBrowser() {
247
- return isPlatformBrowser(this.platformId);
248
- }
249
- /**
250
- * Safely gets sessionStorage reference
251
- * @returns sessionStorage object or null if not available
252
- */
253
- getSessionStorage() {
254
- if (!this.isBrowser()) {
255
- return null;
256
- }
257
- try {
258
- return this.document.defaultView?.sessionStorage || null;
259
- }
260
- catch {
261
- // Handle cases where sessionStorage might be disabled
262
- return null;
263
- }
264
- }
265
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: UrlRedirectService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
266
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: UrlRedirectService, providedIn: 'root' });
267
- }
268
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: UrlRedirectService, decorators: [{
269
- type: Injectable,
270
- args: [{
271
- providedIn: 'root',
272
- }]
273
- }] });
274
-
275
- const authGuard = (_route, state) => {
276
- const authService = inject(AuthTokenService);
277
- const router = inject(Router);
278
- const urlRedirectService = inject(UrlRedirectService);
279
- const environment = inject(ENVIRONMENT);
280
- if (authService.isAuthenticated()) {
281
- return true;
282
- }
283
- // Store the current URL for redirection after login
284
- urlRedirectService.storeIntendedUrl(state.url);
285
- // Redirect to login page (configurable via environment)
286
- router.navigate([environment.loginRoute]);
287
- return false;
288
- };
289
-
290
- /**
291
- * Interceptor that handles authentication errors and manages URL redirection
292
- * Captures the current URL when a 401 error occurs and redirects to login
293
- */
294
- const authRedirectInterceptor = (req, next) => {
295
- const router = inject(Router);
296
- const urlRedirectService = inject(UrlRedirectService);
297
- const authTokenService = inject(AuthTokenService);
298
- const environment = inject(ENVIRONMENT);
299
- return next(req).pipe(catchError((error) => {
300
- // Handle 401 Unauthorized errors
301
- if (error.status === 401) {
302
- // Only store and redirect if user was previously authenticated
303
- // This prevents redirect loops and handles session expiry scenarios
304
- if (authTokenService.isAuthenticated()) {
305
- // Store the current URL for redirection after re-authentication
306
- urlRedirectService.storeCurrentUrlIfAllowed();
307
- // Navigate to login page
308
- router.navigate([environment.loginRoute]);
309
- }
310
- }
311
- // Re-throw the error so other error handlers can process it
312
- return throwError(() => error);
313
- }));
314
- };
315
-
316
- // src/lib/domain/models/auth.ts
317
-
318
- // src/lib/domain/models/index.ts
319
-
320
- class AuthRepository {
321
- }
322
-
323
- // src/lib/domain/repositories/index.ts
324
-
325
- // src/lib/domain/index.ts
326
-
327
- // src/lib/services/csrf.service.ts
328
- class CsrfService {
329
- http = inject(HttpClient);
330
- environment = inject(ENVIRONMENT);
331
- csrfToken = null;
332
- /**
333
- * Get CSRF token, fetching it if not available
334
- */
335
- async getCsrfToken() {
336
- if (this.csrfToken) {
337
- return this.csrfToken;
338
- }
339
- try {
340
- const response = await this.http
341
- .get(`${this.environment.apiBaseUrl}/csrf-token`)
342
- .toPromise();
343
- this.csrfToken = response?.csrfToken || null;
344
- return this.csrfToken || '';
345
- }
346
- catch {
347
- // If CSRF endpoint fails, return empty token
348
- // Server should handle missing CSRF tokens appropriately
349
- return '';
350
- }
351
- }
352
- /**
353
- * Clear stored CSRF token (useful on logout)
354
- */
355
- clearCsrfToken() {
356
- this.csrfToken = null;
357
- }
358
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: CsrfService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
359
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: CsrfService, providedIn: 'root' });
360
- }
361
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: CsrfService, decorators: [{
362
- type: Injectable,
363
- args: [{
364
- providedIn: 'root',
365
- }]
366
- }] });
367
-
368
- // src/lib/data/repositories/auth-http.repository.ts
369
- function getDeviceInfo() {
370
- return `${navigator.platform ?? 'Unknown'} - ${navigator.userAgent}`;
371
- }
372
- class AuthHttpRepository extends AuthRepository {
373
- http = inject(HttpClient);
374
- csrfService = inject(CsrfService);
375
- URL = `${AUTH_API.ACCOUNT}`;
376
- login(request) {
377
- return from(this.csrfService.getCsrfToken()).pipe(switchMap(csrfToken => this.http.post(`${this.URL}login`, request, {
378
- headers: {
379
- 'Device-Info': getDeviceInfo(),
380
- 'X-CSRF-Token': csrfToken,
381
- },
382
- withCredentials: true,
383
- })));
384
- }
385
- register(request) {
386
- return from(this.csrfService.getCsrfToken()).pipe(switchMap(csrfToken => this.http.post(`${this.URL}register`, request, {
387
- headers: {
388
- 'Device-Info': getDeviceInfo(),
389
- 'X-CSRF-Token': csrfToken,
390
- },
391
- withCredentials: true,
392
- })));
393
- }
394
- refreshToken(request) {
395
- return from(this.csrfService.getCsrfToken()).pipe(switchMap(csrfToken => this.http.post(`${this.URL}refresh`, request, {
396
- headers: {
397
- 'Device-Info': getDeviceInfo(),
398
- 'X-CSRF-Token': csrfToken,
399
- },
400
- withCredentials: true,
401
- })));
402
- }
403
- logout(email, refreshToken) {
404
- return from(this.csrfService.getCsrfToken()).pipe(switchMap(csrfToken => this.http.post(`${this.URL}logout`, { email, refreshToken: refreshToken || undefined }, {
405
- headers: { 'X-CSRF-Token': csrfToken },
406
- withCredentials: true, // Ensure cookies are sent
407
- })));
408
- }
409
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: AuthHttpRepository, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
410
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: AuthHttpRepository, providedIn: 'root' });
411
- }
412
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: AuthHttpRepository, decorators: [{
413
- type: Injectable,
414
- args: [{
415
- providedIn: 'root',
416
- }]
417
- }] });
418
-
419
- // src/lib/data/repositories/index.ts
420
-
421
- // src/lib/data/index.ts
422
-
423
143
  // src/lib/presentation/stores/auth.store.ts
424
144
  class AuthStore {
425
145
  authRepository = inject(AuthRepository);
@@ -466,7 +186,7 @@ class AuthStore {
466
186
  * Schedule token refresh based on actual expiration time
467
187
  */
468
188
  scheduleTokenRefresh() {
469
- const accessToken = this.tokenRepository.getAccessToken();
189
+ const accessToken = this.tokenRepository.getToken();
470
190
  if (!accessToken) {
471
191
  return;
472
192
  }
@@ -525,7 +245,7 @@ class AuthStore {
525
245
  })
526
246
  .pipe(tap(tokens => {
527
247
  this.setAuthenticated(tokens);
528
- }), catchError$1(() => {
248
+ }), catchError(() => {
529
249
  this.logout();
530
250
  return of(null);
531
251
  }), tap({
@@ -549,84 +269,203 @@ class AuthStore {
549
269
  this.scheduleTokenRefresh();
550
270
  }
551
271
  /**
552
- * Logout user and clear all authentication data
272
+ * Logout user and clear all authentication data
273
+ */
274
+ logout() {
275
+ const user = this._user();
276
+ if (user) {
277
+ // Call server-side logout first
278
+ this.authRepository.logout(user.email, '').subscribe({
279
+ next: () => {
280
+ // Server logout successful, clear client-side data
281
+ this.performClientLogout();
282
+ },
283
+ error: () => {
284
+ // Server logout failed, still clear client-side data for security
285
+ this.performClientLogout();
286
+ },
287
+ });
288
+ }
289
+ else {
290
+ // No user data, just clear client-side data
291
+ this.performClientLogout();
292
+ }
293
+ }
294
+ /**
295
+ * Perform client-side logout operations
296
+ */
297
+ performClientLogout() {
298
+ this.stopTokenRefreshTimer();
299
+ this.tokenRepository.clearTokens();
300
+ this._isAuthenticated.set(false);
301
+ this._user.set(null);
302
+ // Navigate to login page
303
+ this.router.navigate(['/auth']);
304
+ }
305
+ /**
306
+ * Check if user is authenticated
307
+ */
308
+ checkAuthentication() {
309
+ const isAuthenticated = this.tokenRepository.isAuthenticated();
310
+ this._isAuthenticated.set(isAuthenticated);
311
+ if (!isAuthenticated) {
312
+ this.logout();
313
+ }
314
+ return isAuthenticated;
315
+ }
316
+ /**
317
+ * Decode JWT token
318
+ */
319
+ decodeToken(token) {
320
+ try {
321
+ const base64Url = token.split('.')[1];
322
+ const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
323
+ const jsonPayload = decodeURIComponent(atob(base64)
324
+ .split('')
325
+ .map(c => {
326
+ return `%${`00${c.charCodeAt(0).toString(16)}`.slice(-2)}`;
327
+ })
328
+ .join(''));
329
+ return JSON.parse(jsonPayload);
330
+ }
331
+ catch {
332
+ throw new Error('Invalid token format');
333
+ }
334
+ }
335
+ /**
336
+ * Cleanup on store destruction
337
+ */
338
+ ngOnDestroy() {
339
+ this.stopTokenRefreshTimer();
340
+ }
341
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: AuthStore, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
342
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: AuthStore, providedIn: 'root' });
343
+ }
344
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: AuthStore, decorators: [{
345
+ type: Injectable,
346
+ args: [{
347
+ providedIn: 'root',
348
+ }]
349
+ }], ctorParameters: () => [] });
350
+
351
+ /**
352
+ * Service to manage URL redirection after authentication
353
+ * Stores the intended URL when session is lost and redirects to it after successful login
354
+ * SSR-compatible by checking platform before accessing sessionStorage
355
+ */
356
+ class UrlRedirectService {
357
+ REDIRECT_URL_KEY = 'acp_redirect_url';
358
+ EXCLUDED_ROUTES = [
359
+ '/login',
360
+ '/auth',
361
+ '/register',
362
+ '/forgot-password',
363
+ '/reset-password',
364
+ ];
365
+ router = inject(Router);
366
+ platformId = inject(PLATFORM_ID);
367
+ document = inject(DOCUMENT);
368
+ /**
369
+ * Stores the current URL for later redirection
370
+ * @param url - The URL to store (defaults to current URL)
371
+ */
372
+ storeIntendedUrl(url) {
373
+ // Only store in browser environment
374
+ if (!this.isBrowser()) {
375
+ return;
376
+ }
377
+ const urlToStore = url || this.router.url;
378
+ // Don't store authentication-related routes
379
+ if (this.isExcludedRoute(urlToStore)) {
380
+ return;
381
+ }
382
+ // Don't store URLs with query parameters that might contain sensitive data
383
+ const urlWithoutParams = urlToStore.split('?')[0];
384
+ this.getSessionStorage()?.setItem(this.REDIRECT_URL_KEY, urlWithoutParams);
385
+ }
386
+ /**
387
+ * Gets the stored intended URL
388
+ * @returns The stored URL or null if none exists
389
+ */
390
+ getIntendedUrl() {
391
+ if (!this.isBrowser()) {
392
+ return null;
393
+ }
394
+ return this.getSessionStorage()?.getItem(this.REDIRECT_URL_KEY) || null;
395
+ }
396
+ /**
397
+ * Redirects to the stored URL and clears it from storage
398
+ * @param defaultRoute - The default route to navigate to if no URL is stored
399
+ */
400
+ redirectToIntendedUrl(defaultRoute = '/') {
401
+ const intendedUrl = this.getIntendedUrl();
402
+ if (intendedUrl && !this.isExcludedRoute(intendedUrl)) {
403
+ this.clearIntendedUrl();
404
+ this.router.navigateByUrl(intendedUrl);
405
+ }
406
+ else {
407
+ this.router.navigate([defaultRoute]);
408
+ }
409
+ }
410
+ /**
411
+ * Clears the stored intended URL
553
412
  */
554
- logout() {
555
- const user = this._user();
556
- if (user) {
557
- // Call server-side logout first
558
- this.authRepository.logout(user.email, '').subscribe({
559
- next: () => {
560
- // Server logout successful, clear client-side data
561
- this.performClientLogout();
562
- },
563
- error: () => {
564
- // Server logout failed, still clear client-side data for security
565
- this.performClientLogout();
566
- },
567
- });
568
- }
569
- else {
570
- // No user data, just clear client-side data
571
- this.performClientLogout();
413
+ clearIntendedUrl() {
414
+ if (!this.isBrowser()) {
415
+ return;
572
416
  }
417
+ this.getSessionStorage()?.removeItem(this.REDIRECT_URL_KEY);
573
418
  }
574
419
  /**
575
- * Perform client-side logout operations
420
+ * Checks if a URL should be excluded from redirection
421
+ * @param url - The URL to check
422
+ * @returns True if the URL should be excluded
576
423
  */
577
- performClientLogout() {
578
- this.stopTokenRefreshTimer();
579
- this.tokenRepository.clearTokens();
580
- this._isAuthenticated.set(false);
581
- this._user.set(null);
582
- // Navigate to login page
583
- this.router.navigate(['/auth']);
424
+ isExcludedRoute(url) {
425
+ return this.EXCLUDED_ROUTES.some(route => url.includes(route));
584
426
  }
585
427
  /**
586
- * Check if user is authenticated
428
+ * Stores the current URL if it's not an excluded route
429
+ * Useful for guards and interceptors
587
430
  */
588
- checkAuthentication() {
589
- const isAuthenticated = this.tokenRepository.isAuthenticated();
590
- this._isAuthenticated.set(isAuthenticated);
591
- if (!isAuthenticated) {
592
- this.logout();
431
+ storeCurrentUrlIfAllowed() {
432
+ const currentUrl = this.router.url;
433
+ if (!this.isExcludedRoute(currentUrl)) {
434
+ this.storeIntendedUrl(currentUrl);
593
435
  }
594
- return isAuthenticated;
595
436
  }
596
437
  /**
597
- * Decode JWT token
438
+ * Checks if we're running in a browser environment
439
+ * @returns True if running in browser, false if SSR
598
440
  */
599
- decodeToken(token) {
441
+ isBrowser() {
442
+ return isPlatformBrowser(this.platformId);
443
+ }
444
+ /**
445
+ * Safely gets sessionStorage reference
446
+ * @returns sessionStorage object or null if not available
447
+ */
448
+ getSessionStorage() {
449
+ if (!this.isBrowser()) {
450
+ return null;
451
+ }
600
452
  try {
601
- const base64Url = token.split('.')[1];
602
- const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
603
- const jsonPayload = decodeURIComponent(atob(base64)
604
- .split('')
605
- .map(c => {
606
- return `%${`00${c.charCodeAt(0).toString(16)}`.slice(-2)}`;
607
- })
608
- .join(''));
609
- return JSON.parse(jsonPayload);
453
+ return this.document.defaultView?.sessionStorage || null;
610
454
  }
611
455
  catch {
612
- throw new Error('Invalid token format');
456
+ // Handle cases where sessionStorage might be disabled
457
+ return null;
613
458
  }
614
459
  }
615
- /**
616
- * Cleanup on store destruction
617
- */
618
- ngOnDestroy() {
619
- this.stopTokenRefreshTimer();
620
- }
621
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: AuthStore, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
622
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: AuthStore, providedIn: 'root' });
460
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: UrlRedirectService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
461
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: UrlRedirectService, providedIn: 'root' });
623
462
  }
624
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: AuthStore, decorators: [{
463
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: UrlRedirectService, decorators: [{
625
464
  type: Injectable,
626
465
  args: [{
627
466
  providedIn: 'root',
628
467
  }]
629
- }], ctorParameters: () => [] });
468
+ }] });
630
469
 
631
470
  // src/lib/application/use-cases/login.use-case.ts
632
471
  class LoginUseCase extends BaseUseCase {
@@ -698,7 +537,7 @@ class RefreshTokenUseCase extends BaseUseCase {
698
537
  const rememberMe = this.tokenRepository.isRememberMeEnabled();
699
538
  // Update authentication state
700
539
  this.authStore.setAuthenticated(tokens, rememberMe);
701
- }), catchError$1(error => {
540
+ }), catchError(error => {
702
541
  // Don't logout here, let the interceptor handle it
703
542
  return throwError(() => error);
704
543
  }));
@@ -748,6 +587,202 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImpor
748
587
 
749
588
  // src/lib/application/index.ts
750
589
 
590
+ // src/lib/data/repositories/auth-http.repository.ts
591
+ function getDeviceInfo() {
592
+ return `${navigator.platform ?? 'Unknown'} - ${navigator.userAgent}`;
593
+ }
594
+ class AuthHttpRepository extends AuthRepository {
595
+ http = inject(HttpClient);
596
+ URL = `${AUTH_API.ACCOUNT}`;
597
+ login(request) {
598
+ return this.http.post(`${this.URL}login`, request, {
599
+ headers: {
600
+ 'Device-Info': getDeviceInfo(),
601
+ },
602
+ withCredentials: true,
603
+ });
604
+ }
605
+ register(request) {
606
+ return this.http.post(`${this.URL}register`, request, {
607
+ headers: {
608
+ 'Device-Info': getDeviceInfo(),
609
+ },
610
+ withCredentials: true,
611
+ });
612
+ }
613
+ refreshToken(request) {
614
+ return this.http.post(`${this.URL}refresh`, request, {
615
+ headers: {
616
+ 'Device-Info': getDeviceInfo(),
617
+ },
618
+ withCredentials: true,
619
+ });
620
+ }
621
+ logout(email, refreshToken) {
622
+ return this.http.post(`${this.URL}logout`, { email, refreshToken: refreshToken || undefined }, {
623
+ headers: {},
624
+ withCredentials: true, // Ensure cookies are sent
625
+ });
626
+ }
627
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: AuthHttpRepository, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
628
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: AuthHttpRepository, providedIn: 'root' });
629
+ }
630
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: AuthHttpRepository, decorators: [{
631
+ type: Injectable,
632
+ args: [{
633
+ providedIn: 'root',
634
+ }]
635
+ }] });
636
+
637
+ // src/lib/data/repositories/index.ts
638
+
639
+ // src/lib/data/index.ts
640
+
641
+ // src/lib/domain/models/auth.ts
642
+
643
+ // src/lib/domain/models/index.ts
644
+
645
+ // src/lib/domain/repositories/index.ts
646
+
647
+ // src/lib/domain/index.ts
648
+
649
+ const authGuard = (_route, state) => {
650
+ const tokenRepository = inject(TokenRepository);
651
+ const router = inject(Router);
652
+ const urlRedirectService = inject(UrlRedirectService);
653
+ const environment = inject(ENVIRONMENT);
654
+ if (tokenRepository.isAuthenticated()) {
655
+ return true;
656
+ }
657
+ // Store the current URL for redirection after login
658
+ urlRedirectService.storeIntendedUrl(state.url);
659
+ // Redirect to login page (configurable via environment)
660
+ router.navigate([environment.loginRoute]);
661
+ return false;
662
+ };
663
+
664
+ /**
665
+ * Interceptor that handles authentication errors and manages URL redirection
666
+ * Captures the current URL when a 401 error occurs and redirects to login
667
+ */
668
+ const authRedirectInterceptor = (req, next) => {
669
+ const router = inject(Router);
670
+ const urlRedirectService = inject(UrlRedirectService);
671
+ const tokenRepository = inject(TokenRepository);
672
+ const environment = inject(ENVIRONMENT);
673
+ return next(req).pipe(catchError$1((error) => {
674
+ // Handle 401 Unauthorized errors
675
+ if (error.status === 401) {
676
+ // Only store and redirect if user was previously authenticated
677
+ // This prevents redirect loops and handles session expiry scenarios
678
+ if (tokenRepository.isAuthenticated()) {
679
+ // Store the current URL for redirection after re-authentication
680
+ urlRedirectService.storeCurrentUrlIfAllowed();
681
+ // Navigate to login page
682
+ router.navigate([environment.loginRoute]);
683
+ }
684
+ }
685
+ // Re-throw the error so other error handlers can process it
686
+ return throwError(() => error);
687
+ }));
688
+ };
689
+
690
+ // src/lib/services/csrf.service.ts
691
+ class CsrfService {
692
+ http = inject(HttpClient);
693
+ csrfToken = null;
694
+ /**
695
+ * Get CSRF token, fetching it if not available
696
+ */
697
+ async getCsrfToken() {
698
+ if (this.csrfToken) {
699
+ return this.csrfToken;
700
+ }
701
+ try {
702
+ this.csrfToken = await firstValueFrom(this.http
703
+ .get('/csrf-token')
704
+ .pipe(map(response => response.csrfToken)));
705
+ return this.csrfToken || '';
706
+ }
707
+ catch {
708
+ // If CSRF endpoint fails, return empty token
709
+ // Server should handle missing CSRF tokens appropriately
710
+ return '';
711
+ }
712
+ }
713
+ /**
714
+ * Clear stored CSRF token (useful on logout)
715
+ */
716
+ clearCsrfToken() {
717
+ this.csrfToken = null;
718
+ }
719
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: CsrfService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
720
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: CsrfService, providedIn: 'root' });
721
+ }
722
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: CsrfService, decorators: [{
723
+ type: Injectable,
724
+ args: [{
725
+ providedIn: 'root',
726
+ }]
727
+ }] });
728
+
729
+ // A token to use with HttpContext for skipping CSRF token addition on specific requests.
730
+ const SKIP_CSRF = new HttpContextToken(() => false);
731
+ /**
732
+ * HTTP interceptor that automatically adds CSRF tokens to state-changing requests
733
+ * Only applies to requests to the same origin to avoid leaking tokens to external APIs
734
+ */
735
+ const csrfInterceptor = (req, next) => {
736
+ const csrfService = inject(CsrfService);
737
+ // Check if CSRF should be skipped for this request
738
+ const skipCsrf = req.context.get(SKIP_CSRF);
739
+ if (skipCsrf) {
740
+ return next(req);
741
+ }
742
+ // Only add CSRF token to state-changing requests (POST, PUT, PATCH, DELETE)
743
+ const isStateChangingMethod = ['POST', 'PUT', 'PATCH', 'DELETE'].includes(req.method.toUpperCase());
744
+ // Only add CSRF token to same-origin requests
745
+ const isSameOrigin = isRequestToSameOrigin(req);
746
+ if (isStateChangingMethod && isSameOrigin) {
747
+ return from(csrfService.getCsrfToken()).pipe(switchMap(csrfToken => {
748
+ const modifiedReq = req.clone({
749
+ setHeaders: {
750
+ 'X-CSRF-Token': csrfToken,
751
+ },
752
+ });
753
+ return next(modifiedReq);
754
+ }));
755
+ }
756
+ // For non-state-changing requests or external requests, proceed without modification
757
+ return next(req);
758
+ };
759
+ /**
760
+ * Checks if the request is going to the same origin as the current application
761
+ */
762
+ function isRequestToSameOrigin(req) {
763
+ try {
764
+ const requestUrl = new URL(req.url, window.location.origin);
765
+ return requestUrl.origin === window.location.origin;
766
+ }
767
+ catch {
768
+ // If URL parsing fails, assume it's not same origin for security
769
+ return false;
770
+ }
771
+ }
772
+
773
+ const authProviders = [
774
+ {
775
+ provide: AuthRepository,
776
+ useClass: AuthHttpRepository,
777
+ },
778
+ {
779
+ provide: TOKEN_PROVIDER,
780
+ useClass: TokenRepository,
781
+ },
782
+ ];
783
+
784
+ // src/lib/providers/index.ts
785
+
751
786
  // src/lib/presentation/stores/index.ts
752
787
 
753
788
  // src/lib/presentation/components/login/login.component.ts
@@ -848,7 +883,7 @@ class LoginComponent {
848
883
  }
849
884
  }
850
885
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: LoginComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
851
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.2", type: LoginComponent, isStandalone: true, selector: "acp-login", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, showRegisterButton: { classPropertyName: "showRegisterButton", publicName: "showRegisterButton", isSignal: true, isRequired: false, transformFunction: null }, showRememberMe: { classPropertyName: "showRememberMe", publicName: "showRememberMe", isSignal: true, isRequired: false, transformFunction: null }, additionalSigninControls: { classPropertyName: "additionalSigninControls", publicName: "additionalSigninControls", isSignal: true, isRequired: false, transformFunction: null }, additionalSignupControls: { classPropertyName: "additionalSignupControls", publicName: "additionalSignupControls", isSignal: true, isRequired: false, transformFunction: null }, additionalSigninFields: { classPropertyName: "additionalSigninFields", publicName: "additionalSigninFields", isSignal: true, isRequired: false, transformFunction: null }, additionalSignupFields: { classPropertyName: "additionalSignupFields", publicName: "additionalSignupFields", isSignal: true, isRequired: false, transformFunction: null }, footerContent: { classPropertyName: "footerContent", publicName: "footerContent", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<section id=\"wrapper\" class=\"d-flex justify-content-center align-items-center\">\n <mat-card class=\"mat-elevation-z8 p-4 rounded\">\n <mat-card-header>\n <mat-card-title class=\"text-center\">{{ title() }}</mat-card-title>\n </mat-card-header>\n <mat-card-content>\n @if (isLoginMode()) {\n <form [formGroup]=\"signinForm\" (ngSubmit)=\"signIn()\" class=\"d-flex flex-column gap-3\">\n <mat-form-field class=\"w-100\">\n <mat-label>Usuario</mat-label>\n <input matInput type=\"text\" placeholder=\"Ingrese su usuario\" formControlName=\"email\" />\n </mat-form-field>\n\n <mat-form-field class=\"w-100\">\n <mat-label>Contrase\u00F1a</mat-label>\n <input\n matInput\n type=\"password\"\n placeholder=\"Ingrese su contrase\u00F1a\"\n formControlName=\"password\"\n autocomplete=\"current-password\"\n />\n </mat-form-field>\n\n <!-- Remember Me checkbox - conditional -->\n @if (showRememberMe()) {\n <div class=\"d-flex align-items-center mt-2\">\n <mat-checkbox formControlName=\"rememberMe\"> Recordarme </mat-checkbox>\n </div>\n }\n\n <!-- Additional signin fields -->\n <ng-container *ngTemplateOutlet=\"additionalSigninFields()\"></ng-container>\n\n <div class=\"d-flex justify-content-center mt-3\">\n <button\n mat-raised-button\n color=\"primary\"\n [disabled]=\"!signinForm.valid || isLoading()\"\n type=\"submit\"\n class=\"w-100\"\n >\n @if (isLoading()) {\n Ingresando...\n } @else {\n <ng-container>Ingresar <mat-icon>login</mat-icon></ng-container>\n }\n </button>\n </div>\n\n <div class=\"text-center mt-2\">\n @if (showRegisterButton()) {\n <button mat-button type=\"button\" (click)=\"switchMode()\">\n \u00BFNo tienes cuenta? Reg\u00EDstrate\n </button>\n }\n </div>\n </form>\n } @else {\n <form [formGroup]=\"signupForm\" (ngSubmit)=\"registerUser()\" class=\"d-flex flex-column gap-3\">\n <mat-form-field class=\"w-100\">\n <mat-label>Nombre</mat-label>\n <input\n matInput\n type=\"text\"\n placeholder=\"Ingrese su nombre\"\n formControlName=\"displayName\"\n />\n </mat-form-field>\n\n <mat-form-field class=\"w-100\">\n <mat-label>Email</mat-label>\n <input matInput type=\"email\" placeholder=\"Ingrese su email\" formControlName=\"email\" />\n </mat-form-field>\n\n <mat-form-field class=\"w-100\">\n <mat-label>Contrase\u00F1a</mat-label>\n <input\n matInput\n type=\"password\"\n placeholder=\"Ingrese su contrase\u00F1a\"\n formControlName=\"password\"\n autocomplete=\"new-password\"\n />\n </mat-form-field>\n\n <!-- Additional signup fields -->\n <ng-container *ngTemplateOutlet=\"additionalSignupFields()\"></ng-container>\n\n <div class=\"d-flex justify-content-center mt-3\">\n <button\n mat-raised-button\n color=\"primary\"\n [disabled]=\"!signupForm.valid || isLoading()\"\n type=\"submit\"\n class=\"w-100\"\n >\n @if (isLoading()) {\n Registrando...\n } @else {\n <ng-container>Registrarse <mat-icon>person_add</mat-icon></ng-container>\n }\n </button>\n </div>\n\n <div class=\"text-center mt-2\">\n <button mat-button type=\"button\" (click)=\"switchMode()\">\n \u00BFYa tienes cuenta? Inicia sesi\u00F3n\n </button>\n </div>\n </form>\n }\n @if (errorMessage()) {\n <div class=\"alert alert-danger mt-3\" role=\"alert\">\n {{ errorMessage() }}\n </div>\n }\n </mat-card-content>\n @if (hasFooterContent()) {\n <mat-card-footer>\n <ng-container *ngTemplateOutlet=\"footerContent()\"></ng-container>\n </mat-card-footer>\n }\n </mat-card>\n</section>\n", styles: ["#wrapper{min-height:100vh;display:flex;align-items:center;justify-content:center;background:linear-gradient(135deg,var(--mdc-theme-primary, #5c5f5c) 0%,var(--mdc-theme-secondary, #79747e) 100%);padding:20px}mat-card{width:100%;max-width:400px;border-radius:12px;box-shadow:0 8px 32px #0000001a}mat-card-header{text-align:center;margin-bottom:20px}mat-card-title{font-size:24px;font-weight:600;color:var(--mdc-theme-on-surface, #1c1b1f)}mat-form-field{width:100%}.w-100{width:100%}.d-flex{display:flex}.flex-column{flex-direction:column}.gap-3{gap:12px}.justify-content-center{justify-content:center}.align-items-center{align-items:center}.mt-3{margin-top:12px}.mt-2{margin-top:8px}.text-center{text-align:center}.p-4{padding:16px}.rounded{border-radius:12px}.alert-danger{background-color:var(--mdc-theme-error-container, #ffdad6);border-color:var(--mdc-theme-error, #ba1a1a);color:var(--mdc-theme-on-error-container, #410002);padding:12px;border-radius:4px;border:1px solid transparent}.row{display:flex;flex-wrap:wrap;margin:0 -15px}.col-xs-12,.col-sm-12,.col-md-12{flex:0 0 100%;max-width:100%;padding:0 15px}.m-t-10{margin-top:10px}.social{display:flex;justify-content:center}mat-button{border-radius:8px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "component", type: MatCardHeader, selector: "mat-card-header" }, { kind: "directive", type: MatLabel, selector: "mat-label" }, { kind: "directive", type: MatCardTitle, selector: "mat-card-title, [mat-card-title], [matCardTitle]" }, { kind: "directive", type: MatCardContent, selector: "mat-card-content" }, { kind: "component", type: MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "directive", type: MatCardFooter, selector: "mat-card-footer" }, { kind: "component", type: MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
886
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.2", type: LoginComponent, isStandalone: true, selector: "acp-login", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, showRegisterButton: { classPropertyName: "showRegisterButton", publicName: "showRegisterButton", isSignal: true, isRequired: false, transformFunction: null }, showRememberMe: { classPropertyName: "showRememberMe", publicName: "showRememberMe", isSignal: true, isRequired: false, transformFunction: null }, additionalSigninControls: { classPropertyName: "additionalSigninControls", publicName: "additionalSigninControls", isSignal: true, isRequired: false, transformFunction: null }, additionalSignupControls: { classPropertyName: "additionalSignupControls", publicName: "additionalSignupControls", isSignal: true, isRequired: false, transformFunction: null }, additionalSigninFields: { classPropertyName: "additionalSigninFields", publicName: "additionalSigninFields", isSignal: true, isRequired: false, transformFunction: null }, additionalSignupFields: { classPropertyName: "additionalSignupFields", publicName: "additionalSignupFields", isSignal: true, isRequired: false, transformFunction: null }, footerContent: { classPropertyName: "footerContent", publicName: "footerContent", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<mat-card class=\"mat-elevation-z8 p-4 rounded\">\n <mat-card-header>\n <mat-card-title class=\"text-center\">{{ title() }}</mat-card-title>\n </mat-card-header>\n <mat-card-content>\n @if (isLoginMode()) {\n <form [formGroup]=\"signinForm\" (ngSubmit)=\"signIn()\" class=\"d-flex flex-column gap-3\">\n <mat-form-field class=\"w-100\">\n <mat-label>Usuario</mat-label>\n <input matInput type=\"text\" placeholder=\"Ingrese su usuario\" formControlName=\"email\" />\n </mat-form-field>\n\n <mat-form-field class=\"w-100\">\n <mat-label>Contrase\u00F1a</mat-label>\n <input\n matInput\n type=\"password\"\n placeholder=\"Ingrese su contrase\u00F1a\"\n formControlName=\"password\"\n autocomplete=\"current-password\"\n />\n </mat-form-field>\n\n <!-- Remember Me checkbox - conditional -->\n @if (showRememberMe()) {\n <div class=\"d-flex align-items-center mt-2\">\n <mat-checkbox formControlName=\"rememberMe\"> Recordarme </mat-checkbox>\n </div>\n }\n\n <!-- Additional signin fields -->\n <ng-container *ngTemplateOutlet=\"additionalSigninFields()\"></ng-container>\n\n <div class=\"d-flex justify-content-center mt-3\">\n <button\n mat-raised-button\n color=\"primary\"\n [disabled]=\"!signinForm.valid || isLoading()\"\n type=\"submit\"\n class=\"w-100\"\n >\n @if (isLoading()) {\n Ingresando...\n } @else {\n <ng-container>Ingresar <mat-icon>login</mat-icon></ng-container>\n }\n </button>\n </div>\n\n <div class=\"text-center mt-2\">\n @if (showRegisterButton()) {\n <button mat-button type=\"button\" (click)=\"switchMode()\">\n \u00BFNo tienes cuenta? Reg\u00EDstrate\n </button>\n }\n </div>\n </form>\n } @else {\n <form [formGroup]=\"signupForm\" (ngSubmit)=\"registerUser()\" class=\"d-flex flex-column gap-3\">\n <mat-form-field class=\"w-100\">\n <mat-label>Nombre</mat-label>\n <input\n matInput\n type=\"text\"\n placeholder=\"Ingrese su nombre\"\n formControlName=\"displayName\"\n />\n </mat-form-field>\n\n <mat-form-field class=\"w-100\">\n <mat-label>Email</mat-label>\n <input matInput type=\"email\" placeholder=\"Ingrese su email\" formControlName=\"email\" />\n </mat-form-field>\n\n <mat-form-field class=\"w-100\">\n <mat-label>Contrase\u00F1a</mat-label>\n <input\n matInput\n type=\"password\"\n placeholder=\"Ingrese su contrase\u00F1a\"\n formControlName=\"password\"\n autocomplete=\"new-password\"\n />\n </mat-form-field>\n\n <!-- Additional signup fields -->\n <ng-container *ngTemplateOutlet=\"additionalSignupFields()\"></ng-container>\n\n <div class=\"d-flex justify-content-center mt-3\">\n <button\n mat-raised-button\n color=\"primary\"\n [disabled]=\"!signupForm.valid || isLoading()\"\n type=\"submit\"\n class=\"w-100\"\n >\n @if (isLoading()) {\n Registrando...\n } @else {\n <ng-container>Registrarse <mat-icon>person_add</mat-icon></ng-container>\n }\n </button>\n </div>\n\n <div class=\"text-center mt-2\">\n <button mat-button type=\"button\" (click)=\"switchMode()\">\n \u00BFYa tienes cuenta? Inicia sesi\u00F3n\n </button>\n </div>\n </form>\n }\n @if (errorMessage()) {\n <div class=\"alert alert-danger mt-3\" role=\"alert\">\n {{ errorMessage() }}\n </div>\n }\n </mat-card-content>\n @if (hasFooterContent()) {\n <mat-card-footer>\n <ng-container *ngTemplateOutlet=\"footerContent()\"></ng-container>\n </mat-card-footer>\n }\n</mat-card>\n", styles: [":host{display:flex;justify-content:center;align-items:center;min-height:100vh;width:100%;padding:16px;box-sizing:border-box}mat-card{width:100%;max-width:400px;border-radius:12px;box-shadow:0 8px 32px #0000001a}mat-card-header{text-align:center;margin-bottom:20px}mat-card-title{font-size:24px;font-weight:600;color:var(--mat-sys-on-surface, #1c1b1f)}mat-form-field{width:100%}.w-100{width:100%}.d-flex{display:flex}.flex-column{flex-direction:column}.gap-3{gap:12px}.justify-content-center{justify-content:center}.align-items-center{align-items:center}.mt-3{margin-top:12px}.mt-2{margin-top:8px}.text-center{text-align:center}.p-4{padding:16px}.rounded{border-radius:12px}.alert-danger{background-color:var(--mat-sys-error-container, #ffdad6);border-color:var(--mat-sys-error, #ba1a1a);color:var(--mat-sys-on-error-container, #410002);padding:12px;border-radius:4px;border:1px solid transparent}.row{display:flex;flex-wrap:wrap;margin:0 -15px}.col-xs-12,.col-sm-12,.col-md-12{flex:0 0 100%;max-width:100%;padding:0 15px}.m-t-10{margin-top:10px}.social{display:flex;justify-content:center}mat-button{border-radius:8px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "component", type: MatCardHeader, selector: "mat-card-header" }, { kind: "directive", type: MatLabel, selector: "mat-label" }, { kind: "directive", type: MatCardTitle, selector: "mat-card-title, [mat-card-title], [matCardTitle]" }, { kind: "directive", type: MatCardContent, selector: "mat-card-content" }, { kind: "component", type: MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "directive", type: MatCardFooter, selector: "mat-card-footer" }, { kind: "component", type: MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
852
887
  }
853
888
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: LoginComponent, decorators: [{
854
889
  type: Component,
@@ -867,25 +902,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImpor
867
902
  MatCardFooter,
868
903
  MatAnchor,
869
904
  MatCheckbox,
870
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<section id=\"wrapper\" class=\"d-flex justify-content-center align-items-center\">\n <mat-card class=\"mat-elevation-z8 p-4 rounded\">\n <mat-card-header>\n <mat-card-title class=\"text-center\">{{ title() }}</mat-card-title>\n </mat-card-header>\n <mat-card-content>\n @if (isLoginMode()) {\n <form [formGroup]=\"signinForm\" (ngSubmit)=\"signIn()\" class=\"d-flex flex-column gap-3\">\n <mat-form-field class=\"w-100\">\n <mat-label>Usuario</mat-label>\n <input matInput type=\"text\" placeholder=\"Ingrese su usuario\" formControlName=\"email\" />\n </mat-form-field>\n\n <mat-form-field class=\"w-100\">\n <mat-label>Contrase\u00F1a</mat-label>\n <input\n matInput\n type=\"password\"\n placeholder=\"Ingrese su contrase\u00F1a\"\n formControlName=\"password\"\n autocomplete=\"current-password\"\n />\n </mat-form-field>\n\n <!-- Remember Me checkbox - conditional -->\n @if (showRememberMe()) {\n <div class=\"d-flex align-items-center mt-2\">\n <mat-checkbox formControlName=\"rememberMe\"> Recordarme </mat-checkbox>\n </div>\n }\n\n <!-- Additional signin fields -->\n <ng-container *ngTemplateOutlet=\"additionalSigninFields()\"></ng-container>\n\n <div class=\"d-flex justify-content-center mt-3\">\n <button\n mat-raised-button\n color=\"primary\"\n [disabled]=\"!signinForm.valid || isLoading()\"\n type=\"submit\"\n class=\"w-100\"\n >\n @if (isLoading()) {\n Ingresando...\n } @else {\n <ng-container>Ingresar <mat-icon>login</mat-icon></ng-container>\n }\n </button>\n </div>\n\n <div class=\"text-center mt-2\">\n @if (showRegisterButton()) {\n <button mat-button type=\"button\" (click)=\"switchMode()\">\n \u00BFNo tienes cuenta? Reg\u00EDstrate\n </button>\n }\n </div>\n </form>\n } @else {\n <form [formGroup]=\"signupForm\" (ngSubmit)=\"registerUser()\" class=\"d-flex flex-column gap-3\">\n <mat-form-field class=\"w-100\">\n <mat-label>Nombre</mat-label>\n <input\n matInput\n type=\"text\"\n placeholder=\"Ingrese su nombre\"\n formControlName=\"displayName\"\n />\n </mat-form-field>\n\n <mat-form-field class=\"w-100\">\n <mat-label>Email</mat-label>\n <input matInput type=\"email\" placeholder=\"Ingrese su email\" formControlName=\"email\" />\n </mat-form-field>\n\n <mat-form-field class=\"w-100\">\n <mat-label>Contrase\u00F1a</mat-label>\n <input\n matInput\n type=\"password\"\n placeholder=\"Ingrese su contrase\u00F1a\"\n formControlName=\"password\"\n autocomplete=\"new-password\"\n />\n </mat-form-field>\n\n <!-- Additional signup fields -->\n <ng-container *ngTemplateOutlet=\"additionalSignupFields()\"></ng-container>\n\n <div class=\"d-flex justify-content-center mt-3\">\n <button\n mat-raised-button\n color=\"primary\"\n [disabled]=\"!signupForm.valid || isLoading()\"\n type=\"submit\"\n class=\"w-100\"\n >\n @if (isLoading()) {\n Registrando...\n } @else {\n <ng-container>Registrarse <mat-icon>person_add</mat-icon></ng-container>\n }\n </button>\n </div>\n\n <div class=\"text-center mt-2\">\n <button mat-button type=\"button\" (click)=\"switchMode()\">\n \u00BFYa tienes cuenta? Inicia sesi\u00F3n\n </button>\n </div>\n </form>\n }\n @if (errorMessage()) {\n <div class=\"alert alert-danger mt-3\" role=\"alert\">\n {{ errorMessage() }}\n </div>\n }\n </mat-card-content>\n @if (hasFooterContent()) {\n <mat-card-footer>\n <ng-container *ngTemplateOutlet=\"footerContent()\"></ng-container>\n </mat-card-footer>\n }\n </mat-card>\n</section>\n", styles: ["#wrapper{min-height:100vh;display:flex;align-items:center;justify-content:center;background:linear-gradient(135deg,var(--mdc-theme-primary, #5c5f5c) 0%,var(--mdc-theme-secondary, #79747e) 100%);padding:20px}mat-card{width:100%;max-width:400px;border-radius:12px;box-shadow:0 8px 32px #0000001a}mat-card-header{text-align:center;margin-bottom:20px}mat-card-title{font-size:24px;font-weight:600;color:var(--mdc-theme-on-surface, #1c1b1f)}mat-form-field{width:100%}.w-100{width:100%}.d-flex{display:flex}.flex-column{flex-direction:column}.gap-3{gap:12px}.justify-content-center{justify-content:center}.align-items-center{align-items:center}.mt-3{margin-top:12px}.mt-2{margin-top:8px}.text-center{text-align:center}.p-4{padding:16px}.rounded{border-radius:12px}.alert-danger{background-color:var(--mdc-theme-error-container, #ffdad6);border-color:var(--mdc-theme-error, #ba1a1a);color:var(--mdc-theme-on-error-container, #410002);padding:12px;border-radius:4px;border:1px solid transparent}.row{display:flex;flex-wrap:wrap;margin:0 -15px}.col-xs-12,.col-sm-12,.col-md-12{flex:0 0 100%;max-width:100%;padding:0 15px}.m-t-10{margin-top:10px}.social{display:flex;justify-content:center}mat-button{border-radius:8px}\n"] }]
905
+ ], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<mat-card class=\"mat-elevation-z8 p-4 rounded\">\n <mat-card-header>\n <mat-card-title class=\"text-center\">{{ title() }}</mat-card-title>\n </mat-card-header>\n <mat-card-content>\n @if (isLoginMode()) {\n <form [formGroup]=\"signinForm\" (ngSubmit)=\"signIn()\" class=\"d-flex flex-column gap-3\">\n <mat-form-field class=\"w-100\">\n <mat-label>Usuario</mat-label>\n <input matInput type=\"text\" placeholder=\"Ingrese su usuario\" formControlName=\"email\" />\n </mat-form-field>\n\n <mat-form-field class=\"w-100\">\n <mat-label>Contrase\u00F1a</mat-label>\n <input\n matInput\n type=\"password\"\n placeholder=\"Ingrese su contrase\u00F1a\"\n formControlName=\"password\"\n autocomplete=\"current-password\"\n />\n </mat-form-field>\n\n <!-- Remember Me checkbox - conditional -->\n @if (showRememberMe()) {\n <div class=\"d-flex align-items-center mt-2\">\n <mat-checkbox formControlName=\"rememberMe\"> Recordarme </mat-checkbox>\n </div>\n }\n\n <!-- Additional signin fields -->\n <ng-container *ngTemplateOutlet=\"additionalSigninFields()\"></ng-container>\n\n <div class=\"d-flex justify-content-center mt-3\">\n <button\n mat-raised-button\n color=\"primary\"\n [disabled]=\"!signinForm.valid || isLoading()\"\n type=\"submit\"\n class=\"w-100\"\n >\n @if (isLoading()) {\n Ingresando...\n } @else {\n <ng-container>Ingresar <mat-icon>login</mat-icon></ng-container>\n }\n </button>\n </div>\n\n <div class=\"text-center mt-2\">\n @if (showRegisterButton()) {\n <button mat-button type=\"button\" (click)=\"switchMode()\">\n \u00BFNo tienes cuenta? Reg\u00EDstrate\n </button>\n }\n </div>\n </form>\n } @else {\n <form [formGroup]=\"signupForm\" (ngSubmit)=\"registerUser()\" class=\"d-flex flex-column gap-3\">\n <mat-form-field class=\"w-100\">\n <mat-label>Nombre</mat-label>\n <input\n matInput\n type=\"text\"\n placeholder=\"Ingrese su nombre\"\n formControlName=\"displayName\"\n />\n </mat-form-field>\n\n <mat-form-field class=\"w-100\">\n <mat-label>Email</mat-label>\n <input matInput type=\"email\" placeholder=\"Ingrese su email\" formControlName=\"email\" />\n </mat-form-field>\n\n <mat-form-field class=\"w-100\">\n <mat-label>Contrase\u00F1a</mat-label>\n <input\n matInput\n type=\"password\"\n placeholder=\"Ingrese su contrase\u00F1a\"\n formControlName=\"password\"\n autocomplete=\"new-password\"\n />\n </mat-form-field>\n\n <!-- Additional signup fields -->\n <ng-container *ngTemplateOutlet=\"additionalSignupFields()\"></ng-container>\n\n <div class=\"d-flex justify-content-center mt-3\">\n <button\n mat-raised-button\n color=\"primary\"\n [disabled]=\"!signupForm.valid || isLoading()\"\n type=\"submit\"\n class=\"w-100\"\n >\n @if (isLoading()) {\n Registrando...\n } @else {\n <ng-container>Registrarse <mat-icon>person_add</mat-icon></ng-container>\n }\n </button>\n </div>\n\n <div class=\"text-center mt-2\">\n <button mat-button type=\"button\" (click)=\"switchMode()\">\n \u00BFYa tienes cuenta? Inicia sesi\u00F3n\n </button>\n </div>\n </form>\n }\n @if (errorMessage()) {\n <div class=\"alert alert-danger mt-3\" role=\"alert\">\n {{ errorMessage() }}\n </div>\n }\n </mat-card-content>\n @if (hasFooterContent()) {\n <mat-card-footer>\n <ng-container *ngTemplateOutlet=\"footerContent()\"></ng-container>\n </mat-card-footer>\n }\n</mat-card>\n", styles: [":host{display:flex;justify-content:center;align-items:center;min-height:100vh;width:100%;padding:16px;box-sizing:border-box}mat-card{width:100%;max-width:400px;border-radius:12px;box-shadow:0 8px 32px #0000001a}mat-card-header{text-align:center;margin-bottom:20px}mat-card-title{font-size:24px;font-weight:600;color:var(--mat-sys-on-surface, #1c1b1f)}mat-form-field{width:100%}.w-100{width:100%}.d-flex{display:flex}.flex-column{flex-direction:column}.gap-3{gap:12px}.justify-content-center{justify-content:center}.align-items-center{align-items:center}.mt-3{margin-top:12px}.mt-2{margin-top:8px}.text-center{text-align:center}.p-4{padding:16px}.rounded{border-radius:12px}.alert-danger{background-color:var(--mat-sys-error-container, #ffdad6);border-color:var(--mat-sys-error, #ba1a1a);color:var(--mat-sys-on-error-container, #410002);padding:12px;border-radius:4px;border:1px solid transparent}.row{display:flex;flex-wrap:wrap;margin:0 -15px}.col-xs-12,.col-sm-12,.col-md-12{flex:0 0 100%;max-width:100%;padding:0 15px}.m-t-10{margin-top:10px}.social{display:flex;justify-content:center}mat-button{border-radius:8px}\n"] }]
871
906
  }], ctorParameters: () => [] });
872
907
 
873
908
  // src/lib/presentation/components/index.ts
874
909
 
875
910
  // src/lib/presentation/index.ts
876
911
 
877
- const authProviders = [
878
- {
879
- provide: AuthRepository,
880
- useClass: AuthHttpRepository,
881
- },
882
- ];
883
-
884
- // src/lib/providers/index.ts
885
-
886
912
  /**
887
913
  * Generated bundle index. Do not edit.
888
914
  */
889
915
 
890
- export { AuthHttpRepository, AuthRepository, AuthStore, AuthTokenService, LoginComponent, LoginUseCase, LogoutUseCase, RefreshTokenUseCase, RegisterUseCase, TokenRepository, UrlRedirectService, authGuard, authProviders, authRedirectInterceptor };
916
+ export { AuthHttpRepository, AuthRepository, AuthStore, CsrfService, LoginComponent, LoginUseCase, LogoutUseCase, RefreshTokenUseCase, RegisterUseCase, SKIP_CSRF, TokenRepository, UrlRedirectService, authGuard, authProviders, authRedirectInterceptor, csrfInterceptor };
891
917
  //# sourceMappingURL=acontplus-ng-auth.mjs.map