@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.
- package/README.md +288 -0
- package/fesm2022/acontplus-ng-auth.mjs +618 -32
- package/fesm2022/acontplus-ng-auth.mjs.map +1 -1
- package/index.d.ts +197 -4
- package/package.json +12 -6
|
@@ -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.
|
|
12
|
-
|
|
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
|
-
|
|
20
|
-
|
|
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(
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|