@acontplus/ng-auth 1.0.0 → 1.0.2

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,25 +1,50 @@
1
+ import { UserRepository } from '@acontplus/ng-infrastructure';
1
2
  export { TOKEN_PROVIDER } from '@acontplus/ng-infrastructure';
2
3
  import * as i0 from '@angular/core';
3
- import { inject, Injectable } from '@angular/core';
4
+ import { inject, PLATFORM_ID, Injectable, signal, input, computed, ChangeDetectionStrategy, Component } from '@angular/core';
4
5
  import { Router } from '@angular/router';
6
+ import * as i1 from '@angular/common';
7
+ import { isPlatformBrowser, CommonModule } from '@angular/common';
5
8
  import { jwtDecode } from 'jwt-decode';
6
- import { ENVIRONMENT } from '@acontplus/ng-config';
9
+ import { ENVIRONMENT, AUTH_API } from '@acontplus/ng-config';
10
+ import { HttpClient } from '@angular/common/http';
11
+ import { from, map, of, tap, catchError, throwError } from 'rxjs';
12
+ import { switchMap } from 'rxjs/operators';
13
+ import { AuthTokens } from '@acontplus/core';
14
+ import * as i2 from '@angular/forms';
15
+ import { FormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
16
+ import { MatCard, MatCardHeader, MatCardTitle, MatCardContent, MatCardFooter } from '@angular/material/card';
17
+ import { MatLabel, MatFormField } from '@angular/material/form-field';
18
+ import { MatInput } from '@angular/material/input';
19
+ import { MatIcon } from '@angular/material/icon';
20
+ import { MatButton, MatAnchor } from '@angular/material/button';
7
21
 
8
22
  class TokenRepository {
9
23
  environment = inject(ENVIRONMENT);
24
+ platformId = inject(PLATFORM_ID);
10
25
  saveTokens(tokens, rememberMe = false) {
11
- this.setToken(tokens.token, rememberMe);
12
- this.setRefreshToken(tokens.refreshToken, rememberMe);
26
+ if (isPlatformBrowser(this.platformId)) {
27
+ this.setToken(tokens.token, rememberMe);
28
+ this.setRefreshToken(tokens.refreshToken, rememberMe);
29
+ }
13
30
  }
14
31
  getAccessToken() {
32
+ if (!isPlatformBrowser(this.platformId)) {
33
+ return null;
34
+ }
15
35
  return (localStorage.getItem(this.environment.tokenKey) ||
16
36
  sessionStorage.getItem(this.environment.tokenKey));
17
37
  }
18
38
  getRefreshToken() {
19
- return (localStorage.getItem(this.environment.refreshTokenKey) ||
20
- sessionStorage.getItem(this.environment.refreshTokenKey));
39
+ // Refresh tokens are now stored in HttpOnly cookies by the server
40
+ // We can't read them directly from client-side code for security
41
+ // They are automatically sent with requests when withCredentials: true is used
42
+ return null;
21
43
  }
22
44
  setToken(token, rememberMe = false) {
45
+ if (!isPlatformBrowser(this.platformId)) {
46
+ return;
47
+ }
23
48
  if (rememberMe) {
24
49
  localStorage.setItem(this.environment.tokenKey, token);
25
50
  }
@@ -27,15 +52,15 @@ class TokenRepository {
27
52
  sessionStorage.setItem(this.environment.tokenKey, token);
28
53
  }
29
54
  }
30
- setRefreshToken(refreshToken, rememberMe = false) {
31
- if (rememberMe) {
32
- localStorage.setItem(this.environment.refreshTokenKey, refreshToken);
33
- }
34
- else {
35
- sessionStorage.setItem(this.environment.refreshTokenKey, refreshToken);
36
- }
55
+ setRefreshToken(_refreshToken, _rememberMe = false) {
56
+ // Refresh tokens are now set by the server as HttpOnly cookies
57
+ // Client-side code cannot set HttpOnly cookies for security reasons
58
+ // They are set in response to successful login/register requests
37
59
  }
38
60
  clearTokens() {
61
+ if (!isPlatformBrowser(this.platformId)) {
62
+ return;
63
+ }
39
64
  localStorage.removeItem(this.environment.tokenKey);
40
65
  localStorage.removeItem(this.environment.refreshTokenKey);
41
66
  sessionStorage.removeItem(this.environment.tokenKey);
@@ -43,30 +68,14 @@ class TokenRepository {
43
68
  }
44
69
  isAuthenticated() {
45
70
  const accessToken = this.getAccessToken();
46
- const refreshToken = this.getRefreshToken();
47
- if (!accessToken || !refreshToken) {
71
+ if (!accessToken) {
48
72
  return false;
49
73
  }
50
74
  try {
51
75
  const decodedAccessToken = jwtDecode(accessToken);
52
76
  const accessExpiration = Number(decodedAccessToken.exp);
53
77
  const currentTimeUTC = Math.floor(Date.now() / 1000);
54
- if (accessExpiration > currentTimeUTC) {
55
- return true;
56
- }
57
- return this.isRefreshTokenValid();
58
- }
59
- catch {
60
- return false;
61
- }
62
- }
63
- isRefreshTokenValid() {
64
- const refreshToken = this.getRefreshToken();
65
- if (!refreshToken) {
66
- return false;
67
- }
68
- try {
69
- return true; // Backend validates expiration
78
+ return accessExpiration > currentTimeUTC;
70
79
  }
71
80
  catch {
72
81
  return false;
@@ -140,9 +149,586 @@ const authGuard = (_route, _state) => {
140
149
  return false;
141
150
  };
142
151
 
152
+ // src/lib/services/csrf.service.ts
153
+ class CsrfService {
154
+ http = inject(HttpClient);
155
+ environment = inject(ENVIRONMENT);
156
+ csrfToken = null;
157
+ /**
158
+ * Get CSRF token, fetching it if not available
159
+ */
160
+ async getCsrfToken() {
161
+ if (this.csrfToken) {
162
+ return this.csrfToken;
163
+ }
164
+ try {
165
+ const response = await this.http
166
+ .get(`${this.environment.apiBaseUrl}/csrf-token`)
167
+ .toPromise();
168
+ this.csrfToken = response?.csrfToken || null;
169
+ return this.csrfToken || '';
170
+ }
171
+ catch {
172
+ // If CSRF endpoint fails, return empty token
173
+ // Server should handle missing CSRF tokens appropriately
174
+ return '';
175
+ }
176
+ }
177
+ /**
178
+ * Clear stored CSRF token (useful on logout)
179
+ */
180
+ clearCsrfToken() {
181
+ this.csrfToken = null;
182
+ }
183
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: CsrfService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
184
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: CsrfService, providedIn: 'root' });
185
+ }
186
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: CsrfService, decorators: [{
187
+ type: Injectable,
188
+ args: [{
189
+ providedIn: 'root',
190
+ }]
191
+ }] });
192
+
193
+ class User {
194
+ id;
195
+ email;
196
+ displayName;
197
+ _refreshToken;
198
+ constructor(id, email, displayName, _refreshToken) {
199
+ this.id = id;
200
+ this.email = email;
201
+ this.displayName = displayName;
202
+ this._refreshToken = _refreshToken;
203
+ }
204
+ get refreshToken() {
205
+ return this._refreshToken;
206
+ }
207
+ updateRefreshToken(token) {
208
+ this._refreshToken = token;
209
+ }
210
+ clearRefreshToken() {
211
+ this._refreshToken = undefined;
212
+ }
213
+ static create(email, displayName, _password) {
214
+ const id = Date.now(); // Generate a numeric ID
215
+ return new User(id, email, displayName);
216
+ }
217
+ }
218
+
219
+ // src/lib/domain/models/index.ts
220
+
221
+ class AuthRepository {
222
+ }
223
+
224
+ // src/lib/domain/repositories/index.ts
225
+
226
+ // src/lib/domain/index.ts
227
+
228
+ // src/lib/data/repositories/auth-http.repository.ts
229
+ function getDeviceInfo() {
230
+ return `${navigator.platform ?? 'Unknown'} - ${navigator.userAgent}`;
231
+ }
232
+ class AuthHttpRepository extends AuthRepository {
233
+ http = inject(HttpClient);
234
+ csrfService = inject(CsrfService);
235
+ URL = `${AUTH_API.ACCOUNT}`;
236
+ login(request) {
237
+ return from(this.csrfService.getCsrfToken()).pipe(switchMap(csrfToken => this.http.post(`${this.URL}login`, request, {
238
+ headers: {
239
+ 'Device-Info': getDeviceInfo(),
240
+ 'X-CSRF-Token': csrfToken,
241
+ },
242
+ withCredentials: true,
243
+ })));
244
+ }
245
+ register(request) {
246
+ return from(this.csrfService.getCsrfToken()).pipe(switchMap(csrfToken => this.http
247
+ .post(`${this.URL}register`, request, {
248
+ headers: {
249
+ 'Device-Info': getDeviceInfo(),
250
+ 'X-CSRF-Token': csrfToken,
251
+ },
252
+ withCredentials: true,
253
+ })
254
+ .pipe(map(response => {
255
+ // Convert string ID to number - if response.id is a string, hash it to get a number
256
+ const numericId = response.id !== ''
257
+ ? Math.abs(response.id.split('').reduce((a, b) => {
258
+ a = (a << 5) - a + b.charCodeAt(0);
259
+ return a & a;
260
+ }, 0))
261
+ : Date.now();
262
+ return new User(numericId, request.email, request.displayName);
263
+ }))));
264
+ }
265
+ refreshToken(request) {
266
+ return from(this.csrfService.getCsrfToken()).pipe(switchMap(csrfToken => this.http
267
+ .post(`${this.URL}refresh`, request, {
268
+ headers: {
269
+ 'Device-Info': getDeviceInfo(),
270
+ 'X-CSRF-Token': csrfToken,
271
+ },
272
+ withCredentials: true,
273
+ })
274
+ .pipe(map(response => new AuthTokens(response.token, response.refreshToken)))));
275
+ }
276
+ logout(email, refreshToken) {
277
+ return from(this.csrfService.getCsrfToken()).pipe(switchMap(csrfToken => this.http.post(`${this.URL}logout`, { email, refreshToken: refreshToken || undefined }, {
278
+ headers: { 'X-CSRF-Token': csrfToken },
279
+ withCredentials: true, // Ensure cookies are sent
280
+ })));
281
+ }
282
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: AuthHttpRepository, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
283
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: AuthHttpRepository, providedIn: 'root' });
284
+ }
285
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: AuthHttpRepository, decorators: [{
286
+ type: Injectable,
287
+ args: [{
288
+ providedIn: 'root',
289
+ }]
290
+ }] });
291
+
292
+ // src/lib/data/repositories/index.ts
293
+
294
+ // src/lib/data/index.ts
295
+
296
+ // src/lib/presentation/stores/auth.store.ts
297
+ class AuthStore {
298
+ authRepository = inject(AuthRepository);
299
+ tokenRepository = inject(TokenRepository);
300
+ userRepository = inject(UserRepository);
301
+ router = inject(Router);
302
+ // Authentication state signals
303
+ _isAuthenticated = signal(false, ...(ngDevMode ? [{ debugName: "_isAuthenticated" }] : []));
304
+ _isLoading = signal(false, ...(ngDevMode ? [{ debugName: "_isLoading" }] : []));
305
+ _user = signal(null, ...(ngDevMode ? [{ debugName: "_user" }] : []));
306
+ // Public readonly signals
307
+ isAuthenticated = this._isAuthenticated.asReadonly();
308
+ isLoading = this._isLoading.asReadonly();
309
+ user = this._user.asReadonly();
310
+ // Private refresh token timeout (instead of interval)
311
+ refreshTokenTimeout;
312
+ refreshInProgress$;
313
+ constructor() {
314
+ this.initializeAuthentication();
315
+ }
316
+ /**
317
+ * Initialize authentication state on app startup
318
+ */
319
+ initializeAuthentication() {
320
+ this._isLoading.set(true);
321
+ try {
322
+ const isAuthenticated = this.tokenRepository.isAuthenticated();
323
+ this._isAuthenticated.set(isAuthenticated);
324
+ if (isAuthenticated) {
325
+ const userData = this.userRepository.getCurrentUser();
326
+ this._user.set(userData);
327
+ this.scheduleTokenRefresh();
328
+ }
329
+ }
330
+ catch {
331
+ this.logout();
332
+ }
333
+ finally {
334
+ this._isLoading.set(false);
335
+ }
336
+ }
337
+ /**
338
+ * Schedule token refresh based on actual expiration time
339
+ */
340
+ scheduleTokenRefresh() {
341
+ const accessToken = this.tokenRepository.getAccessToken();
342
+ if (!accessToken) {
343
+ return;
344
+ }
345
+ try {
346
+ const decodedToken = this.decodeToken(accessToken);
347
+ const currentTime = Math.floor(Date.now() / 1000);
348
+ const timeUntilExpiry = decodedToken.exp - currentTime;
349
+ // Refresh 5 minutes before expiry (300 seconds)
350
+ const refreshTime = Math.max((timeUntilExpiry - 300) * 1000, 1000);
351
+ // Clear any existing timeout
352
+ if (this.refreshTokenTimeout) {
353
+ clearTimeout(this.refreshTokenTimeout);
354
+ }
355
+ this.refreshTokenTimeout = window.setTimeout(() => {
356
+ // Check if refresh is still needed before executing
357
+ if (this.tokenRepository.needsRefresh()) {
358
+ this.refreshToken().subscribe();
359
+ }
360
+ }, refreshTime);
361
+ }
362
+ catch {
363
+ // Silent fail - token might be invalid
364
+ }
365
+ }
366
+ /**
367
+ * Stop token refresh timer
368
+ */
369
+ stopTokenRefreshTimer() {
370
+ if (this.refreshTokenTimeout) {
371
+ clearTimeout(this.refreshTokenTimeout);
372
+ this.refreshTokenTimeout = undefined;
373
+ }
374
+ }
375
+ /**
376
+ * Refresh authentication tokens
377
+ */
378
+ refreshToken() {
379
+ // Return existing refresh request if one is in progress
380
+ if (this.refreshInProgress$) {
381
+ return this.refreshInProgress$;
382
+ }
383
+ const userData = this.userRepository.getCurrentUser();
384
+ const refreshToken = this.tokenRepository.getRefreshToken();
385
+ if (!userData?.email || !refreshToken) {
386
+ this.logout();
387
+ return of(null);
388
+ }
389
+ this.refreshInProgress$ = this.authRepository
390
+ .refreshToken({
391
+ email: userData.email,
392
+ refreshToken,
393
+ })
394
+ .pipe(tap(tokens => {
395
+ this.setAuthenticated(tokens);
396
+ }), catchError(() => {
397
+ this.logout();
398
+ return of(null);
399
+ }), tap({
400
+ complete: () => {
401
+ this.refreshInProgress$ = undefined;
402
+ },
403
+ error: () => {
404
+ this.refreshInProgress$ = undefined;
405
+ }
406
+ }));
407
+ return this.refreshInProgress$;
408
+ }
409
+ /**
410
+ * Set authentication state after successful login
411
+ */
412
+ setAuthenticated(tokens) {
413
+ this.tokenRepository.saveTokens(tokens);
414
+ this._isAuthenticated.set(true);
415
+ const userData = this.userRepository.getCurrentUser();
416
+ this._user.set(userData);
417
+ this.scheduleTokenRefresh();
418
+ }
419
+ /**
420
+ * Logout user and clear all authentication data
421
+ */
422
+ logout() {
423
+ const user = this._user();
424
+ if (user) {
425
+ // Call server-side logout first
426
+ this.authRepository.logout(user.email, '').subscribe({
427
+ next: () => {
428
+ // Server logout successful, clear client-side data
429
+ this.performClientLogout();
430
+ },
431
+ error: () => {
432
+ // Server logout failed, still clear client-side data for security
433
+ this.performClientLogout();
434
+ }
435
+ });
436
+ }
437
+ else {
438
+ // No user data, just clear client-side data
439
+ this.performClientLogout();
440
+ }
441
+ }
442
+ /**
443
+ * Perform client-side logout operations
444
+ */
445
+ performClientLogout() {
446
+ this.stopTokenRefreshTimer();
447
+ this.tokenRepository.clearTokens();
448
+ this._isAuthenticated.set(false);
449
+ this._user.set(null);
450
+ // Navigate to login page
451
+ this.router.navigate(['/auth']);
452
+ }
453
+ /**
454
+ * Check if user is authenticated
455
+ */
456
+ checkAuthentication() {
457
+ const isAuthenticated = this.tokenRepository.isAuthenticated();
458
+ this._isAuthenticated.set(isAuthenticated);
459
+ if (!isAuthenticated) {
460
+ this.logout();
461
+ }
462
+ return isAuthenticated;
463
+ }
464
+ /**
465
+ * Decode JWT token
466
+ */
467
+ decodeToken(token) {
468
+ try {
469
+ const base64Url = token.split('.')[1];
470
+ const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
471
+ const jsonPayload = decodeURIComponent(atob(base64)
472
+ .split('')
473
+ .map(c => {
474
+ return `%${`00${c.charCodeAt(0).toString(16)}`.slice(-2)}`;
475
+ })
476
+ .join(''));
477
+ return JSON.parse(jsonPayload);
478
+ }
479
+ catch {
480
+ throw new Error('Invalid token format');
481
+ }
482
+ }
483
+ /**
484
+ * Cleanup on store destruction
485
+ */
486
+ ngOnDestroy() {
487
+ this.stopTokenRefreshTimer();
488
+ }
489
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: AuthStore, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
490
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: AuthStore, providedIn: 'root' });
491
+ }
492
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: AuthStore, decorators: [{
493
+ type: Injectable,
494
+ args: [{
495
+ providedIn: 'root',
496
+ }]
497
+ }], ctorParameters: () => [] });
498
+
499
+ // src/lib/application/use-cases/login.use-case.ts
500
+ class LoginUseCase {
501
+ authRepository = inject(AuthRepository);
502
+ authStore = inject(AuthStore);
503
+ router = inject(Router);
504
+ execute(request) {
505
+ return this.authRepository.login(request).pipe(tap(tokens => {
506
+ // Set authentication state
507
+ this.authStore.setAuthenticated(tokens);
508
+ // Navigate to main page
509
+ this.router.navigate(['/']);
510
+ }));
511
+ }
512
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: LoginUseCase, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
513
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: LoginUseCase, providedIn: 'root' });
514
+ }
515
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: LoginUseCase, decorators: [{
516
+ type: Injectable,
517
+ args: [{
518
+ providedIn: 'root',
519
+ }]
520
+ }] });
521
+
522
+ // src/lib/application/use-cases/register.use-case.ts
523
+ class RegisterUseCase {
524
+ authRepository = inject(AuthRepository);
525
+ router = inject(Router);
526
+ execute(request) {
527
+ return this.authRepository.register(request).pipe(tap(() => {
528
+ this.router.navigate(['/', 'auth']);
529
+ }));
530
+ }
531
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: RegisterUseCase, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
532
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: RegisterUseCase, providedIn: 'root' });
533
+ }
534
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: RegisterUseCase, decorators: [{
535
+ type: Injectable,
536
+ args: [{
537
+ providedIn: 'root',
538
+ }]
539
+ }] });
540
+
541
+ // src/lib/application/use-cases/refresh-token.use-case.ts
542
+ class RefreshTokenUseCase {
543
+ authRepository = inject(AuthRepository);
544
+ userRepository = inject(UserRepository);
545
+ tokenRepository = inject(TokenRepository);
546
+ authStore = inject(AuthStore);
547
+ execute() {
548
+ const userData = this.userRepository.getCurrentUser();
549
+ const refreshToken = this.tokenRepository.getRefreshToken();
550
+ if (!userData?.email || !refreshToken || refreshToken.trim().length === 0) {
551
+ const error = new Error('No refresh token or email available');
552
+ return throwError(() => error);
553
+ }
554
+ return this.authRepository
555
+ .refreshToken({
556
+ email: userData.email,
557
+ refreshToken,
558
+ })
559
+ .pipe(tap(tokens => {
560
+ // Update authentication state
561
+ this.authStore.setAuthenticated(tokens);
562
+ }), catchError(error => {
563
+ // Don't logout here, let the interceptor handle it
564
+ return throwError(() => error);
565
+ }));
566
+ }
567
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: RefreshTokenUseCase, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
568
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: RefreshTokenUseCase, providedIn: 'root' });
569
+ }
570
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: RefreshTokenUseCase, decorators: [{
571
+ type: Injectable,
572
+ args: [{
573
+ providedIn: 'root',
574
+ }]
575
+ }] });
576
+
577
+ // src/lib/application/use-cases/logout.use-case.ts
578
+ class LogoutUseCase {
579
+ authRepository = inject(AuthRepository);
580
+ userRepository = inject(UserRepository);
581
+ tokenRepository = inject(TokenRepository);
582
+ authStore = inject(AuthStore);
583
+ execute() {
584
+ const userData = this.userRepository.getCurrentUser();
585
+ const refreshToken = this.tokenRepository.getRefreshToken();
586
+ if (userData?.email && refreshToken && refreshToken.length > 0) {
587
+ return this.authRepository.logout(userData.email, refreshToken).pipe(tap(() => this.cleanup()));
588
+ }
589
+ this.cleanup();
590
+ return of(void 0);
591
+ }
592
+ cleanup() {
593
+ // Use auth store for centralized logout
594
+ this.authStore.logout();
595
+ }
596
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: LogoutUseCase, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
597
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: LogoutUseCase, providedIn: 'root' });
598
+ }
599
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: LogoutUseCase, decorators: [{
600
+ type: Injectable,
601
+ args: [{
602
+ providedIn: 'root',
603
+ }]
604
+ }] });
605
+
606
+ // src/lib/application/use-cases/index.ts
607
+
608
+ // src/lib/application/index.ts
609
+
610
+ // src/lib/presentation/stores/index.ts
611
+
612
+ // src/lib/presentation/components/login/login.component.ts
613
+ class LoginComponent {
614
+ title = input('Login', ...(ngDevMode ? [{ debugName: "title" }] : []));
615
+ showRegisterButton = input(true, ...(ngDevMode ? [{ debugName: "showRegisterButton" }] : []));
616
+ // Additional form controls that can be passed from parent components
617
+ additionalSigninControls = input({}, ...(ngDevMode ? [{ debugName: "additionalSigninControls" }] : []));
618
+ additionalSignupControls = input({}, ...(ngDevMode ? [{ debugName: "additionalSignupControls" }] : []));
619
+ // Additional field templates
620
+ additionalSigninFields = input(null, ...(ngDevMode ? [{ debugName: "additionalSigninFields" }] : []));
621
+ additionalSignupFields = input(null, ...(ngDevMode ? [{ debugName: "additionalSignupFields" }] : []));
622
+ // Footer content template
623
+ footerContent = input(null, ...(ngDevMode ? [{ debugName: "footerContent" }] : []));
624
+ // Computed signal to check if footer content exists
625
+ hasFooterContent = computed(() => this.footerContent() !== null, ...(ngDevMode ? [{ debugName: "hasFooterContent" }] : []));
626
+ fb = inject(FormBuilder);
627
+ loginUseCase = inject(LoginUseCase);
628
+ registerUseCase = inject(RegisterUseCase);
629
+ // Angular 20+ signals
630
+ isLoginMode = signal(true, ...(ngDevMode ? [{ debugName: "isLoginMode" }] : []));
631
+ isLoading = signal(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
632
+ errorMessage = signal(null, ...(ngDevMode ? [{ debugName: "errorMessage" }] : []));
633
+ signinForm;
634
+ signupForm;
635
+ constructor() {
636
+ this.signinForm = this.fb.group({
637
+ email: ['', [Validators.required, Validators.email]],
638
+ password: ['', Validators.required],
639
+ });
640
+ this.signupForm = this.fb.group({
641
+ displayName: ['', Validators.required],
642
+ email: ['', [Validators.required, Validators.email]],
643
+ password: ['', [Validators.required, Validators.minLength(6)]],
644
+ });
645
+ }
646
+ ngOnInit() {
647
+ // Add additional controls to signin form
648
+ Object.entries(this.additionalSigninControls()).forEach(([key, control]) => {
649
+ this.signinForm.addControl(key, control);
650
+ });
651
+ // Add additional controls to signup form
652
+ Object.entries(this.additionalSignupControls()).forEach(([key, control]) => {
653
+ this.signupForm.addControl(key, control);
654
+ });
655
+ }
656
+ switchMode() {
657
+ this.isLoginMode.set(!this.isLoginMode());
658
+ this.errorMessage.set(null);
659
+ }
660
+ signIn() {
661
+ if (this.signinForm.valid) {
662
+ this.isLoading.set(true);
663
+ this.errorMessage.set(null);
664
+ this.loginUseCase.execute(this.signinForm.value).subscribe({
665
+ next: () => {
666
+ this.isLoading.set(false);
667
+ },
668
+ error: error => {
669
+ this.isLoading.set(false);
670
+ this.errorMessage.set('Error al iniciar sesión. Verifique sus credenciales.');
671
+ console.error('Login error:', error);
672
+ },
673
+ });
674
+ }
675
+ }
676
+ registerUser() {
677
+ if (this.signupForm.valid) {
678
+ this.isLoading.set(true);
679
+ this.errorMessage.set(null);
680
+ this.registerUseCase.execute(this.signupForm.value).subscribe({
681
+ next: () => {
682
+ this.isLoading.set(false);
683
+ this.signupForm.reset();
684
+ this.isLoginMode.set(true);
685
+ },
686
+ error: error => {
687
+ this.isLoading.set(false);
688
+ this.errorMessage.set('Error al registrar usuario. Intente nuevamente.');
689
+ console.error('Register error:', error);
690
+ },
691
+ });
692
+ }
693
+ }
694
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: LoginComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
695
+ 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 }, 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 matInput type=\"password\" placeholder=\"Ingrese su contrase\u00F1a\" formControlName=\"password\" />\n </mat-form-field>\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 mat-raised-button color=\"primary\" [disabled]=\"!signinForm.valid || isLoading()\" type=\"submit\"\n class=\"w-100\">\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 matInput type=\"text\" placeholder=\"Ingrese su nombre\" formControlName=\"displayName\" />\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 matInput type=\"password\" placeholder=\"Ingrese su contrase\u00F1a\" formControlName=\"password\" />\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 mat-raised-button color=\"primary\" [disabled]=\"!signupForm.valid || isLoading()\" type=\"submit\"\n class=\"w-100\">\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()\">\u00BFYa tienes cuenta? Inicia sesi\u00F3n</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" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
696
+ }
697
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: LoginComponent, decorators: [{
698
+ type: Component,
699
+ args: [{ selector: 'acp-login', imports: [
700
+ CommonModule,
701
+ ReactiveFormsModule,
702
+ MatCard,
703
+ MatCardHeader,
704
+ MatLabel,
705
+ MatCardTitle,
706
+ MatCardContent,
707
+ MatFormField,
708
+ MatInput,
709
+ MatIcon,
710
+ MatButton,
711
+ MatCardFooter,
712
+ MatAnchor,
713
+ ], 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 matInput type=\"password\" placeholder=\"Ingrese su contrase\u00F1a\" formControlName=\"password\" />\n </mat-form-field>\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 mat-raised-button color=\"primary\" [disabled]=\"!signinForm.valid || isLoading()\" type=\"submit\"\n class=\"w-100\">\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 matInput type=\"text\" placeholder=\"Ingrese su nombre\" formControlName=\"displayName\" />\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 matInput type=\"password\" placeholder=\"Ingrese su contrase\u00F1a\" formControlName=\"password\" />\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 mat-raised-button color=\"primary\" [disabled]=\"!signupForm.valid || isLoading()\" type=\"submit\"\n class=\"w-100\">\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()\">\u00BFYa tienes cuenta? Inicia sesi\u00F3n</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"] }]
714
+ }], ctorParameters: () => [] });
715
+
716
+ // src/lib/presentation/components/index.ts
717
+
718
+ // src/lib/presentation/index.ts
719
+
720
+ const authProviders = [
721
+ {
722
+ provide: AuthRepository,
723
+ useClass: AuthHttpRepository,
724
+ },
725
+ ];
726
+
727
+ // src/lib/providers/index.ts
728
+
143
729
  /**
144
730
  * Generated bundle index. Do not edit.
145
731
  */
146
732
 
147
- export { AuthTokenService, TokenRepository, authGuard };
733
+ export { AuthHttpRepository, AuthRepository, AuthStore, AuthTokenService, CsrfService, LoginComponent, LoginUseCase, LogoutUseCase, RefreshTokenUseCase, RegisterUseCase, TokenRepository, User, authGuard, authProviders };
148
734
  //# sourceMappingURL=acontplus-ng-auth.mjs.map