@hichchi/ngx-auth 0.0.1-alpha.0

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.
@@ -0,0 +1,1629 @@
1
+ import * as i0 from '@angular/core';
2
+ import { Injectable, Inject, computed, inject, input, output, signal, effect, Component, TemplateRef, ViewContainerRef, Directive, NgModule } from '@angular/core';
3
+ import * as i1$1 from '@angular/forms';
4
+ import { Validators, FormsModule, ReactiveFormsModule } from '@angular/forms';
5
+ import { AuthEndpoint, AuthField, isRoleObject, AuthErrorResponseCode } from '@hichchi/nest-connector/auth';
6
+ import * as i1 from '@angular/common/http';
7
+ import { provideHttpClient } from '@angular/common/http';
8
+ import { take, map, tap, catchError, EMPTY, ReplaySubject, throwError, switchMap, filter } from 'rxjs';
9
+ import { Endpoint, HttpClientErrorStatus } from '@hichchi/nest-connector';
10
+ import { toFirstCase } from '@hichchi/utils';
11
+ import { validatedFormData } from '@hichchi/ngx-utils';
12
+ import { signalStore, withState, withComputed, withMethods, patchState } from '@ngrx/signals';
13
+ import { withStorageSync } from '@angular-architects/ngrx-toolkit';
14
+ import { Router } from '@angular/router';
15
+ import * as i3 from '@hichchi/ngx-ui';
16
+ import { ButtonComponent, HcCardComponent, HcSeparatorComponent } from '@hichchi/ngx-ui';
17
+ import { CommonModule } from '@angular/common';
18
+
19
+ /**
20
+ * Injection token for authentication configuration
21
+ *
22
+ * This constant defines the injection token used by Angular's dependency injection
23
+ * system to provide authentication configuration throughout the ngx-auth module.
24
+ * It allows the AuthConfig interface to be injected into services and components
25
+ * that need access to authentication settings.
26
+ *
27
+ * The token is used internally by the NgxHichchiAuthModule.forRoot() method to
28
+ * register the authentication configuration as a provider, making it available
29
+ * for injection in services like AuthService.
30
+ *
31
+ * This follows Angular's recommended pattern for providing configuration objects
32
+ * to libraries and modules, ensuring type safety and proper dependency injection.
33
+ *
34
+ * @example
35
+ * ```typescript
36
+ * // Used internally by the module to provide configuration
37
+ * @NgModule({
38
+ * providers: [
39
+ * { provide: AUTH_CONFIG, useValue: config },
40
+ * AuthService
41
+ * ]
42
+ * })
43
+ * export class NgxHichchiAuthModule {
44
+ * static forRoot(config: AuthConfig): ModuleWithProviders<NgxHichchiAuthModule> {
45
+ * return {
46
+ * ngModule: NgxHichchiAuthModule,
47
+ * providers: [
48
+ * { provide: AUTH_CONFIG, useValue: config },
49
+ * provideHttpClient(),
50
+ * AuthService
51
+ * ]
52
+ * };
53
+ * }
54
+ * }
55
+ * ```
56
+ *
57
+ * @example
58
+ * ```typescript
59
+ * // Injecting the configuration in a service
60
+ * @Injectable()
61
+ * export class AuthService {
62
+ * constructor(
63
+ * @Inject(AUTH_CONFIG) private readonly config: AuthConfig
64
+ * ) {
65
+ * console.log('API Base URL:', this.config.apiBaseURL);
66
+ * console.log('Auth Field:', this.config.authField);
67
+ * }
68
+ * }
69
+ * ```
70
+ *
71
+ * @example
72
+ * ```typescript
73
+ * // Using in a component (though typically not recommended)
74
+ * @Component({
75
+ * selector: 'app-auth-info',
76
+ * template: `<p>API URL: {{ apiUrl }}</p>`
77
+ * })
78
+ * export class AuthInfoComponent {
79
+ * apiUrl: string;
80
+ *
81
+ * constructor(@Inject(AUTH_CONFIG) private config: AuthConfig) {
82
+ * this.apiUrl = config.apiBaseURL;
83
+ * }
84
+ * }
85
+ * ```
86
+ *
87
+ * @see {@link AuthConfig} Interface that defines the structure of the configuration object
88
+ * @see {@link NgxHichchiAuthModule} Module that uses this token to provide configuration
89
+ * @see {@link AuthService} Service that injects this configuration
90
+ */
91
+ const AUTH_CONFIG = "AUTH_CONFIG";
92
+
93
+ /**
94
+ * Width of the Google OAuth authentication popup window in pixels
95
+ *
96
+ * This constant defines the width of the popup window that opens during Google OAuth
97
+ * authentication. The width is optimized to provide a good user experience while
98
+ * ensuring the Google sign-in interface is fully visible and usable.
99
+ *
100
+ * @example
101
+ * ```typescript
102
+ * // Used internally in AuthService.googleSignIn()
103
+ * const popup = window.open(
104
+ * googleAuthUrl,
105
+ * 'google-login-popup',
106
+ * `width=${GOOGLE_AUTH_POPUP_WIDTH}, height=${GOOGLE_AUTH_POPUP_HEIGHT}`
107
+ * );
108
+ * ```
109
+ *
110
+ * @see {@link AuthService.googleSignIn} Method that uses this constant
111
+ * @see {@link GOOGLE_AUTH_POPUP_HEIGHT} Related constant for popup height
112
+ */
113
+ const GOOGLE_AUTH_POPUP_WIDTH = 500;
114
+ /**
115
+ * Height of the Google OAuth authentication popup window in pixels
116
+ *
117
+ * This constant defines the height of the popup window that opens during Google OAuth
118
+ * authentication. The height is optimized to accommodate the Google sign-in interface
119
+ * and provide sufficient space for user interaction.
120
+ *
121
+ * @example
122
+ * ```typescript
123
+ * // Used internally in AuthService.googleSignIn()
124
+ * const popup = window.open(
125
+ * googleAuthUrl,
126
+ * 'google-login-popup',
127
+ * `width=${GOOGLE_AUTH_POPUP_WIDTH}, height=${GOOGLE_AUTH_POPUP_HEIGHT}`
128
+ * );
129
+ * ```
130
+ *
131
+ * @see {@link AuthService.googleSignIn} Method that uses this constant
132
+ * @see {@link GOOGLE_AUTH_POPUP_WIDTH} Related constant for popup width
133
+ */
134
+ const GOOGLE_AUTH_POPUP_HEIGHT = 600;
135
+ /**
136
+ * Polling interval for checking Google OAuth popup status in milliseconds
137
+ *
138
+ * This constant defines how frequently (in milliseconds) the AuthService checks
139
+ * the status of the Google OAuth popup window. The polling is used to detect
140
+ * when the authentication process is complete or if the user has closed the popup.
141
+ *
142
+ * A shorter interval provides more responsive detection but uses more CPU resources.
143
+ * The current value of 100ms provides a good balance between responsiveness and
144
+ * performance.
145
+ *
146
+ * @example
147
+ * ```typescript
148
+ * // Used internally in AuthService.googleSignIn()
149
+ * const interval = setInterval(() => {
150
+ * // Check popup status
151
+ * if (popup?.closed) {
152
+ * clearInterval(interval);
153
+ * }
154
+ * // Check for authentication completion
155
+ * }, POPUP_POLLING_INTERVAL_MS);
156
+ * ```
157
+ *
158
+ * @see {@link AuthService.googleSignIn} Method that uses this constant for popup polling
159
+ */
160
+ const POPUP_POLLING_INTERVAL_MS = 100;
161
+ /**
162
+ * Key used to store authentication guard options in route data
163
+ *
164
+ * This constant defines the property name used to store authentication guard
165
+ * configuration in Angular route data. It allows routes to specify custom
166
+ * authentication requirements and behaviors.
167
+ *
168
+ * @example
169
+ * ```typescript
170
+ * // In route configuration
171
+ * const routes: Routes = [
172
+ * {
173
+ * path: 'admin',
174
+ * component: AdminComponent,
175
+ * canActivate: [AuthGuard],
176
+ * data: {
177
+ * [AUTH_GUARD_OPTIONS_KEY]: {
178
+ * requiredPermissions: ['admin.read'],
179
+ * redirectTo: '/unauthorized'
180
+ * }
181
+ * }
182
+ * }
183
+ * ];
184
+ * ```
185
+ *
186
+ * @see {@link AuthGuardOption} Interface defining the structure of guard options
187
+ */
188
+ const AUTH_GUARD_OPTIONS_KEY = "authGuardOptions";
189
+
190
+ // noinspection JSUnusedGlobalSymbols
191
+ /**
192
+ * Angular authentication service for client-side authentication operations
193
+ *
194
+ * This service provides methods for handling authentication operations in Angular applications,
195
+ * including user sign-in, sign-up, Google OAuth authentication, token management, and sign-out.
196
+ * It communicates with the backend authentication API and handles the client-side aspects
197
+ * of the authentication flow.
198
+ *
199
+ * The service is configured through the AuthConfig interface and automatically handles
200
+ * token expiration date parsing and HTTP request management. It integrates seamlessly
201
+ * with the @hichchi/nest-auth backend module.
202
+ *
203
+ * Key features:
204
+ * - Local authentication (email/username and password)
205
+ * - Google OAuth authentication with popup flow
206
+ * - Token refresh functionality
207
+ * - User registration
208
+ * - Automatic token expiration handling
209
+ * - RESTful API communication
210
+ *
211
+ * @example
212
+ * ```typescript
213
+ * // In a component
214
+ * export class LoginComponent {
215
+ * constructor(private authService: AuthService) {}
216
+ *
217
+ * async signIn() {
218
+ * try {
219
+ * const response = await this.authService.signIn({
220
+ * email: 'user@example.com',
221
+ * password: 'password123'
222
+ * }).toPromise();
223
+ * console.log('Signed in:', response.user);
224
+ * } catch (error) {
225
+ * console.error('Sign in failed:', error);
226
+ * }
227
+ * }
228
+ * }
229
+ * ```
230
+ *
231
+ * @see {@link AuthConfig} Configuration interface for the authentication service
232
+ * @see {@link NgxHichchiAuthModule} Module that provides this service
233
+ * @see {@link AuthState} State management service for authentication
234
+ * @see {@link AuthResponse} Response interface for authentication operations
235
+ */
236
+ class AuthService {
237
+ http;
238
+ config;
239
+ /**
240
+ * Creates an instance of AuthService
241
+ *
242
+ * @param http - Http client
243
+ * @param config - The authentication configuration injected from AUTH_CONFIG token
244
+ *
245
+ * @see {@link AUTH_CONFIG} Injection token for authentication configuration
246
+ * @see {@link AuthConfig} Interface defining the configuration structure
247
+ */
248
+ constructor(http, config) {
249
+ this.http = http;
250
+ this.config = config;
251
+ }
252
+ /**
253
+ * Authenticates a user with email/username and password
254
+ *
255
+ * This method sends a sign-in request to the backend authentication API with the provided
256
+ * credentials. It automatically converts the token expiration timestamps from the response
257
+ * into JavaScript Date objects for easier handling in the client application.
258
+ *
259
+ * @param dto - The sign-in data containing user credentials
260
+ * @returns Observable that emits the authentication response with user data and tokens
261
+ *
262
+ * @example
263
+ * ```typescript
264
+ * // Sign in with email and password
265
+ * this.authService.signIn({
266
+ * email: 'user@example.com',
267
+ * password: 'password123'
268
+ * }).subscribe({
269
+ * next: (response) => {
270
+ * console.log('User signed in:', response.user);
271
+ * console.log('Access token expires:', response.accessTokenExpiresOn);
272
+ * },
273
+ * error: (error) => {
274
+ * console.error('Sign in failed:', error);
275
+ * }
276
+ * });
277
+ * ```
278
+ *
279
+ * @see {@link SignInBody} Interface for sign-in request data
280
+ * @see {@link AuthResponse} Interface for authentication response
281
+ * @see {@link AuthEndpoint.SIGN_IN} Backend endpoint for user authentication
282
+ */
283
+ signIn(dto) {
284
+ return this.http.post(`${Endpoint.AUTH}/${AuthEndpoint.SIGN_IN}`, dto).pipe(take(1), map(res => ({
285
+ ...res,
286
+ accessTokenExpiresOn: new Date(res.accessTokenExpiresOn),
287
+ refreshTokenExpiresOn: new Date(res.refreshTokenExpiresOn),
288
+ })));
289
+ }
290
+ /**
291
+ * Initiates Google OAuth authentication using a popup window
292
+ *
293
+ * This method opens a popup window that navigates to the Google OAuth authentication
294
+ * endpoint. It handles the OAuth flow by monitoring the popup window and extracting
295
+ * the access token from the callback URL when authentication is successful.
296
+ *
297
+ * The popup is automatically positioned in the center of the screen and has predefined
298
+ * dimensions for optimal user experience. The method polls the popup window to detect
299
+ * when authentication is complete or if the user closes the popup.
300
+ *
301
+ * @returns Promise that resolves with the access token when authentication succeeds
302
+ *
303
+ * @throws {Error} If authentication fails or the popup is blocked
304
+ *
305
+ * @example
306
+ * ```typescript
307
+ * // Initiate Google sign-in
308
+ * try {
309
+ * const accessToken = await this.authService.googleSignIn();
310
+ * console.log('Google authentication successful:', accessToken);
311
+ *
312
+ * // Use the token to get full auth response
313
+ * const authResponse = await this.authService.getAuthResponse(accessToken).toPromise();
314
+ * console.log('User data:', authResponse.user);
315
+ * } catch (error) {
316
+ * console.error('Google authentication failed:', error);
317
+ * }
318
+ * ```
319
+ *
320
+ * @example
321
+ * ```typescript
322
+ * // In a component with error handling
323
+ * async signInWithGoogle() {
324
+ * try {
325
+ * const token = await this.authService.googleSignIn();
326
+ * // Handle successful authentication
327
+ * this.router.navigate(['/dashboard']);
328
+ * } catch (error) {
329
+ * if (error.message.includes('popup')) {
330
+ * this.showError('Please allow popups for Google sign-in');
331
+ * } else {
332
+ * this.showError('Google sign-in failed. Please try again.');
333
+ * }
334
+ * }
335
+ * }
336
+ * ```
337
+ *
338
+ * @see {@link getAuthResponse} Method to get full authentication response using the access token
339
+ * @see {@link AuthEndpoint.GOOGLE_SIGN_IN} Backend endpoint for Google OAuth initiation
340
+ * @see {@link GOOGLE_AUTH_POPUP_WIDTH} Constant defining popup window width
341
+ * @see {@link GOOGLE_AUTH_POPUP_HEIGHT} Constant defining popup window height
342
+ * @see {@link POPUP_POLLING_INTERVAL_MS} Constant defining popup polling interval
343
+ */
344
+ googleSignIn() {
345
+ return new Promise((resolve, reject) => {
346
+ // eslint-disable-next-line @typescript-eslint/no-magic-numbers
347
+ const left = (window.screen.width - GOOGLE_AUTH_POPUP_WIDTH) / 2;
348
+ // eslint-disable-next-line @typescript-eslint/no-magic-numbers
349
+ const top = (window.screen.height - GOOGLE_AUTH_POPUP_HEIGHT) / 2;
350
+ const popup = window.open(`${this.config.apiBaseURL}/${Endpoint.AUTH}/${AuthEndpoint.GOOGLE_SIGN_IN}?redirectUrl=${window.location.origin}`, "google-login-popup",
351
+ // eslint-disable-next-line prefer-template
352
+ "resizable=no, location=no, toolbar=false, width=" +
353
+ GOOGLE_AUTH_POPUP_WIDTH +
354
+ ", height=" +
355
+ GOOGLE_AUTH_POPUP_HEIGHT +
356
+ ", top=" +
357
+ top +
358
+ ", left=" +
359
+ left);
360
+ const interval = setInterval(() => {
361
+ if (popup?.closed) {
362
+ clearInterval(interval);
363
+ }
364
+ try {
365
+ if (popup?.location.href !== "about:blank" && popup?.location?.search?.includes("?token=e")) {
366
+ const token = popup.location.search.split("=")[1];
367
+ clearInterval(interval);
368
+ popup.close();
369
+ resolve(token);
370
+ }
371
+ }
372
+ catch (error) {
373
+ if (!String(error).includes("SecurityError")) {
374
+ clearInterval(interval);
375
+ reject(error);
376
+ }
377
+ }
378
+ }, POPUP_POLLING_INTERVAL_MS);
379
+ });
380
+ }
381
+ /**
382
+ * Retrieves the complete authentication response using an access token
383
+ *
384
+ * This method exchanges an access token for a complete authentication response
385
+ * containing user information and token details. It's typically used after
386
+ * Google OAuth authentication to get the full user profile and session data.
387
+ *
388
+ * The method automatically converts token expiration timestamps to JavaScript
389
+ * Date objects for easier handling in the client application.
390
+ *
391
+ * @param accessToken - The access token to exchange for authentication response
392
+ * @returns Observable that emits the complete authentication response
393
+ *
394
+ * @example
395
+ * ```typescript
396
+ * // Get auth response after Google sign-in
397
+ * const accessToken = await this.authService.googleSignIn();
398
+ * this.authService.getAuthResponse(accessToken).subscribe({
399
+ * next: (response) => {
400
+ * console.log('User:', response.user);
401
+ * console.log('Tokens:', {
402
+ * access: response.accessToken,
403
+ * refresh: response.refreshToken
404
+ * });
405
+ * },
406
+ * error: (error) => {
407
+ * console.error('Failed to get auth response:', error);
408
+ * }
409
+ * });
410
+ * ```
411
+ *
412
+ * @see {@link AccessToken} Type representing access tokens
413
+ * @see {@link AuthResponse} Interface for complete authentication response
414
+ * @see {@link AuthEndpoint.GET_AUTH_RESPONSE} Backend endpoint for token exchange
415
+ * @see {@link googleSignIn} Method that provides access tokens for this operation
416
+ */
417
+ getAuthResponse(accessToken) {
418
+ return this.http
419
+ .post(`${Endpoint.AUTH}/${AuthEndpoint.GET_AUTH_RESPONSE}`, {
420
+ accessToken,
421
+ })
422
+ .pipe(take(1), map(res => ({
423
+ ...res,
424
+ accessTokenExpiresOn: new Date(res.accessTokenExpiresOn),
425
+ refreshTokenExpiresOn: new Date(res.refreshTokenExpiresOn),
426
+ })));
427
+ }
428
+ /**
429
+ * Registers a new user account
430
+ *
431
+ * This method sends a registration request to the backend API with the provided
432
+ * user information. It creates a new user account and returns the user data
433
+ * upon successful registration.
434
+ *
435
+ * Note that this method only creates the user account and does not automatically
436
+ * sign the user in. After successful registration, you may need to call signIn
437
+ * or handle email verification depending on your application's configuration.
438
+ *
439
+ * @param dto - The sign-up data containing user registration information
440
+ * @returns Observable that emits the newly created user data
441
+ *
442
+ * @example
443
+ * ```typescript
444
+ * // Register a new user
445
+ * this.authService.signUp({
446
+ * email: 'newuser@example.com',
447
+ * password: 'securePassword123',
448
+ * firstName: 'John',
449
+ * lastName: 'Doe'
450
+ * }).subscribe({
451
+ * next: (user) => {
452
+ * console.log('User registered successfully:', user);
453
+ * // Optionally redirect to sign-in or email verification page
454
+ * this.router.navigate(['/verify-email']);
455
+ * },
456
+ * error: (error) => {
457
+ * console.error('Registration failed:', error);
458
+ * // Handle registration errors (email already exists, etc.)
459
+ * }
460
+ * });
461
+ * ```
462
+ *
463
+ * @see {@link SignUpBody} Interface for user registration data
464
+ * @see {@link User} Interface for user data returned after registration
465
+ * @see {@link AuthEndpoint.SIGN_UP} Backend endpoint for user registration
466
+ * @see {@link signIn} Method to authenticate user after registration
467
+ */
468
+ signUp(dto) {
469
+ return this.http.post(`${Endpoint.AUTH}/${AuthEndpoint.SIGN_UP}`, dto).pipe(take(1));
470
+ }
471
+ /**
472
+ * Refreshes an expired access token using a refresh token
473
+ *
474
+ * This method exchanges a valid refresh token for a new set of access and refresh tokens.
475
+ * It's typically used when the current access token has expired but the refresh token
476
+ * is still valid, allowing the user to maintain their session without re-authenticating.
477
+ *
478
+ * The refresh token mechanism provides a secure way to maintain long-lived sessions
479
+ * while keeping access tokens short-lived for better security.
480
+ *
481
+ * @param refreshToken - The refresh token to exchange for new tokens
482
+ * @returns Observable that emits the new token response
483
+ *
484
+ * @example
485
+ * ```typescript
486
+ * // Refresh tokens when access token expires
487
+ * const storedRefreshToken = localStorage.getItem('refreshToken');
488
+ * if (storedRefreshToken) {
489
+ * this.authService.refreshToken(storedRefreshToken).subscribe({
490
+ * next: (tokenResponse) => {
491
+ * console.log('Tokens refreshed successfully');
492
+ * // Store new tokens
493
+ * localStorage.setItem('accessToken', tokenResponse.accessToken);
494
+ * localStorage.setItem('refreshToken', tokenResponse.refreshToken);
495
+ * },
496
+ * error: (error) => {
497
+ * console.error('Token refresh failed:', error);
498
+ * // Redirect to login page
499
+ * this.router.navigate(['/login']);
500
+ * }
501
+ * });
502
+ * }
503
+ * ```
504
+ *
505
+ * @see {@link RefreshToken} Type representing refresh tokens
506
+ * @see {@link TokenResponse} Interface for token refresh response
507
+ * @see {@link AuthEndpoint.REFRESH_TOKEN} Backend endpoint for token refresh
508
+ * @see {@link signIn} Method to get initial tokens through authentication
509
+ */
510
+ refreshToken(refreshToken) {
511
+ return this.http
512
+ .post(`${Endpoint.AUTH}/${AuthEndpoint.REFRESH_TOKEN}`, {
513
+ refreshToken,
514
+ })
515
+ .pipe(take(1));
516
+ }
517
+ /**
518
+ * Signs out the current user and invalidates their session
519
+ *
520
+ * This method sends a sign-out request to the backend API to invalidate the user's
521
+ * current session and tokens. It effectively logs the user out of the application
522
+ * and clears their authentication state on the server.
523
+ *
524
+ * After calling this method, you should also clear any client-side authentication
525
+ * data such as tokens stored in localStorage, sessionStorage, or application state.
526
+ *
527
+ * @returns Observable that emits a success response when sign-out is complete
528
+ *
529
+ * @example
530
+ * ```typescript
531
+ * // Sign out the current user
532
+ * this.authService.signOut().subscribe({
533
+ * next: (response) => {
534
+ * console.log('User signed out successfully');
535
+ * // Clear client-side authentication data
536
+ * localStorage.removeItem('accessToken');
537
+ * localStorage.removeItem('refreshToken');
538
+ * // Redirect to login page
539
+ * this.router.navigate(['/login']);
540
+ * },
541
+ * error: (error) => {
542
+ * console.error('Sign out failed:', error);
543
+ * // Even if server sign-out fails, clear local data
544
+ * localStorage.clear();
545
+ * this.router.navigate(['/login']);
546
+ * }
547
+ * });
548
+ * ```
549
+ *
550
+ * @example
551
+ * ```typescript
552
+ * // Sign out with state management
553
+ * async signOut() {
554
+ * try {
555
+ * await this.authService.signOut().toPromise();
556
+ * // Clear authentication state
557
+ * this.authState.clearUser();
558
+ * this.notificationService.showSuccess('Signed out successfully');
559
+ * } catch (error) {
560
+ * console.error('Sign out error:', error);
561
+ * } finally {
562
+ * // Always redirect to login
563
+ * this.router.navigate(['/login']);
564
+ * }
565
+ * }
566
+ * ```
567
+ *
568
+ * @see {@link SuccessResponse} Interface for success response
569
+ * @see {@link AuthEndpoint.SIGN_OUT} Backend endpoint for user sign-out
570
+ * @see {@link signIn} Method to authenticate user after sign-out
571
+ */
572
+ signOut() {
573
+ // this.app.startSpinner();
574
+ return this.http.post(`${Endpoint.AUTH}/${AuthEndpoint.SIGN_OUT}`, {}).pipe(take(1));
575
+ }
576
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: AuthService, deps: [{ token: i1.HttpClient }, { token: AUTH_CONFIG }], target: i0.ɵɵFactoryTarget.Injectable });
577
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: AuthService, providedIn: "root" });
578
+ }
579
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: AuthService, decorators: [{
580
+ type: Injectable,
581
+ args: [{
582
+ providedIn: "root",
583
+ }]
584
+ }], ctorParameters: () => [{ type: i1.HttpClient }, { type: undefined, decorators: [{
585
+ type: Inject,
586
+ args: [AUTH_CONFIG]
587
+ }] }] });
588
+
589
+ /* eslint-disable */
590
+ // noinspection JSUnusedGlobalSymbols
591
+ const initialState = {
592
+ signedIn: false,
593
+ sessionId: null,
594
+ user: null,
595
+ accessToken: null,
596
+ refreshToken: null,
597
+ accessTokenExpiresOn: null,
598
+ refreshTokenExpiresOn: null,
599
+ };
600
+ /**
601
+ * Authentication state management store using NgRx Signals
602
+ *
603
+ * This signal store provides centralized state management for authentication in Angular applications.
604
+ * It manages user authentication state, tokens, and provides methods for authentication operations.
605
+ * The store automatically persists state to browser storage and provides reactive computed values.
606
+ *
607
+ * Key features:
608
+ * - Automatic state persistence with browser storage sync
609
+ * - Reactive computed properties for common authentication checks
610
+ * - Built-in methods for sign-in, sign-out, and token management
611
+ * - Integration with Angular Router for navigation after authentication
612
+ * - Type-safe state management with TypeScript
613
+ *
614
+ * The store is provided at the root level and can be injected into any component or service.
615
+ * It uses NgRx Signals for reactive state management and provides a modern alternative
616
+ * to traditional NgRx store patterns.
617
+ *
618
+ * @example
619
+ * ```typescript
620
+ * // In a component
621
+ * export class AppComponent {
622
+ * private authState = inject(AuthState)
623
+ *
624
+ * // Access reactive state
625
+ * isSignedIn = this.authState.signedIn;
626
+ * currentUser = this.authState.user;
627
+ * hasAccessToken = this.authState.hasAccessToken;
628
+ *
629
+ * // Sign in user
630
+ * async signIn() {
631
+ * this.authState.signIn({
632
+ * email: 'user@example.com',
633
+ * password: 'password123'
634
+ * }, '/dashboard').subscribe({
635
+ * next: (response) => console.log('Signed in:', response.user),
636
+ * error: (error) => console.error('Sign in failed:', error)
637
+ * });
638
+ * }
639
+ *
640
+ * // Sign out user
641
+ * signOut() {
642
+ * this.authState.signOut('/login').subscribe();
643
+ * }
644
+ * }
645
+ * ```
646
+ *
647
+ * @example
648
+ * ```typescript
649
+ * // In a guard
650
+ * export class AuthGuard {
651
+ * private authState = inject(AuthState)
652
+ *
653
+ * canActivate(): boolean {
654
+ * return this.authState.signedIn() && this.authState.hasAccessToken();
655
+ * }
656
+ * }
657
+ * ```
658
+ *
659
+ * @example
660
+ * ```typescript
661
+ * // Using computed properties
662
+ * export class HeaderComponent {
663
+ * private authState = inject(AuthState)
664
+ *
665
+ * // Reactive computed values
666
+ * userRole = this.authState.role;
667
+ * isEmailVerified = this.authState.emailVerified;
668
+ * hasValidToken = this.authState.hasAccessToken;
669
+ * }
670
+ * ```
671
+ *
672
+ * @see {@link AuthStateModel} Interface defining the state structure
673
+ * @see {@link AuthService} Service used for authentication operations
674
+ * @see {@link signalStore} NgRx Signals store factory function
675
+ * @see {@link withStorageSync} Storage synchronization feature
676
+ */
677
+ const AuthState = signalStore({ providedIn: "root" }, withState(initialState), withStorageSync({ key: "auth" }), withComputed(({ accessToken, user }) => ({
678
+ hasAccessToken: computed(() => Boolean(accessToken())),
679
+ role: computed(() => user()?.role),
680
+ emailVerified: computed(() => Boolean(user()?.emailVerified)),
681
+ })), withMethods((store, router = inject(Router), authService = inject(AuthService)) => ({
682
+ reset() {
683
+ patchState(store, initialState);
684
+ },
685
+ setTokens(tokenResponse) {
686
+ const { accessToken, refreshToken, accessTokenExpiresOn, refreshTokenExpiresOn } = tokenResponse;
687
+ patchState(store, state => ({
688
+ ...state,
689
+ accessToken,
690
+ refreshToken,
691
+ accessTokenExpiresOn,
692
+ refreshTokenExpiresOn,
693
+ }));
694
+ },
695
+ signIn(signInBody, redirect) {
696
+ return authService.signIn(signInBody).pipe(tap((res) => {
697
+ patchState(store, { ...res, signedIn: true });
698
+ if (redirect) {
699
+ void router.navigateByUrl(typeof redirect === "string" ? redirect : redirect(res));
700
+ }
701
+ }));
702
+ },
703
+ authenticateWithToken: (accessToken, redirect) => {
704
+ return authService.getAuthResponse(accessToken).pipe(tap((res) => {
705
+ patchState(store, { ...res, signedIn: Boolean(res.user.role) });
706
+ if (redirect) {
707
+ void router.navigateByUrl(typeof redirect === "string" ? redirect : redirect(res));
708
+ }
709
+ }), catchError(() => EMPTY));
710
+ },
711
+ signOut: (redirect) => {
712
+ return authService.signOut().pipe(tap({
713
+ next: () => {
714
+ patchState(store, initialState);
715
+ if (redirect) {
716
+ void router.navigateByUrl(redirect);
717
+ }
718
+ },
719
+ }), catchError(() => EMPTY));
720
+ },
721
+ })));
722
+
723
+ /* eslint-disable @angular-eslint/no-output-on-prefix */
724
+ class AuthFormComponent {
725
+ config;
726
+ fb;
727
+ authService;
728
+ local = input(true);
729
+ google = input(true);
730
+ facebook = input(true);
731
+ onError = output();
732
+ onSignIn = output();
733
+ onSignUp = output();
734
+ isLoading = signal(false);
735
+ isSignUp = signal(false);
736
+ isError = signal(false);
737
+ authField = signal(AuthField.EMAIL);
738
+ authFieldLabel = signal(toFirstCase(AuthField.EMAIL));
739
+ error = signal(null);
740
+ authState = inject(AuthState);
741
+ authForm;
742
+ constructor(config, fb, authService) {
743
+ this.config = config;
744
+ this.fb = fb;
745
+ this.authService = authService;
746
+ this.authField.set(config.authField === AuthField.USERNAME ? AuthField.USERNAME : AuthField.EMAIL);
747
+ this.authFieldLabel.set(toFirstCase(this.authField()));
748
+ this.authForm = this.fb.group({
749
+ firstName: ["", Validators.required],
750
+ lastName: ["", Validators.required],
751
+ authFieldValue: ["", [Validators.required]],
752
+ password: ["", Validators.required],
753
+ });
754
+ effect(() => {
755
+ if (this.isSignUp()) {
756
+ this.authForm.controls?.firstName?.enable();
757
+ this.authForm.controls?.lastName?.enable();
758
+ }
759
+ else {
760
+ this.authForm.controls?.firstName?.disable();
761
+ this.authForm.controls?.lastName?.disable();
762
+ }
763
+ });
764
+ }
765
+ async handleGoogleSignIn() {
766
+ const accessToken = await this.authService.googleSignIn();
767
+ this.authState.authenticateWithToken(accessToken).subscribe({
768
+ next: authResponse => {
769
+ this.isLoading.set(false);
770
+ this.onSignIn.emit(authResponse);
771
+ },
772
+ error: this.handleError.bind(this),
773
+ });
774
+ }
775
+ handleLocalAuth(signInBody) {
776
+ this.isLoading.set(true);
777
+ this.isError.set(false);
778
+ this.authState.signIn(signInBody).subscribe({
779
+ next: authResponse => {
780
+ this.isLoading.set(false);
781
+ this.onSignIn.emit(authResponse);
782
+ },
783
+ error: this.handleError.bind(this),
784
+ });
785
+ }
786
+ handleSignUp(signUpBody) {
787
+ this.isLoading.set(true);
788
+ this.isError.set(false);
789
+ this.authService.signUp(signUpBody).subscribe({
790
+ next: user => {
791
+ this.isLoading.set(false);
792
+ this.onSignUp.emit(user);
793
+ },
794
+ error: this.handleError.bind(this),
795
+ });
796
+ }
797
+ handleSubmit(e) {
798
+ e.preventDefault();
799
+ if (this.isSignUp()) {
800
+ const formData = validatedFormData(this.authForm);
801
+ if (formData) {
802
+ this.handleSignUp({
803
+ firstName: formData.firstName,
804
+ lastName: formData.lastName,
805
+ [this.authField()]: formData.authFieldValue,
806
+ password: formData.password,
807
+ });
808
+ }
809
+ }
810
+ else {
811
+ const formData = validatedFormData(this.authForm);
812
+ if (formData) {
813
+ this.handleLocalAuth({
814
+ [this.authField()]: formData.authFieldValue,
815
+ password: formData.password,
816
+ });
817
+ }
818
+ }
819
+ }
820
+ handleError(error) {
821
+ this.isLoading.set(false);
822
+ this.isError.set(true);
823
+ this.error.set(error);
824
+ this.onError.emit(error);
825
+ }
826
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: AuthFormComponent, deps: [{ token: AUTH_CONFIG }, { token: i1$1.FormBuilder }, { token: AuthService }], target: i0.ɵɵFactoryTarget.Component });
827
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.0.7", type: AuthFormComponent, isStandalone: false, selector: "hc-auth-card", inputs: { local: { classPropertyName: "local", publicName: "local", isSignal: true, isRequired: false, transformFunction: null }, google: { classPropertyName: "google", publicName: "google", isSignal: true, isRequired: false, transformFunction: null }, facebook: { classPropertyName: "facebook", publicName: "facebook", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onError: "onError", onSignIn: "onSignIn", onSignUp: "onSignUp" }, ngImport: i0, template: "<!--<div class=\"auth-container d-flex justify-content-center align-items-center\">-->\r\n<!-- -->\r\n<!--</div>-->\r\n<hc-card>\r\n <!-- @if (isLoading()) {-->\r\n <!-- <div class=\"loading-overlay w-100 h-100\"></div>-->\r\n <!-- }-->\r\n <form\r\n class=\"d-inline-flex flex-column align-items-center w-100\"\r\n [formGroup]=\"authForm\"\r\n (ngSubmit)=\"handleSubmit($event)\"\r\n >\r\n @if (isSignUp()) {\r\n <div class=\"form-group w-100\">\r\n <label for=\"firstName\" class=\"form-label\">First Name</label>\r\n <input\r\n id=\"firstName\"\r\n type=\"text\"\r\n class=\"form-control w-100\"\r\n formControlName=\"firstName\"\r\n placeholder=\"Enter your first name\"\r\n />\r\n </div>\r\n\r\n <div class=\"form-group w-100\">\r\n <label for=\"lastName\" class=\"form-label\">Last Name</label>\r\n <input\r\n id=\"lastName\"\r\n type=\"text\"\r\n class=\"form-control w-100\"\r\n formControlName=\"lastName\"\r\n placeholder=\"Enter your last name\"\r\n />\r\n </div>\r\n }\r\n\r\n <div class=\"form-group w-100\">\r\n <label for=\"authField\" class=\"form-label\">{{ authFieldLabel() }}</label>\r\n <input\r\n id=\"authField\"\r\n type=\"text\"\r\n class=\"form-control w-100\"\r\n formControlName=\"authFieldValue\"\r\n [placeholder]=\"'Enter your ' + authFieldLabel().toLowerCase()\"\r\n />\r\n </div>\r\n\r\n <div class=\"form-group w-100\">\r\n <label for=\"password\" class=\"form-label\">Password</label>\r\n <input\r\n id=\"password\"\r\n type=\"password\"\r\n class=\"form-control w-100\"\r\n formControlName=\"password\"\r\n placeholder=\"Enter your password\"\r\n />\r\n </div>\r\n\r\n <button type=\"submit\" class=\"btn btn-primary mb-3 w-100\" [disabled]=\"isLoading()\">\r\n {{ isSignUp() ? (isLoading() ? \"Signing Up...\" : \"Sign Up\") : isLoading() ? \"Signing In...\" : \"Sign In\" }}\r\n </button>\r\n\r\n <button type=\"button\" class=\"btn btn-link p-0 mb-3\" (click)=\"isSignUp.set(!isSignUp())\">\r\n {{ isSignUp() ? \"Already have an account? Sign In\" : \"Don't have an account? Sign Up\" }}\r\n </button>\r\n\r\n @if (!isSignUp()) {\r\n @if (local() && (google() || facebook())) {\r\n <hc-separator label=\"OR\"></hc-separator>\r\n }\r\n\r\n @if (google()) {\r\n <button type=\"button\" class=\"btn google-btn btn-light mb-3 w-100\" (click)=\"handleGoogleSignIn()\">\r\n <div class=\"icon\"></div>\r\n Sign in with Google\r\n </button>\r\n }\r\n\r\n @if (facebook()) {\r\n <button type=\"button\" class=\"btn facebook-btn btn-light mb-3 w-100\">\r\n <div class=\"icon\"></div>\r\n Sign in with Facebook\r\n </button>\r\n }\r\n\r\n @if (isError()) {\r\n <div class=\"error-message w-100\">\r\n {{ error()?.error?.message || \"Something went wrong!\" }}\r\n </div>\r\n }\r\n }\r\n </form>\r\n</hc-card>\r\n", styles: [".auth-form{background:#fffffff2;-webkit-backdrop-filter:blur(20px);backdrop-filter:blur(20px);border-radius:16px;margin:8px;padding:2rem;position:relative;z-index:1}.loading-overlay{background:#ffffffe6;-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px);border-radius:16px;display:flex;align-items:center;justify-content:center;font-weight:600;color:var(--secondary-color);font-size:1.1rem;position:absolute;top:0;left:0}.loading-overlay:after{content:\"\";width:50px;height:50px;border:5px solid var(--secondary-color);border-top:5px solid transparent;border-radius:50%;animation:spin 1s linear infinite;margin-left:10px}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.form-group{position:relative;margin-bottom:1.5rem}.form-label{font-weight:600;color:var(--text-color);margin-bottom:.5rem;font-size:.9rem;text-transform:uppercase;letter-spacing:.5px}.form-control{border:2px solid var(--border-color);border-radius:12px;padding:.75rem 1rem;font-size:1rem;transition:all .3s ease;background:#fffc;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px)}.form-control:focus{border-color:var(--secondary-color);box-shadow:0 0 0 3px #667eea1a;background:#fffffff2}.form-control::placeholder{color:var(--text-light)}.btn{border-radius:12px;padding:.75rem 1.5rem;font-weight:600;font-size:1rem;transition:all .3s ease;border:none;position:relative;overflow:hidden}.btn:before{content:\"\";position:absolute;top:0;left:-100%;width:100%;height:100%;background:linear-gradient(90deg,transparent,rgba(255,255,255,.2),transparent);transition:left .5s}.btn:hover:before{left:100%}.btn-primary{background:var(--secondary-gradient);color:#fff;box-shadow:var(--shadow-md)}.btn-primary:hover{box-shadow:var(--shadow-lg)}.btn-link{color:var(--text-light);text-decoration:none;font-size:.9rem;transition:all .3s ease}.btn-link:hover{color:var(--secondary-color);text-decoration:underline}.google-btn,.facebook-btn{display:flex;align-items:center;justify-content:center;gap:12px;color:var(--text-color);background:#ffffffe6;border:2px solid var(--border-color);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);font-weight:600;transition:all .3s ease}.google-btn:hover,.facebook-btn:hover{background:#fff;border-color:var(--secondary-color);box-shadow:var(--shadow-md)}.google-btn .icon,.facebook-btn .icon{height:24px;width:24px;background-repeat:no-repeat;background-size:contain;background-position:center}.google-btn .icon{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTgiIGhlaWdodD0iMTgiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj48cGF0aCBkPSJNMTcuNiA5LjJsLS4xLTEuOEg5djMuNGg0LjhDMTMuNiAxMiAxMyAxMyAxMiAxMy42djIuMmgzYTguOCA4LjggMCAwIDAgMi42LTYuNnoiIGZpbGw9IiM0Mjg1RjQiIGZpbGwtcnVsZT0ibm9uemVybyIvPjxwYXRoIGQ9Ik05IDE4YzIuNCAwIDQuNS0uOCA2LTIuMmwtMy0yLjJhNS40IDUuNCAwIDAgMS04LTIuOUgxVjEzYTkgOSAwIDAgMCA4IDV6IiBmaWxsPSIjMzRBODUzIiBmaWxsLXJ1bGU9Im5vbnplcm8iLz48cGF0aCBkPSJNNCAxMC43YTUuNCA1LjQgMCAwIDEgMC0zLjRWNUgxYTkgOSAwIDAgMCAwIDhsMy0yLjN6IiBmaWxsPSIjRkJCQzA1IiBmaWxsLXJ1bGU9Im5vbnplcm8iLz48cGF0aCBkPSJNOSAzLjZjMS4zIDAgMi41LjQgMy40IDEuM0wxNSAyLjNBOSA5IDAgMCAwIDEgNWwzIDIuNGE1LjQgNS40IDAgMCAxIDUtMy43eiIgZmlsbD0iI0VBNDMzNSIgZmlsbC1ydWxlPSJub256ZXJvIi8+PHBhdGggZD0iTTAgMGgxOHYxOEgweiIvPjwvZz48L3N2Zz4=)}.facebook-btn .icon{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IArs4c6QAAAmJJREFUaEPtmj9oFEEUxr9v7nIpzkIRLQQVFLUSBBs7IbfGwkrhtNHbvYiiRTqbNKIRbGwD/iFkd88uISmsEtwErRQsLEVUiGIbsDAK8W6e3IESkuzt3eVucwMz7b5hvt/73rwZluHwtcr+Wrb6BMJhgnkYMASyKlotDGZqt1goT81R1EUDdG+SqDXnWPD8n6ZkfiNB3Qk6XiAmZv+fZguw0+5ZBzp3QITAOw1EiupDrYYVZvFHaZ0DkNfCHCn7ILwAwolbZ0ccEMgCtRqLKu77pAQ45eAOBI/6CID3oqA0DrCl7tdfAIKJKPRGk7K+/nsfAci3Pav5YzMzl9fMBCBHI9+daEd8PbZvHKhmswdfTV79biSACD4tht7xOPHF4nTux65fD7RIEcIDJAZbBU2ljYrm0mLFLcSJKpSD+xTcbVX0+rhUADT19JI/ciUWwAveEDjTtwAAn0eBW4oT6LjBFxBHzAUohctQctgCdJKB1uYklJB1oLU0JkYJMZ7ReLExUFFW5oPycuwm9vyTSli/Rm8aNWKSwKmUbqNyPQrKU4mkbQQ4nv8V4CEjAU7ffDqwey33m2DGSIAhNziqiM/NDOvySdzdEiqMhA61vDQWwPH8GwCfGQtwzg0eCjGWGoCGvFbkxy0WfBv5nh8npCFUYe8WPfQslJxIDSB+IXsSx+amy10otls3v07bu1AbR3tnoXYP2D3QWeX8n2VLyJaQLaGm/4XsQbbNAkmebrtQfBdK56lBbxxoPDUYKoWzSsml5DLYTkRvAADMsv7cpkp5TKXP9+7RR3cBGpkH5weob/8FwaStQs990hUAAAAASUVORK5CYII=)}.separator{position:relative;width:calc(100% - 40px);height:1px;background:linear-gradient(90deg,transparent,var(--border-color),transparent);margin:2rem auto}.separator:after{content:\"OR\";position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);background:#ffffffe6;padding:0 1rem;font-size:.8rem;font-weight:600;color:var(--text-light);letter-spacing:1px}.error-message{color:var(--danger-color);margin-top:1rem;font-weight:600;text-align:center}@keyframes slideIn{0%{opacity:0}to{opacity:1}}@media (max-width: 480px){.auth-container{margin:1rem;border-radius:16px}.auth-form{padding:1.5rem;margin:4px}.btn{padding:.875rem 1.25rem;font-size:.95rem}}\n"], dependencies: [{ kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.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: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: i3.HcCardComponent, selector: "hc-card" }, { kind: "component", type: i3.HcSeparatorComponent, selector: "hc-separator", inputs: ["label"] }] });
828
+ }
829
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: AuthFormComponent, decorators: [{
830
+ type: Component,
831
+ args: [{ selector: "hc-auth-card", standalone: false, template: "<!--<div class=\"auth-container d-flex justify-content-center align-items-center\">-->\r\n<!-- -->\r\n<!--</div>-->\r\n<hc-card>\r\n <!-- @if (isLoading()) {-->\r\n <!-- <div class=\"loading-overlay w-100 h-100\"></div>-->\r\n <!-- }-->\r\n <form\r\n class=\"d-inline-flex flex-column align-items-center w-100\"\r\n [formGroup]=\"authForm\"\r\n (ngSubmit)=\"handleSubmit($event)\"\r\n >\r\n @if (isSignUp()) {\r\n <div class=\"form-group w-100\">\r\n <label for=\"firstName\" class=\"form-label\">First Name</label>\r\n <input\r\n id=\"firstName\"\r\n type=\"text\"\r\n class=\"form-control w-100\"\r\n formControlName=\"firstName\"\r\n placeholder=\"Enter your first name\"\r\n />\r\n </div>\r\n\r\n <div class=\"form-group w-100\">\r\n <label for=\"lastName\" class=\"form-label\">Last Name</label>\r\n <input\r\n id=\"lastName\"\r\n type=\"text\"\r\n class=\"form-control w-100\"\r\n formControlName=\"lastName\"\r\n placeholder=\"Enter your last name\"\r\n />\r\n </div>\r\n }\r\n\r\n <div class=\"form-group w-100\">\r\n <label for=\"authField\" class=\"form-label\">{{ authFieldLabel() }}</label>\r\n <input\r\n id=\"authField\"\r\n type=\"text\"\r\n class=\"form-control w-100\"\r\n formControlName=\"authFieldValue\"\r\n [placeholder]=\"'Enter your ' + authFieldLabel().toLowerCase()\"\r\n />\r\n </div>\r\n\r\n <div class=\"form-group w-100\">\r\n <label for=\"password\" class=\"form-label\">Password</label>\r\n <input\r\n id=\"password\"\r\n type=\"password\"\r\n class=\"form-control w-100\"\r\n formControlName=\"password\"\r\n placeholder=\"Enter your password\"\r\n />\r\n </div>\r\n\r\n <button type=\"submit\" class=\"btn btn-primary mb-3 w-100\" [disabled]=\"isLoading()\">\r\n {{ isSignUp() ? (isLoading() ? \"Signing Up...\" : \"Sign Up\") : isLoading() ? \"Signing In...\" : \"Sign In\" }}\r\n </button>\r\n\r\n <button type=\"button\" class=\"btn btn-link p-0 mb-3\" (click)=\"isSignUp.set(!isSignUp())\">\r\n {{ isSignUp() ? \"Already have an account? Sign In\" : \"Don't have an account? Sign Up\" }}\r\n </button>\r\n\r\n @if (!isSignUp()) {\r\n @if (local() && (google() || facebook())) {\r\n <hc-separator label=\"OR\"></hc-separator>\r\n }\r\n\r\n @if (google()) {\r\n <button type=\"button\" class=\"btn google-btn btn-light mb-3 w-100\" (click)=\"handleGoogleSignIn()\">\r\n <div class=\"icon\"></div>\r\n Sign in with Google\r\n </button>\r\n }\r\n\r\n @if (facebook()) {\r\n <button type=\"button\" class=\"btn facebook-btn btn-light mb-3 w-100\">\r\n <div class=\"icon\"></div>\r\n Sign in with Facebook\r\n </button>\r\n }\r\n\r\n @if (isError()) {\r\n <div class=\"error-message w-100\">\r\n {{ error()?.error?.message || \"Something went wrong!\" }}\r\n </div>\r\n }\r\n }\r\n </form>\r\n</hc-card>\r\n", styles: [".auth-form{background:#fffffff2;-webkit-backdrop-filter:blur(20px);backdrop-filter:blur(20px);border-radius:16px;margin:8px;padding:2rem;position:relative;z-index:1}.loading-overlay{background:#ffffffe6;-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px);border-radius:16px;display:flex;align-items:center;justify-content:center;font-weight:600;color:var(--secondary-color);font-size:1.1rem;position:absolute;top:0;left:0}.loading-overlay:after{content:\"\";width:50px;height:50px;border:5px solid var(--secondary-color);border-top:5px solid transparent;border-radius:50%;animation:spin 1s linear infinite;margin-left:10px}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.form-group{position:relative;margin-bottom:1.5rem}.form-label{font-weight:600;color:var(--text-color);margin-bottom:.5rem;font-size:.9rem;text-transform:uppercase;letter-spacing:.5px}.form-control{border:2px solid var(--border-color);border-radius:12px;padding:.75rem 1rem;font-size:1rem;transition:all .3s ease;background:#fffc;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px)}.form-control:focus{border-color:var(--secondary-color);box-shadow:0 0 0 3px #667eea1a;background:#fffffff2}.form-control::placeholder{color:var(--text-light)}.btn{border-radius:12px;padding:.75rem 1.5rem;font-weight:600;font-size:1rem;transition:all .3s ease;border:none;position:relative;overflow:hidden}.btn:before{content:\"\";position:absolute;top:0;left:-100%;width:100%;height:100%;background:linear-gradient(90deg,transparent,rgba(255,255,255,.2),transparent);transition:left .5s}.btn:hover:before{left:100%}.btn-primary{background:var(--secondary-gradient);color:#fff;box-shadow:var(--shadow-md)}.btn-primary:hover{box-shadow:var(--shadow-lg)}.btn-link{color:var(--text-light);text-decoration:none;font-size:.9rem;transition:all .3s ease}.btn-link:hover{color:var(--secondary-color);text-decoration:underline}.google-btn,.facebook-btn{display:flex;align-items:center;justify-content:center;gap:12px;color:var(--text-color);background:#ffffffe6;border:2px solid var(--border-color);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);font-weight:600;transition:all .3s ease}.google-btn:hover,.facebook-btn:hover{background:#fff;border-color:var(--secondary-color);box-shadow:var(--shadow-md)}.google-btn .icon,.facebook-btn .icon{height:24px;width:24px;background-repeat:no-repeat;background-size:contain;background-position:center}.google-btn .icon{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTgiIGhlaWdodD0iMTgiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj48cGF0aCBkPSJNMTcuNiA5LjJsLS4xLTEuOEg5djMuNGg0LjhDMTMuNiAxMiAxMyAxMyAxMiAxMy42djIuMmgzYTguOCA4LjggMCAwIDAgMi42LTYuNnoiIGZpbGw9IiM0Mjg1RjQiIGZpbGwtcnVsZT0ibm9uemVybyIvPjxwYXRoIGQ9Ik05IDE4YzIuNCAwIDQuNS0uOCA2LTIuMmwtMy0yLjJhNS40IDUuNCAwIDAgMS04LTIuOUgxVjEzYTkgOSAwIDAgMCA4IDV6IiBmaWxsPSIjMzRBODUzIiBmaWxsLXJ1bGU9Im5vbnplcm8iLz48cGF0aCBkPSJNNCAxMC43YTUuNCA1LjQgMCAwIDEgMC0zLjRWNUgxYTkgOSAwIDAgMCAwIDhsMy0yLjN6IiBmaWxsPSIjRkJCQzA1IiBmaWxsLXJ1bGU9Im5vbnplcm8iLz48cGF0aCBkPSJNOSAzLjZjMS4zIDAgMi41LjQgMy40IDEuM0wxNSAyLjNBOSA5IDAgMCAwIDEgNWwzIDIuNGE1LjQgNS40IDAgMCAxIDUtMy43eiIgZmlsbD0iI0VBNDMzNSIgZmlsbC1ydWxlPSJub256ZXJvIi8+PHBhdGggZD0iTTAgMGgxOHYxOEgweiIvPjwvZz48L3N2Zz4=)}.facebook-btn .icon{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IArs4c6QAAAmJJREFUaEPtmj9oFEEUxr9v7nIpzkIRLQQVFLUSBBs7IbfGwkrhtNHbvYiiRTqbNKIRbGwD/iFkd88uISmsEtwErRQsLEVUiGIbsDAK8W6e3IESkuzt3eVucwMz7b5hvt/73rwZluHwtcr+Wrb6BMJhgnkYMASyKlotDGZqt1goT81R1EUDdG+SqDXnWPD8n6ZkfiNB3Qk6XiAmZv+fZguw0+5ZBzp3QITAOw1EiupDrYYVZvFHaZ0DkNfCHCn7ILwAwolbZ0ccEMgCtRqLKu77pAQ45eAOBI/6CID3oqA0DrCl7tdfAIKJKPRGk7K+/nsfAci3Pav5YzMzl9fMBCBHI9+daEd8PbZvHKhmswdfTV79biSACD4tht7xOPHF4nTux65fD7RIEcIDJAZbBU2ljYrm0mLFLcSJKpSD+xTcbVX0+rhUADT19JI/ciUWwAveEDjTtwAAn0eBW4oT6LjBFxBHzAUohctQctgCdJKB1uYklJB1oLU0JkYJMZ7ReLExUFFW5oPycuwm9vyTSli/Rm8aNWKSwKmUbqNyPQrKU4mkbQQ4nv8V4CEjAU7ffDqwey33m2DGSIAhNziqiM/NDOvySdzdEiqMhA61vDQWwPH8GwCfGQtwzg0eCjGWGoCGvFbkxy0WfBv5nh8npCFUYe8WPfQslJxIDSB+IXsSx+amy10otls3v07bu1AbR3tnoXYP2D3QWeX8n2VLyJaQLaGm/4XsQbbNAkmebrtQfBdK56lBbxxoPDUYKoWzSsml5DLYTkRvAADMsv7cpkp5TKXP9+7RR3cBGpkH5weob/8FwaStQs990hUAAAAASUVORK5CYII=)}.separator{position:relative;width:calc(100% - 40px);height:1px;background:linear-gradient(90deg,transparent,var(--border-color),transparent);margin:2rem auto}.separator:after{content:\"OR\";position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);background:#ffffffe6;padding:0 1rem;font-size:.8rem;font-weight:600;color:var(--text-light);letter-spacing:1px}.error-message{color:var(--danger-color);margin-top:1rem;font-weight:600;text-align:center}@keyframes slideIn{0%{opacity:0}to{opacity:1}}@media (max-width: 480px){.auth-container{margin:1rem;border-radius:16px}.auth-form{padding:1.5rem;margin:4px}.btn{padding:.875rem 1.25rem;font-size:.95rem}}\n"] }]
832
+ }], ctorParameters: () => [{ type: undefined, decorators: [{
833
+ type: Inject,
834
+ args: [AUTH_CONFIG]
835
+ }] }, { type: i1$1.FormBuilder }, { type: AuthService }] });
836
+
837
+ /**
838
+ * Angular structural directive for permission-based conditional rendering
839
+ *
840
+ * This directive conditionally displays or hides DOM elements based on the current user's permissions.
841
+ * It integrates with the authentication state to check if the authenticated user has the required
842
+ * permission to view the content. The directive uses Angular's structural directive pattern and
843
+ * automatically updates when the user's authentication state or permissions change.
844
+ *
845
+ * The directive works by checking the user's role permissions against the required permission string.
846
+ * If the user has the required permission, the template content is rendered; otherwise, it's removed
847
+ * from the DOM.
848
+ *
849
+ * @example
850
+ * ```html
851
+ * <!-- Basic usage - show content only if user has 'users.read' permission -->
852
+ * <div *hcPermission="'users.read'">
853
+ * <p>This content is only visible to users with read permission</p>
854
+ * </div>
855
+ * ```
856
+ *
857
+ * @example
858
+ * ```html
859
+ * <!-- Using with component properties -->
860
+ * <button *hcPermission="'users.delete'" (click)="deleteUser()">
861
+ * Delete User
862
+ * </button>
863
+ * ```
864
+ *
865
+ * @example
866
+ * ```html
867
+ * <!-- Using with multiple permissions (user needs at least one) -->
868
+ * <div *hcPermission="['users.read', 'users.write']">
869
+ * <p>This content is visible to users with either read OR write permission</p>
870
+ * </div>
871
+ * ```
872
+ *
873
+ * @example
874
+ * ```html
875
+ * <!-- Using with dynamic permissions -->
876
+ * <ng-container *hcPermission="requiredPermission">
877
+ * <app-admin-panel></app-admin-panel>
878
+ * </ng-container>
879
+ * ```
880
+ *
881
+ * @example
882
+ * ```typescript
883
+ * // Component usage with dynamic permission
884
+ * export class UserListComponent {
885
+ * requiredPermission = 'users.manage';
886
+ * // Or with multiple permissions
887
+ * requiredPermissions = ['users.read', 'users.write'];
888
+ * }
889
+ * ```
890
+ *
891
+ * @see {@link AuthState} Authentication state service that provides user information
892
+ * @see {@link User} User interface that contains role and permission information
893
+ * @see {@link isRoleObject} Utility function to check if role is an object with permissions
894
+ * @see {@link NgxHichchiAuthModule} Module that provides this directive
895
+ */
896
+ class PermissionDirective {
897
+ /**
898
+ * Template reference for the content to be conditionally rendered
899
+ * @private
900
+ */
901
+ templateRef = inject(TemplateRef);
902
+ /**
903
+ * View container reference for managing the template rendering
904
+ * @private
905
+ */
906
+ viewContainerRef = inject(ViewContainerRef);
907
+ /**
908
+ * Authentication state service for accessing current user information
909
+ * @private
910
+ */
911
+ authState = inject(AuthState);
912
+ /**
913
+ * Required permission string or array of strings input signal
914
+ *
915
+ * This input defines the permission(s) that the current user must have
916
+ * for the template content to be displayed. The permission string(s)
917
+ * should match the permissions defined in the user's role.
918
+ *
919
+ * When an array is provided, the user needs to have at least one of
920
+ * the specified permissions (OR logic).
921
+ *
922
+ * @example
923
+ * ```html
924
+ * <!-- Single permission -->
925
+ * <div *hcPermission="'users.read'">Content</div>
926
+ *
927
+ * <!-- Multiple permissions (user needs at least one) -->
928
+ * <div *hcPermission="['users.read', 'users.write']">Content</div>
929
+ * ```
930
+ */
931
+ hcPermission = input.required();
932
+ /**
933
+ * Constructor that sets up the permission checking effect
934
+ *
935
+ * Initializes an Angular effect that automatically re-evaluates permission
936
+ * whenever the authentication state or required permission changes. This
937
+ * ensures the UI stays in sync with the user's current permissions.
938
+ */
939
+ constructor() {
940
+ effect(() => {
941
+ if (this.hasPermission(this.authState.user(), this.hcPermission())) {
942
+ if (this.viewContainerRef.length === 0) {
943
+ this.viewContainerRef.createEmbeddedView(this.templateRef);
944
+ }
945
+ }
946
+ else {
947
+ this.viewContainerRef.clear();
948
+ }
949
+ });
950
+ }
951
+ /**
952
+ * Checks if the user has the required permission(s)
953
+ *
954
+ * This method evaluates whether the provided user has the specified permission(s)
955
+ * by checking their role and associated permissions. It handles cases where
956
+ * the user is null, has no role, or the role doesn't contain permissions.
957
+ *
958
+ * When an array of permissions is provided, the method returns true if the user
959
+ * has at least one of the specified permissions (OR logic).
960
+ *
961
+ * @param user - The user object to check permissions for, can be null
962
+ * @param requiredPermission - The permission string or array of strings that must be present in the user's role
963
+ * @returns True if the user has the required permission(s), false otherwise
964
+ *
965
+ * @example
966
+ * ```typescript
967
+ * // Single permission
968
+ * const hasPermission = this.hasPermission(currentUser, 'users.delete');
969
+ *
970
+ * // Multiple permissions (user needs at least one)
971
+ * const hasAnyPermission = this.hasPermission(currentUser, ['users.read', 'users.write']);
972
+ * ```
973
+ *
974
+ * @private
975
+ */
976
+ hasPermission(user, requiredPermission) {
977
+ if (!user || !user.role) {
978
+ return false;
979
+ }
980
+ return isRoleObject(user.role) && user.role.permissions?.length
981
+ ? Array.isArray(requiredPermission)
982
+ ? requiredPermission.some(permission => user.role.permissions?.includes(permission))
983
+ : user.role.permissions.includes(requiredPermission)
984
+ : false;
985
+ }
986
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: PermissionDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
987
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.0.7", type: PermissionDirective, isStandalone: true, selector: "[hcPermission]", inputs: { hcPermission: { classPropertyName: "hcPermission", publicName: "hcPermission", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0 });
988
+ }
989
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: PermissionDirective, decorators: [{
990
+ type: Directive,
991
+ args: [{
992
+ selector: "[hcPermission]",
993
+ }]
994
+ }], ctorParameters: () => [] });
995
+
996
+ /**
997
+ * Enumeration of authentication guard conditions
998
+ *
999
+ * This enum defines the different conditions that authentication guards can check
1000
+ * to determine whether a user should be allowed to access a route. Each condition
1001
+ * represents a different aspect of the user's authentication state.
1002
+ *
1003
+ * These conditions are used in conjunction with AuthGuardOption to create
1004
+ * flexible route protection rules that can handle various authentication scenarios.
1005
+ *
1006
+ * @example
1007
+ * ```typescript
1008
+ * // Check if user is signed in
1009
+ * const guardOption: AuthGuardOption = {
1010
+ * condition: AuthGuardCondition.SIGNED_IN,
1011
+ * state: true,
1012
+ * redirect: '/login'
1013
+ * };
1014
+ * ```
1015
+ *
1016
+ * @example
1017
+ * ```typescript
1018
+ * // Check if user has a valid token
1019
+ * const tokenGuardOption: AuthGuardOption = {
1020
+ * condition: AuthGuardCondition.HAS_TOKEN,
1021
+ * state: true,
1022
+ * redirect: '/unauthorized'
1023
+ * };
1024
+ * ```
1025
+ *
1026
+ * @see {@link AuthGuardOption} Interface that uses these conditions
1027
+ */
1028
+ var AuthGuardCondition;
1029
+ (function (AuthGuardCondition) {
1030
+ /** Check if the user is signed in to the application */
1031
+ AuthGuardCondition["SIGNED_IN"] = "signed-in";
1032
+ /** Check if the user has a valid access token */
1033
+ AuthGuardCondition["HAS_TOKEN"] = "has-token";
1034
+ })(AuthGuardCondition || (AuthGuardCondition = {}));
1035
+
1036
+ /**
1037
+ * Retrieves all authentication guard options from a route and its parent routes
1038
+ *
1039
+ * This utility function extracts authentication guard options from the current route
1040
+ * and recursively collects options from parent routes in the route hierarchy. It handles
1041
+ * the inheritance and merging of guard options, ensuring that parent route guards are
1042
+ * applied alongside child route guards.
1043
+ *
1044
+ * The function implements a hierarchical guard system where:
1045
+ * - Child route options take precedence over parent options for the same condition
1046
+ * - Parent options are inherited when no conflicting child option exists
1047
+ * - Options are collected recursively up the route tree
1048
+ * - The final array contains all applicable guard options for the route
1049
+ *
1050
+ * This enables complex authentication scenarios where different route levels can
1051
+ * specify different authentication requirements, with child routes able to override
1052
+ * or supplement parent route authentication rules.
1053
+ *
1054
+ * @param currentRoute - The activated route snapshot to extract guard options from
1055
+ * @returns Array of authentication guard options applicable to the route
1056
+ *
1057
+ * @example
1058
+ * ```typescript
1059
+ * // Route configuration with nested guards
1060
+ * const routes: Routes = [
1061
+ * {
1062
+ * path: 'admin',
1063
+ * component: AdminLayoutComponent,
1064
+ * canActivate: [authGuard(AuthGuardCondition.SIGNED_IN, true, '/login')],
1065
+ * children: [
1066
+ * {
1067
+ * path: 'users',
1068
+ * component: UsersComponent,
1069
+ * canActivate: [authGuard(AuthGuardCondition.HAS_TOKEN, true, '/unauthorized')]
1070
+ * }
1071
+ * ]
1072
+ * }
1073
+ * ];
1074
+ *
1075
+ * // In the guard, get all applicable options
1076
+ * const allOptions = getAllAuthGuardOptions(route);
1077
+ * // Result: [
1078
+ * // { condition: 'signed-in', state: true, redirect: '/login' },
1079
+ * // { condition: 'has-token', state: true, redirect: '/unauthorized' }
1080
+ * // ]
1081
+ * ```
1082
+ *
1083
+ * @example
1084
+ * ```typescript
1085
+ * // Using in a custom guard implementation
1086
+ * export const customAuthGuard: CanActivateFn = (route, state) => {
1087
+ * const authState = inject(AuthState);
1088
+ * const router = inject(Router);
1089
+ *
1090
+ * const guardOptions = getAllAuthGuardOptions(route);
1091
+ *
1092
+ * for (const option of guardOptions) {
1093
+ * const conditionMet = checkAuthCondition(option.condition, authState);
1094
+ * if (option.state !== conditionMet) {
1095
+ * router.navigateByUrl(option.redirect);
1096
+ * return false;
1097
+ * }
1098
+ * }
1099
+ *
1100
+ * return true;
1101
+ * };
1102
+ * ```
1103
+ *
1104
+ * @example
1105
+ * ```typescript
1106
+ * // Route hierarchy with inherited guards
1107
+ * const routes: Routes = [
1108
+ * {
1109
+ * path: 'app',
1110
+ * data: { [AUTH_GUARD_OPTIONS_KEY]: [
1111
+ * { condition: AuthGuardCondition.SIGNED_IN, state: true, redirect: '/login' }
1112
+ * ]},
1113
+ * children: [
1114
+ * {
1115
+ * path: 'profile',
1116
+ * component: ProfileComponent,
1117
+ * data: { [AUTH_GUARD_OPTIONS_KEY]: [
1118
+ * { condition: AuthGuardCondition.HAS_TOKEN, state: true, redirect: '/expired' }
1119
+ * ]}
1120
+ * }
1121
+ * ]
1122
+ * }
1123
+ * ];
1124
+ *
1125
+ * // When accessing /app/profile, both parent and child guards apply
1126
+ * ```
1127
+ *
1128
+ * @see {@link AuthGuardOption} Interface defining the structure of guard options
1129
+ * @see {@link AUTH_GUARD_OPTIONS_KEY} Constant for the route data key
1130
+ * @see {@link authGuard} Function that uses this utility to process guard options
1131
+ * @see {@link ActivatedRouteSnapshot} Angular router interface for route snapshots
1132
+ */
1133
+ const getAllAuthGuardOptions = (currentRoute) => {
1134
+ const options = currentRoute.data?.[AUTH_GUARD_OPTIONS_KEY] || [];
1135
+ if (!currentRoute.parent?.data?.[AUTH_GUARD_OPTIONS_KEY]) {
1136
+ return options;
1137
+ }
1138
+ const parentOptions = getAllAuthGuardOptions(currentRoute.parent);
1139
+ for (const parentOption of parentOptions) {
1140
+ const currentConditionIndex = options.findIndex(option => option.condition === parentOption.condition);
1141
+ if (currentConditionIndex !== -1) {
1142
+ // options[currentConditionIndex] = parentOption;
1143
+ }
1144
+ else {
1145
+ options.unshift(parentOption);
1146
+ }
1147
+ }
1148
+ return options;
1149
+ };
1150
+
1151
+ /**
1152
+ * Authentication guard factory function for Angular route protection
1153
+ *
1154
+ * This function creates a route guard that protects routes based on authentication state.
1155
+ * It supports both simple single-condition guards and complex multi-condition guards.
1156
+ * The guard evaluates authentication conditions and redirects users when conditions are not met.
1157
+ *
1158
+ * The guard integrates with the AuthState service to check the current authentication status
1159
+ * and uses the Angular Router for navigation when redirects are needed. It supports checking
1160
+ * whether users are signed in, have valid tokens, and other authentication-related conditions.
1161
+ *
1162
+ * Key features:
1163
+ * - Multiple authentication condition support
1164
+ * - Automatic redirection on failed conditions
1165
+ * - Integration with AuthState for reactive authentication checks
1166
+ * - Support for both simple and complex guard configurations
1167
+ * - Type-safe condition checking
1168
+ *
1169
+ * @param param - Either a single AuthGuardCondition or an array of AuthGuardOption objects
1170
+ * @param state - Required state for single condition (ignored when using options array)
1171
+ * @param redirect - Redirect path for single condition (ignored when using options array)
1172
+ * @returns A CanActivateFn that evaluates authentication conditions and handles navigation
1173
+ *
1174
+ * @example
1175
+ * ```typescript
1176
+ * // Protecting a route that requires authentication
1177
+ * const routes: Routes = [
1178
+ * {
1179
+ * path: 'dashboard',
1180
+ * component: DashboardComponent,
1181
+ * canActivate: [authGuard(AuthGuardCondition.SIGNED_IN, true, '/login')]
1182
+ * }
1183
+ * ];
1184
+ * ```
1185
+ *
1186
+ * @example
1187
+ * ```typescript
1188
+ * // Complex guard with multiple conditions
1189
+ * const routes: Routes = [
1190
+ * {
1191
+ * path: 'admin',
1192
+ * component: AdminComponent,
1193
+ * canActivate: [authGuard([
1194
+ * { condition: AuthGuardCondition.SIGNED_IN, state: true, redirect: '/login' },
1195
+ * { condition: AuthGuardCondition.HAS_TOKEN, state: true, redirect: '/unauthorized' }
1196
+ * ])]
1197
+ * }
1198
+ * ];
1199
+ * ```
1200
+ *
1201
+ * @example
1202
+ * ```typescript
1203
+ * // Preventing authenticated users from accessing login page
1204
+ * const routes: Routes = [
1205
+ * {
1206
+ * path: 'login',
1207
+ * component: LoginComponent,
1208
+ * canActivate: [authGuard(AuthGuardCondition.SIGNED_IN, false, '/dashboard')]
1209
+ * }
1210
+ * ];
1211
+ * ```
1212
+ *
1213
+ * @see {@link AuthState} Service that provides authentication state information
1214
+ * @see {@link AuthGuardOption} Interface for configuring guard options
1215
+ * @see {@link AuthGuardCondition} Enum defining available authentication conditions
1216
+ * @see {@link getAllAuthGuardOptions} Utility function for extracting guard options from routes
1217
+ * @see {@link AUTH_GUARD_OPTIONS_KEY} Constant for storing guard options in route data
1218
+ */
1219
+ function authGuard(param, state, redirect) {
1220
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1221
+ return async (route, _state) => {
1222
+ const router = inject(Router);
1223
+ const authState = inject(AuthState);
1224
+ route.data = {
1225
+ ...route.data,
1226
+ [AUTH_GUARD_OPTIONS_KEY]: Array.isArray(param)
1227
+ ? param
1228
+ : [{ condition: param, state: state, redirect: redirect }],
1229
+ };
1230
+ const conditionCheckers = {
1231
+ [AuthGuardCondition.SIGNED_IN]: authState.signedIn,
1232
+ [AuthGuardCondition.HAS_TOKEN]: authState.hasAccessToken,
1233
+ };
1234
+ const authGuardOptions = getAllAuthGuardOptions(route);
1235
+ if (!authGuardOptions.length) {
1236
+ return true;
1237
+ }
1238
+ const conditionsMet = authGuardOptions.every(option => {
1239
+ return option.state === conditionCheckers[option.condition]();
1240
+ });
1241
+ if (!conditionsMet) {
1242
+ const option = authGuardOptions.pop();
1243
+ const redirectPath = option.redirect.startsWith("/") ? option.redirect : `/${option.redirect}`;
1244
+ await router.navigateByUrl(redirectPath);
1245
+ return false;
1246
+ }
1247
+ return true;
1248
+ };
1249
+ }
1250
+
1251
+ // noinspection JSUnusedGlobalSymbols
1252
+ function roleGuard(param, state, redirect) {
1253
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1254
+ return async (route, _state) => {
1255
+ const router = inject(Router);
1256
+ const authState = inject(AuthState);
1257
+ route.data = {
1258
+ ...route.data,
1259
+ [AUTH_GUARD_OPTIONS_KEY]: Array.isArray(param)
1260
+ ? param
1261
+ : [{ condition: param, state: state, redirect: redirect }],
1262
+ };
1263
+ const conditionCheckers = {
1264
+ [AuthGuardCondition.SIGNED_IN]: authState.signedIn,
1265
+ [AuthGuardCondition.HAS_TOKEN]: authState.hasAccessToken,
1266
+ };
1267
+ const authGuardOptions = getAllAuthGuardOptions(route);
1268
+ if (!authGuardOptions.length) {
1269
+ return true;
1270
+ }
1271
+ const conditionsMet = authGuardOptions.every(option => {
1272
+ return option.state === conditionCheckers[option.condition]();
1273
+ });
1274
+ if (!conditionsMet) {
1275
+ const option = authGuardOptions.pop();
1276
+ const redirectPath = option.redirect.startsWith("/") ? option.redirect : `/${option.redirect}`;
1277
+ await router.navigateByUrl(redirectPath);
1278
+ return false;
1279
+ }
1280
+ return true;
1281
+ };
1282
+ }
1283
+
1284
+ /**
1285
+ * Array of authentication error codes that should trigger token refresh instead of immediate redirect
1286
+ *
1287
+ * These error codes indicate authentication issues that can potentially be resolved
1288
+ * by refreshing the access token. When these errors are encountered, the interceptor
1289
+ * will attempt to refresh the token before redirecting the user to the login page.
1290
+ *
1291
+ * @example
1292
+ * ```typescript
1293
+ * // The interceptor checks if the error code is in this array
1294
+ * if (SKIPPED_ERRORS.includes(error.error?.code)) {
1295
+ * // Attempt token refresh instead of immediate redirect
1296
+ * return refreshToken(req, next);
1297
+ * }
1298
+ * ```
1299
+ *
1300
+ * @see {@link AuthErrorResponseCode} Enum containing all authentication error codes
1301
+ * @see {@link authInterceptor} Function that uses this array for error handling
1302
+ */
1303
+ const SKIPPED_ERRORS = [
1304
+ AuthErrorResponseCode.AUTH_401_EXPIRED_TOKEN,
1305
+ AuthErrorResponseCode.AUTH_401_INVALID_TOKEN,
1306
+ AuthErrorResponseCode.AUTH_401_NOT_LOGGED_IN,
1307
+ ];
1308
+ /**
1309
+ * Flag to prevent multiple simultaneous token refresh operations
1310
+ *
1311
+ * This global flag ensures that only one token refresh operation can be in progress
1312
+ * at any given time. This prevents race conditions and duplicate refresh requests
1313
+ * when multiple HTTP requests fail simultaneously due to expired tokens.
1314
+ *
1315
+ * @private
1316
+ */
1317
+ let refreshingInProgress = false;
1318
+ /**
1319
+ * Checks if an HTTP request is a token refresh request
1320
+ *
1321
+ * This utility function determines whether the given HTTP request is attempting
1322
+ * to refresh authentication tokens. This is important to avoid intercepting
1323
+ * and modifying token refresh requests themselves, which could cause infinite loops.
1324
+ *
1325
+ * @param req - The HTTP request to check
1326
+ * @returns True if the request is a token refresh request, false otherwise
1327
+ *
1328
+ * @example
1329
+ * ```typescript
1330
+ * // Used in the interceptor to avoid processing refresh token requests
1331
+ * if (!isRefreshTokenReq(req)) {
1332
+ * // Apply authentication logic
1333
+ * }
1334
+ * ```
1335
+ *
1336
+ * @private
1337
+ * @see {@link AuthEndpoint.REFRESH_TOKEN} Endpoint constant for token refresh
1338
+ * @see {@link Endpoint.AUTH} Base authentication endpoint
1339
+ */
1340
+ const isRefreshTokenReq = (req) => req.url.includes(`${Endpoint.AUTH}/${AuthEndpoint.REFRESH_TOKEN}`);
1341
+ /**
1342
+ * Subject for coordinating token refresh operations across multiple HTTP requests
1343
+ *
1344
+ * This ReplaySubject is used to coordinate token refresh operations when multiple
1345
+ * HTTP requests fail simultaneously due to expired tokens. It ensures that all
1346
+ * waiting requests receive the new token once the refresh operation completes.
1347
+ *
1348
+ * The subject emits the new access token when refresh succeeds, or an error when
1349
+ * refresh fails. It uses ReplaySubject to ensure late subscribers still receive
1350
+ * the last emitted value.
1351
+ *
1352
+ * @private
1353
+ * @see {@link ReplaySubject} RxJS subject type used for token coordination
1354
+ * @see {@link AccessToken} Type representing access tokens
1355
+ */
1356
+ let tokenSubject = new ReplaySubject(1);
1357
+ /**
1358
+ * Creates an HTTP interceptor for handling authentication tokens and automatic token refresh
1359
+ *
1360
+ * This interceptor automatically adds authentication tokens to outgoing HTTP requests
1361
+ * and handles token refresh when requests fail due to expired tokens. It provides
1362
+ * seamless authentication management for Angular applications using JWT tokens.
1363
+ *
1364
+ * Key features:
1365
+ * - Automatic token attachment to HTTP requests
1366
+ * - Automatic token refresh on authentication errors
1367
+ * - Prevention of multiple simultaneous refresh operations
1368
+ * - Coordinated handling of multiple failed requests during token refresh
1369
+ * - Automatic redirect to login page when refresh fails
1370
+ * - Configurable redirect behavior with optional callback
1371
+ *
1372
+ * The interceptor works by:
1373
+ * 1. Adding the current access token to outgoing requests
1374
+ * 2. Monitoring responses for authentication errors
1375
+ * 3. Attempting token refresh when authentication errors occur
1376
+ * 4. Retrying failed requests with the new token
1377
+ * 5. Redirecting to login when refresh fails or no refresh token is available
1378
+ *
1379
+ * @param redirect - The path to redirect to when authentication fails completely
1380
+ * @param onRedirect - Optional callback function to execute before redirecting
1381
+ * @returns An HttpInterceptorFn that can be used in Angular HTTP interceptor configuration
1382
+ *
1383
+ * @example
1384
+ * ```typescript
1385
+ * // Basic usage in app configuration
1386
+ * export const appConfig: ApplicationConfig = {
1387
+ * providers: [
1388
+ * provideHttpClient(
1389
+ * withInterceptors([
1390
+ * authInterceptor('/login')
1391
+ * ])
1392
+ * )
1393
+ * ]
1394
+ * };
1395
+ * ```
1396
+ *
1397
+ * @example
1398
+ * ```typescript
1399
+ * // With custom redirect callback
1400
+ * export const appConfig: ApplicationConfig = {
1401
+ * providers: [
1402
+ * provideHttpClient(
1403
+ * withInterceptors([
1404
+ * authInterceptor('/login', () => {
1405
+ * console.log('Redirecting to login due to authentication failure');
1406
+ * // Clear any cached data
1407
+ * localStorage.clear();
1408
+ * })
1409
+ * ])
1410
+ * )
1411
+ * ]
1412
+ * };
1413
+ * ```
1414
+ *
1415
+ * @example
1416
+ * ```typescript
1417
+ * // In a module-based application
1418
+ * @NgModule({
1419
+ * providers: [
1420
+ * {
1421
+ * provide: HTTP_INTERCEPTORS,
1422
+ * useValue: authInterceptor('/auth/login'),
1423
+ * multi: true
1424
+ * }
1425
+ * ]
1426
+ * })
1427
+ * export class AppModule {}
1428
+ * ```
1429
+ *
1430
+ * @see {@link AuthState} Service that provides authentication state and tokens
1431
+ * @see {@link AuthService} Service that handles token refresh operations
1432
+ * @see {@link HttpInterceptorFn} Angular HTTP interceptor function type
1433
+ * @see {@link SKIPPED_ERRORS} Array of error codes that trigger token refresh
1434
+ */
1435
+ function authInterceptor(redirect, onRedirect) {
1436
+ return (req, next) => {
1437
+ const authState = inject(AuthState);
1438
+ const authService = inject(AuthService);
1439
+ const router = inject(Router);
1440
+ const setAccessToken = (req, accessToken) => {
1441
+ return req.clone({
1442
+ headers: req.headers.set("Authorization", `Bearer ${accessToken}`),
1443
+ });
1444
+ };
1445
+ const gotoSignIn = () => {
1446
+ onRedirect?.();
1447
+ authState.reset();
1448
+ // eslint-disable-next-line no-void
1449
+ void router.navigateByUrl(redirect);
1450
+ };
1451
+ const refreshToken = (req, next) => {
1452
+ if (!refreshingInProgress) {
1453
+ refreshingInProgress = true;
1454
+ tokenSubject.next(null);
1455
+ const refreshToken = authState.refreshToken();
1456
+ if (!refreshToken) {
1457
+ refreshingInProgress = false;
1458
+ gotoSignIn();
1459
+ return throwError(() => new Error("Refresh token not found."));
1460
+ }
1461
+ return authService.refreshToken(refreshToken).pipe(switchMap((tokenResponse) => {
1462
+ authState.setTokens(tokenResponse);
1463
+ tokenSubject.next(tokenResponse.accessToken);
1464
+ tokenSubject.complete();
1465
+ tokenSubject = new ReplaySubject(1);
1466
+ refreshingInProgress = false;
1467
+ return next(setAccessToken(req, tokenResponse.accessToken));
1468
+ }), catchError((error) => {
1469
+ refreshingInProgress = false;
1470
+ tokenSubject.error(error);
1471
+ tokenSubject.complete();
1472
+ tokenSubject = new ReplaySubject(1);
1473
+ gotoSignIn();
1474
+ return throwError(() => error);
1475
+ }));
1476
+ }
1477
+ return tokenSubject.pipe(filter(result => result !== null), take(1), switchMap(token => {
1478
+ return next(setAccessToken(req, token));
1479
+ }));
1480
+ };
1481
+ const handleRequest = (req, next) => {
1482
+ return next(req).pipe(catchError((error) => {
1483
+ if (error.status === HttpClientErrorStatus.UNAUTHORIZED &&
1484
+ error.error?.code &&
1485
+ SKIPPED_ERRORS.includes(error.error?.code) &&
1486
+ !isRefreshTokenReq(req)) {
1487
+ if (authState.signedIn()) {
1488
+ return refreshToken(req, next);
1489
+ }
1490
+ }
1491
+ return throwError(() => error);
1492
+ }));
1493
+ };
1494
+ if (authState.accessToken()) {
1495
+ const tokenizedRequest = req.clone({
1496
+ headers: req.headers.set("Authorization", `Bearer ${authState.accessToken()}`),
1497
+ });
1498
+ return handleRequest(tokenizedRequest, next);
1499
+ }
1500
+ return handleRequest(req, next);
1501
+ };
1502
+ }
1503
+
1504
+ /**
1505
+ * Angular module for authentication functionality
1506
+ *
1507
+ * This module provides comprehensive authentication features for Angular applications,
1508
+ * including authentication forms, permission-based directives, and authentication services.
1509
+ * It integrates with the Hichchi authentication system and provides both components
1510
+ * and directives for building secure Angular applications.
1511
+ *
1512
+ * The module exports:
1513
+ * - AuthFormComponent: A ready-to-use authentication form component
1514
+ * - PermissionDirective: A structural directive for permission-based conditional rendering
1515
+ *
1516
+ * The module must be configured using the forRoot() method to provide the necessary
1517
+ * authentication configuration.
1518
+ *
1519
+ * @example
1520
+ * ```typescript
1521
+ * // Basic module configuration
1522
+ * @NgModule({
1523
+ * imports: [
1524
+ * NgxHichchiAuthModule.forRoot({
1525
+ * apiBaseURL: 'https://api.example.com'
1526
+ * })
1527
+ * ]
1528
+ * })
1529
+ * export class AppModule { }
1530
+ * ```
1531
+ *
1532
+ * @example
1533
+ * ```typescript
1534
+ * // Advanced configuration with custom authentication field
1535
+ * @NgModule({
1536
+ * imports: [
1537
+ * NgxHichchiAuthModule.forRoot({
1538
+ * apiBaseURL: 'https://api.example.com',
1539
+ * authField: AuthField.EMAIL
1540
+ * })
1541
+ * ]
1542
+ * })
1543
+ * export class AppModule { }
1544
+ * ```
1545
+ *
1546
+ * @see {@link AuthConfig} Configuration interface for the authentication module
1547
+ * @see {@link AuthFormComponent} Authentication form component
1548
+ * @see {@link PermissionDirective} Permission-based conditional rendering directive
1549
+ * @see {@link AuthService} Authentication service for managing user sessions
1550
+ */
1551
+ class NgxHichchiAuthModule {
1552
+ /**
1553
+ * Configures the NgxHichchiAuthModule with the provided authentication configuration
1554
+ *
1555
+ * This static method sets up the module with the necessary providers and configuration
1556
+ * for authentication functionality. It provides the AuthService, HTTP client, and
1557
+ * authentication configuration token that are required for the module to function properly.
1558
+ *
1559
+ * @param config - The authentication configuration object containing API endpoints and settings
1560
+ * @returns A ModuleWithProviders object configured with authentication providers
1561
+ *
1562
+ * @example
1563
+ * ```typescript
1564
+ * // Basic configuration
1565
+ * NgxHichchiAuthModule.forRoot({
1566
+ * apiBaseURL: 'https://api.example.com'
1567
+ * })
1568
+ * ```
1569
+ *
1570
+ * @example
1571
+ * ```typescript
1572
+ * // Configuration with environment variables and authentication field
1573
+ * NgxHichchiAuthModule.forRoot({
1574
+ * apiBaseURL: environment.apiUrl,
1575
+ * authField: AuthField.USERNAME
1576
+ * })
1577
+ * ```
1578
+ *
1579
+ * @see {@link AuthConfig} Interface defining the configuration structure
1580
+ * @see {@link AUTH_CONFIG} Injection token for the authentication configuration
1581
+ * @see {@link AuthService} Service that uses the provided configuration
1582
+ */
1583
+ static forRoot(config) {
1584
+ return {
1585
+ ngModule: NgxHichchiAuthModule,
1586
+ providers: [{ provide: AUTH_CONFIG, useValue: config }, provideHttpClient(), AuthService],
1587
+ };
1588
+ }
1589
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: NgxHichchiAuthModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
1590
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.0.7", ngImport: i0, type: NgxHichchiAuthModule, declarations: [AuthFormComponent], imports: [CommonModule,
1591
+ FormsModule,
1592
+ ReactiveFormsModule,
1593
+ ButtonComponent,
1594
+ HcCardComponent,
1595
+ HcCardComponent,
1596
+ HcSeparatorComponent,
1597
+ PermissionDirective], exports: [AuthFormComponent, PermissionDirective] });
1598
+ static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: NgxHichchiAuthModule, imports: [CommonModule,
1599
+ FormsModule,
1600
+ ReactiveFormsModule,
1601
+ ButtonComponent,
1602
+ HcCardComponent,
1603
+ HcCardComponent,
1604
+ HcSeparatorComponent] });
1605
+ }
1606
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.7", ngImport: i0, type: NgxHichchiAuthModule, decorators: [{
1607
+ type: NgModule,
1608
+ args: [{
1609
+ declarations: [AuthFormComponent],
1610
+ imports: [
1611
+ CommonModule,
1612
+ FormsModule,
1613
+ ReactiveFormsModule,
1614
+ ButtonComponent,
1615
+ HcCardComponent,
1616
+ HcCardComponent,
1617
+ HcSeparatorComponent,
1618
+ PermissionDirective,
1619
+ ],
1620
+ exports: [AuthFormComponent, PermissionDirective],
1621
+ }]
1622
+ }] });
1623
+
1624
+ /**
1625
+ * Generated bundle index. Do not edit.
1626
+ */
1627
+
1628
+ export { AuthFormComponent, AuthGuardCondition, AuthService, AuthState, NgxHichchiAuthModule, PermissionDirective, SKIPPED_ERRORS, authGuard, authInterceptor, roleGuard };
1629
+ //# sourceMappingURL=hichchi-ngx-auth.mjs.map