@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.
- package/fesm2022/acontplus-ng-auth.mjs +389 -363
- package/fesm2022/acontplus-ng-auth.mjs.map +1 -1
- package/index.d.ts +122 -109
- package/package.json +2 -2
|
@@ -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 {
|
|
11
|
-
import {
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
555
|
-
|
|
556
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
578
|
-
this.
|
|
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
|
-
*
|
|
428
|
+
* Stores the current URL if it's not an excluded route
|
|
429
|
+
* Useful for guards and interceptors
|
|
587
430
|
*/
|
|
588
|
-
|
|
589
|
-
const
|
|
590
|
-
this.
|
|
591
|
-
|
|
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
|
-
*
|
|
438
|
+
* Checks if we're running in a browser environment
|
|
439
|
+
* @returns True if running in browser, false if SSR
|
|
598
440
|
*/
|
|
599
|
-
|
|
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
|
-
|
|
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
|
-
|
|
456
|
+
// Handle cases where sessionStorage might be disabled
|
|
457
|
+
return null;
|
|
613
458
|
}
|
|
614
459
|
}
|
|
615
|
-
|
|
616
|
-
|
|
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:
|
|
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
|
-
}]
|
|
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
|
|
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,
|
|
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,
|
|
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
|