@flusys/ng-auth 1.1.1-beta → 1.2.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,815 +1,152 @@
1
- # @flusys/ng-auth Package Guide
1
+ # @flusys/ng-auth
2
2
 
3
- ## Overview
3
+ JWT authentication library for FLUSYS Angular apps — login, session restore, token refresh, multi-company selection, and provider adapter registration.
4
4
 
5
- `@flusys/ng-auth` provides complete authentication functionality including login, registration, token management, company/branch selection, user administration, and permission handling.
6
-
7
- **Package:** `@flusys/ng-auth`
8
- **Dependencies:** ng-core, ng-shared, ng-layout
9
- **Build:** `npm run build:ng-auth`
10
-
11
- ## Architecture
12
-
13
- ```
14
- ┌─────────────────────────────────────────────────────────────┐
15
- │ ng-auth │
16
- ├─────────────────────────────────────────────────────────────┤
17
- │ Services │
18
- │ ├── AuthApiService (login, register, refresh, logout) │
19
- │ ├── AuthStateService (signal-based state management) │
20
- │ ├── AuthInitService (session restoration on refresh) │
21
- │ ├── TokenRefreshStateService (concurrent refresh handling) │
22
- │ ├── UserApiService (user CRUD) │
23
- │ ├── CompanyApiService (company CRUD) │
24
- │ ├── BranchApiService (branch CRUD) │
25
- │ └── UserPermissionApiService (permission assignments) │
26
- ├─────────────────────────────────────────────────────────────┤
27
- │ Guards │
28
- │ ├── authGuard (requires authentication) │
29
- │ ├── guestGuard (allows only unauthenticated) │
30
- │ └── companyFeatureGuard (requires company feature enabled) │
31
- ├─────────────────────────────────────────────────────────────┤
32
- │ Interceptors │
33
- │ ├── authInterceptor (adds JWT + tenant headers) │
34
- │ └── tokenRefreshInterceptor (handles 401 + token refresh) │
35
- ├─────────────────────────────────────────────────────────────┤
36
- │ Adapters (Provider Interface Pattern) │
37
- │ ├── AuthLayoutStateAdapter → LAYOUT_AUTH_STATE │
38
- │ ├── AuthLayoutApiAdapter → LAYOUT_AUTH_API │
39
- │ ├── AuthUserProviderAdapter → USER_PROVIDER │
40
- │ ├── AuthCompanyApiProviderAdapter → COMPANY_API_PROVIDER │
41
- │ └── AuthUserPermissionProviderAdapter → USER_PERMISSION_PROVIDER │
42
- ├─────────────────────────────────────────────────────────────┤
43
- │ Constants │
44
- │ └── AUTH_ENDPOINTS (endpoint path definitions) │
45
- └─────────────────────────────────────────────────────────────┘
46
- ```
5
+ [![npm version](https://img.shields.io/npm/v/@flusys/ng-auth.svg)](https://www.npmjs.com/package/@flusys/ng-auth)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
47
7
 
48
8
  ---
49
9
 
50
- ## Services
51
-
52
- ### AuthApiService
53
-
54
- HTTP client for authentication endpoints.
55
-
56
- ```typescript
57
- import { AuthApiService } from '@flusys/ng-auth';
58
-
59
- const authApi = inject(AuthApiService);
60
-
61
- // Authentication
62
- authApi.login({ email, password }); // POST /auth/login
63
- authApi.register({ name, email, password }); // POST /auth/register
64
- authApi.refresh(); // POST /auth/refresh
65
- authApi.logout(); // POST /auth/logout
66
- authApi.me(); // GET /auth/me
67
-
68
- // Company/Branch selection
69
- authApi.select({ companyId, branchId, sessionId }); // POST /auth/select
70
- authApi.switchCompany({ companyId, branchId }); // POST /auth/switch-company
71
-
72
- // Password management
73
- authApi.changePassword({ currentPassword, newPassword });
74
- authApi.forgotPassword({ email });
75
- authApi.resetPassword({ token, newPassword });
76
- ```
77
-
78
- ### AuthStateService
79
-
80
- Signal-based centralized auth state management with auto-persistence.
81
-
82
- ```typescript
83
- import { AuthStateService } from '@flusys/ng-auth';
84
-
85
- const authState = inject(AuthStateService);
86
-
87
- // Read-only signals
88
- authState.user(); // Signal<IUserInfo | null>
89
- authState.accessToken(); // Signal<string | null>
90
- authState.company(); // Signal<ICompanyInfo | null>
91
- authState.branch(); // Signal<IBranchInfo | null>
92
- authState.availableCompanies(); // Signal<ICompanyWithBranches[]>
93
- authState.selectionSessionId(); // Signal<string | null>
94
-
95
- // Computed signals
96
- authState.isAuthenticated(); // Signal<boolean>
97
- authState.requiresCompanySelection(); // Signal<boolean>
98
- authState.userName(); // Signal<string>
99
- authState.companyName(); // Signal<string>
100
- authState.branchName(); // Signal<string>
101
- authState.tenantId(); // Signal<string | null>
102
-
103
- // State mutations
104
- authState.setLoginState(response); // After successful login
105
- authState.setSelectionState(response); // After company/branch selection
106
- authState.clearSelectionState(); // Clear pending selection
107
- authState.resetState(); // Full logout
108
- authState.navigateToLogin(returnUrl?);
109
- authState.navigateAfterLogin();
110
- ```
111
-
112
- **Auto-persistence:** Tokens and company/branch IDs are automatically saved to localStorage via effects.
113
-
114
- ### AuthInitService
115
-
116
- Handles session restoration on page refresh.
117
-
118
- ```typescript
119
- import { AuthInitService } from '@flusys/ng-auth';
120
-
121
- const authInit = inject(AuthInitService);
122
-
123
- // Signals
124
- authInit.initialized(); // Signal<boolean>
125
- authInit.loading(); // Signal<boolean>
126
- authInit.error(); // Signal<string | null>
127
-
128
- // Restore session (called by appInitGuard)
129
- authInit.initialize().subscribe({
130
- next: (user) => console.log('Session restored:', user),
131
- error: (err) => console.log('No session or expired')
132
- });
133
- ```
134
-
135
- **Flow:**
136
- 1. Attempts `POST /auth/refresh` with httpOnly cookie
137
- 2. On success, calls `GET /auth/me` to get user info
138
- 3. Updates AuthStateService with restored session
139
- 4. Caches result to prevent duplicate calls
140
-
141
- ### Resource API Services
142
-
143
- All extend `ApiResourceService` from ng-shared with signal-based state.
144
-
145
- ```typescript
146
- import { UserApiService, CompanyApiService, BranchApiService } from '@flusys/ng-auth';
147
-
148
- // Common signals (from ApiResourceService)
149
- service.data(); // Signal<T[]>
150
- service.isLoading(); // Signal<boolean>
151
- service.error(); // Signal<string | null>
152
-
153
- // Common methods
154
- await service.insertAsync(data); // Create
155
- await service.updateAsync(data); // Update
156
- await service.deleteAsync(id); // Delete
157
- await service.fetchByIdAsync(id); // Get one
158
- service.fetchList(search, options); // Get list with pagination
159
- ```
10
+ ## Installation
160
11
 
161
- **UserApiService** - Endpoint: `administration/users`
162
- ```typescript
163
- userApi.verifyEmail(userId);
164
- userApi.verifyPhone(userId);
165
- userApi.updateStatus(userId, status);
166
- userApi.updateProfile(data);
167
- ```
168
-
169
- **CompanyApiService** - Endpoint: `administration/company`
170
-
171
- **BranchApiService** - Endpoint: `administration/branch`
172
- ```typescript
173
- branchApi.currentCompanyId(); // Signal for company context
174
- branchApi.companyBranches(); // Computed: branches filtered by company
175
- branchApi.setCurrentCompanyId(id);
176
- branchApi.insertBranch(data); // Auto-sets companyId
177
- branchApi.fetchByCompany(companyId);
12
+ ```bash
13
+ npm install @flusys/ng-auth @flusys/ng-core @flusys/ng-shared
178
14
  ```
179
15
 
180
- ### UserPermissionApiService
181
-
182
- Manages user-to-company and user-to-branch permission assignments.
16
+ ## 1. Register Providers
183
17
 
184
18
  ```typescript
185
- import { UserPermissionApiService } from '@flusys/ng-auth';
186
-
187
- const permApi = inject(UserPermissionApiService);
188
-
189
- // Company permissions
190
- permApi.assignUserCompanies({
191
- userId: 'user-1',
192
- items: [
193
- { targetId: 'company-1', isAdd: true }, // Assign
194
- { targetId: 'company-2', isAdd: false } // Revoke
195
- ]
196
- });
197
- permApi.getUserCompanies(userId); // Get user's companies
198
- permApi.assignUserToCompany(userId, companyId); // Single assign
199
- permApi.revokeUserFromCompany(userId, companyId); // Single revoke
200
-
201
- // Branch permissions
202
- permApi.assignUserBranches({
203
- userId: 'user-1',
204
- items: [
205
- { targetId: 'branch-1', isAdd: true }
206
- ]
207
- });
208
- permApi.getUserBranches(userId); // Get user's branches
209
- permApi.assignUserToBranch(userId, branchId);
210
- permApi.revokeUserFromBranch(userId, branchId);
211
- ```
212
-
213
- **Backend Endpoints:**
214
- - `POST /administration/permissions/user-company/assign`
215
- - `GET /administration/permissions/user-company?userId=xxx`
216
- - `POST /administration/permissions/user-branch/assign`
217
- - `GET /administration/permissions/user-branch?userId=xxx`
218
-
219
- ---
220
-
221
- ## Guards
222
-
223
- ### authGuard
224
-
225
- Protects routes requiring authentication.
226
-
227
- ```typescript
228
- import { authGuard } from '@flusys/ng-auth';
229
-
230
- {
231
- path: 'dashboard',
232
- canActivate: [authGuard],
233
- loadComponent: () => import('./dashboard.component')
234
- }
235
- ```
236
-
237
- **Checks:**
238
- 1. User is authenticated
239
- 2. If company feature enabled: company AND branch are selected
240
- 3. Redirects to `/auth/login` with `returnUrl` if checks fail
241
-
242
- ### guestGuard
243
-
244
- Protects auth routes (login, register) from authenticated users.
245
-
246
- ```typescript
247
- import { guestGuard } from '@flusys/ng-auth';
248
-
249
- {
250
- path: 'auth/login',
251
- canActivate: [guestGuard],
252
- loadComponent: () => import('./login.component')
253
- }
254
- ```
255
-
256
- **Behavior:**
257
- 1. Restores session via `AuthInitService.initialize()` first
258
- 2. If authenticated AND company selected: redirects to home or returnUrl
259
- 3. If selection pending: allows access (login handles Step 2)
260
- 4. If not authenticated: allows access
261
-
262
- ### companyFeatureGuard
263
-
264
- Enables/disables company management routes based on config.
265
-
266
- ```typescript
267
- import { companyFeatureGuard } from '@flusys/ng-auth';
268
-
269
- {
270
- path: 'administration/company',
271
- canActivate: [authGuard, companyFeatureGuard],
272
- loadComponent: () => import('./company-list.component')
273
- }
274
- ```
275
-
276
- **Checks:** `services.auth.enabled` in APP_CONFIG. Redirects to `/` if disabled.
277
-
278
- ---
279
-
280
- ## Interceptors
281
-
282
- ### authInterceptor
283
-
284
- Adds authentication headers to HTTP requests.
285
-
286
- ```typescript
287
- import { authInterceptor } from '@flusys/ng-auth';
288
-
289
- provideHttpClient(
290
- withInterceptors([authInterceptor, ...])
291
- )
292
- ```
293
-
294
- **Adds:**
295
- - `Authorization: Bearer {token}` if token exists
296
- - Tenant header if multi-tenant enabled
297
- - `withCredentials: true` for auth endpoints (cookie support)
298
-
299
- ### tokenRefreshInterceptor
300
-
301
- Handles 401 responses and token refresh.
302
-
303
- ```typescript
304
- import { tokenRefreshInterceptor } from '@flusys/ng-auth';
305
-
306
- provideHttpClient(
307
- withInterceptors([authInterceptor, tokenRefreshInterceptor, ...])
308
- )
309
- ```
310
-
311
- **Behavior:**
312
- 1. Intercepts 401 Unauthorized responses
313
- 2. Skips refresh for: `/auth/refresh`, `/auth/login`, `/auth/register`, `/auth/select`
314
- 3. Uses `TokenRefreshStateService` to queue concurrent requests during refresh
315
- 4. On success: updates token, retries original request
316
- 5. On failure: logs out, shows error, redirects to login
317
-
318
- ---
319
-
320
- ## Constants
321
-
322
- ### AUTH_ENDPOINTS
323
-
324
- Centralized endpoint definitions used by interceptors.
325
-
326
- ```typescript
327
- import { AUTH_ENDPOINTS, isAuthEndpoint, isEndpoint } from '@flusys/ng-auth';
328
-
329
- // Endpoint constants
330
- AUTH_ENDPOINTS.LOGIN // '/auth/login'
331
- AUTH_ENDPOINTS.LOGOUT // '/auth/logout'
332
- AUTH_ENDPOINTS.REFRESH // '/auth/refresh'
333
- AUTH_ENDPOINTS.REGISTER // '/auth/register'
334
- AUTH_ENDPOINTS.SELECT // '/auth/select'
335
- AUTH_ENDPOINTS.SWITCH_COMPANY // '/auth/switch-company'
336
-
337
- // Helper functions
338
- isAuthEndpoint(url); // Check if URL is any auth endpoint
339
- isEndpoint(url, endpoint); // Check if URL matches specific endpoint
340
- ```
341
-
342
- ---
343
-
344
- ## Provider Adapters
345
-
346
- ng-auth implements provider interfaces from ng-shared, enabling ng-iam and ng-storage to access auth data without direct dependencies.
347
-
348
- ### provideAuthProviders()
349
-
350
- Registers provider adapters for ng-iam/ng-storage.
351
-
352
- ```typescript
353
- import { provideAuthProviders } from '@flusys/ng-auth';
354
-
355
19
  // app.config.ts
356
- providers: [
357
- ...provideAuthProviders()
358
- ]
359
- ```
360
-
361
- **Registers:**
362
- - `USER_PROVIDER` → `AuthUserProviderAdapter` (user list for ng-iam)
363
- - `COMPANY_API_PROVIDER` → `AuthCompanyApiProviderAdapter` (company list for ng-iam)
364
- - `USER_PERMISSION_PROVIDER` → `AuthUserPermissionProviderAdapter` (permission queries)
365
-
366
- ### provideAuthLayoutIntegration()
367
-
368
- Bridges ng-auth to ng-layout components.
369
-
370
- ```typescript
371
- import { provideAuthLayoutIntegration } from '@flusys/ng-auth';
372
-
373
- // app.config.ts
374
- providers: [
375
- ...provideAuthLayoutIntegration()
376
- ]
377
- ```
378
-
379
- **Registers:**
380
- - `LAYOUT_AUTH_STATE` → `AuthLayoutStateAdapter` (user/company/branch signals)
381
- - `LAYOUT_AUTH_API` → `AuthLayoutApiAdapter` (logout, switch company, etc.)
382
-
383
- **AuthLayoutApiAdapter Methods:**
384
- ```typescript
385
- logOut();
386
- navigateLogin(withUrl?: boolean);
387
- switchCompany(companyId, branchId); // Triggers page reload
388
- getUserCompanies(); // User's permitted companies
389
- getCompanyBranches(companyId); // User's permitted branches
390
- ```
391
-
392
- ---
393
-
394
- ## Pages
395
-
396
- ### Authentication Pages
397
-
398
- **LoginPageComponent** (`/auth/login`)
399
- - Multi-step login: Step 1 = credentials, Step 2 = company/branch selection (if needed)
400
- - Visual step indicator
401
- - Auto-selects if only one company/branch available
402
- - Stores returnUrl for post-login redirect
403
-
404
- **RegisterPageComponent** (`/auth/register`)
405
- - User registration with optional company creation
406
- - Email/password validation
407
- - Terms acceptance
408
-
409
- ### Profile Page
410
-
411
- **ProfilePageComponent** (`/profile`)
412
- - Update name and phone
413
- - Upload profile picture (requires `PROFILE_UPLOAD_PROVIDER` from ng-storage)
414
- - Change password
415
- - View company/branch info (if company feature enabled)
416
- - View roles/actions (requires `PROFILE_PERMISSION_PROVIDER` from ng-iam)
417
-
418
- **Optional Provider Dependencies:**
419
-
420
- | Provider | Source | Feature Enabled |
421
- |----------|--------|-----------------|
422
- | `PROFILE_UPLOAD_PROVIDER` | ng-storage via `provideStorageProviders()` | Profile picture upload |
423
- | `PROFILE_PERMISSION_PROVIDER` | ng-iam via `provideIamProviders()` | Roles/actions display |
424
-
425
- **Graceful Degradation:**
426
- - If provider not registered, feature shows "not enabled" message
427
- - If service disabled in config, section is hidden
428
- - No errors thrown for missing optional providers
429
-
430
- ### Administration Pages
431
-
432
- **AdministrationPageComponent** (`/administration`)
433
- - Tabbed interface: Users, Companies, Branches
434
- - Company/Branch tabs require company feature enabled
435
-
436
- **User Management:**
437
- - `UserListComponent` - LazyLoad datatable with Edit, Delete, Permission actions
438
- - `UserFormComponent` - Create/edit user form
439
-
440
- **Company Management:**
441
- - `CompanyListComponent` - Company CRUD
442
- - `CompanyFormComponent` - Create/edit company
443
-
444
- **Branch Management:**
445
- - `BranchListComponent` - Branch CRUD filtered by company
446
- - `BranchFormComponent` - Create/edit branch
447
-
448
- ---
449
-
450
- ## Dialog Components
451
-
452
- ### UserCompanyPermissionDialogComponent
453
-
454
- Manages user-to-company assignments with checkboxes.
455
-
456
- ```typescript
457
- import { UserCompanyPermissionDialogComponent } from '@flusys/ng-auth';
458
-
459
- @Component({
460
- imports: [UserCompanyPermissionDialogComponent],
461
- template: `
462
- <lib-user-company-permission-dialog
463
- [visible]="showDialog()"
464
- [user]="selectedUser()"
465
- (closed)="showDialog.set(false)"
466
- (permissionsChanged)="reloadData()" />
467
- `
468
- })
469
- ```
470
-
471
- **Features:**
472
- - Shows all companies with checkboxes
473
- - Pre-checks assigned companies
474
- - Immediate save on toggle (no Save button)
475
- - Toast notifications for success/error
476
-
477
- ### UserBranchPermissionDialogComponent
478
-
479
- Manages user-to-branch assignments with company filtering.
480
-
481
- ```typescript
482
- import { UserBranchPermissionDialogComponent } from '@flusys/ng-auth';
483
-
484
- @Component({
485
- imports: [UserBranchPermissionDialogComponent],
486
- template: `
487
- <lib-user-branch-permission-dialog
488
- [visible]="showDialog()"
489
- [user]="selectedUser()"
490
- (closed)="showDialog.set(false)"
491
- (permissionsChanged)="reloadData()" />
492
- `
493
- })
494
- ```
495
-
496
- **Features:**
497
- - Shows only companies where user has company-level permissions
498
- - User selects company → branches load
499
- - Immediate save on toggle
500
- - Scoped to user's permitted companies
501
-
502
- **Workflow:**
503
- 1. Dialog opens → Loads user's permitted companies
504
- 2. User selects company → Loads branches for that company
505
- 3. User toggles branch checkboxes → Immediately assigns/revokes
506
-
507
- ---
508
-
509
- ## Routes
510
-
511
- ### AUTH_ROUTES
512
-
513
- Public authentication routes.
514
-
515
- ```typescript
516
- import { AUTH_ROUTES } from '@flusys/ng-auth';
517
-
518
- // Routes: /auth/login, /auth/register
519
- { path: 'auth', children: AUTH_ROUTES }
520
- ```
521
-
522
- ### ADMINISTRATION_ROUTES
523
-
524
- Protected administration routes.
525
-
526
- ```typescript
527
- import { ADMINISTRATION_ROUTES } from '@flusys/ng-auth';
528
-
529
- // Routes: /administration/users/*, /administration/company/*, /administration/branch/*
530
- {
531
- path: 'administration',
532
- canActivate: [authGuard],
533
- children: ADMINISTRATION_ROUTES
534
- }
535
- ```
536
-
537
- ### PROFILE_ROUTES
538
-
539
- User profile page routes.
540
-
541
- ```typescript
542
- import { PROFILE_ROUTES } from '@flusys/ng-auth';
543
-
544
- // Route: /profile
545
- {
546
- path: 'profile',
547
- canActivate: [authGuard],
548
- children: PROFILE_ROUTES
549
- }
550
- ```
551
-
552
- ---
553
-
554
- ## Setup
555
-
556
- ### app.config.ts
557
-
558
- ```typescript
559
- import { ApplicationConfig } from '@angular/core';
560
- import { provideHttpClient, withFetch, withInterceptors } from '@angular/common/http';
561
- import { APP_CONFIG, errorCatchingInterceptor } from '@flusys/ng-core';
562
- import {
563
- authInterceptor,
564
- tokenRefreshInterceptor,
565
- provideAuthLayoutIntegration,
566
- provideAuthProviders
567
- } from '@flusys/ng-auth';
568
- import { provideIamProviders } from '@flusys/ng-iam';
569
- import { provideStorageProviders } from '@flusys/ng-storage';
20
+ import { provideHttpClient, withInterceptors } from '@angular/common/http';
21
+ import { provideAuthProviders, provideAuthLayoutIntegration, authInterceptor, tokenRefreshInterceptor } from '@flusys/ng-auth';
570
22
 
571
23
  export const appConfig: ApplicationConfig = {
572
24
  providers: [
573
25
  provideHttpClient(
574
- withFetch(),
575
26
  withInterceptors([
576
- authInterceptor,
577
- tokenRefreshInterceptor,
578
- errorCatchingInterceptor
27
+ authInterceptor, // attaches Bearer token; sends withCredentials for cookie endpoints
28
+ tokenRefreshInterceptor, // proactive + reactive refresh; queues concurrent 401s
579
29
  ])
580
30
  ),
581
- { provide: APP_CONFIG, useValue: DEFAULT_APP_CONFIG },
582
- ...provideAuthLayoutIntegration(),
583
- ...provideAuthProviders(),
584
- ...provideIamProviders(), // Enables permissions display on profile page
585
- ...provideStorageProviders(), // Enables profile picture upload
586
- ]
31
+ ...provideAuthProviders(), // USER_PROVIDER, COMPANY_API_PROVIDER, USER_PERMISSION_PROVIDER, AUTH_STATE_PROVIDER
32
+ ...provideAuthLayoutIntegration(), // LAYOUT_AUTH_STATE, LAYOUT_AUTH_API
33
+ ],
587
34
  };
588
35
  ```
589
36
 
590
- ### app.routes.ts
37
+ Interceptor order matters: `authInterceptor` must come before `tokenRefreshInterceptor`.
38
+
39
+ ## 2. Add Routes
591
40
 
592
41
  ```typescript
593
- import { Routes } from '@angular/router';
594
- import { authGuard, guestGuard, AUTH_ROUTES, ADMINISTRATION_ROUTES, PROFILE_ROUTES } from '@flusys/ng-auth';
42
+ // app.routes.ts
43
+ import { AUTH_ROUTES, ADMINISTRATION_ROUTES, PROFILE_ROUTES } from '@flusys/ng-auth';
595
44
 
596
45
  export const routes: Routes = [
597
- { path: 'auth', children: AUTH_ROUTES },
598
46
  {
599
- path: '',
600
- canActivate: [authGuard],
601
- children: [
602
- { path: 'dashboard', loadComponent: () => import('./dashboard.component') },
603
- { path: 'administration', children: ADMINISTRATION_ROUTES },
604
- { path: 'profile', children: PROFILE_ROUTES }
605
- ]
606
- }
47
+ path: 'auth',
48
+ loadChildren: () => AUTH_ROUTES, // login, register, forgot-password, reset-password, verify-email
49
+ },
50
+ {
51
+ path: 'administration',
52
+ loadChildren: () => ADMINISTRATION_ROUTES, // users, company, branch (protected by authGuard)
53
+ },
54
+ {
55
+ path: 'profile',
56
+ loadChildren: () => PROFILE_ROUTES, // profile page (protected by authGuard)
57
+ },
607
58
  ];
608
59
  ```
609
60
 
610
- ---
611
-
612
- ## Interfaces
613
-
614
- ### Request DTOs
61
+ ## 3. Protect Routes with Guards
615
62
 
616
63
  ```typescript
617
- interface ILoginRequest { email: string; password: string; }
618
- interface IRegistrationRequest { name: string; email: string; password: string; phone?: string; }
619
- interface ISelectRequest { companyId: string; branchId?: string; sessionId: string; }
620
- interface ISwitchCompanyRequest { companyId: string; branchId: string; }
621
- interface IChangePasswordRequest { currentPassword: string; newPassword: string; }
622
- ```
64
+ import { authGuard, guestGuard, companyFeatureGuard } from '@flusys/ng-auth';
623
65
 
624
- ### Response DTOs
625
-
626
- ```typescript
627
- interface ILoginResponse {
628
- accessToken: string;
629
- user: IUserInfo;
630
- requiresSelection?: boolean;
631
- sessionId?: string;
632
- companies?: ICompanyWithBranches[];
633
- expiresAt?: number;
634
- }
66
+ const routes: Routes = [
67
+ {
68
+ path: 'dashboard',
69
+ canActivate: [authGuard], // redirects to /auth/login if not authenticated
70
+ loadComponent: () => import('./dashboard.component'),
71
+ },
72
+ {
73
+ path: 'auth/login',
74
+ canActivate: [guestGuard], // redirects to '/' if already authenticated; restores session first
75
+ loadComponent: () => import('./login.component'),
76
+ },
77
+ ];
78
+ ```
635
79
 
636
- interface ICompanySelectionResponse {
637
- accessToken: string;
638
- user: IUserInfo;
639
- company: ICompanyInfo;
640
- branch: IBranchInfo;
641
- }
80
+ | Guard | Exported name | Behavior |
81
+ |---|---|---|
82
+ | Require auth | `authGuard` | Redirects to `/auth/login`; checks company if `enableCompanyFeature` is on |
83
+ | Require guest | `guestGuard` | Redirects to `/` (or `returnUrl`) if authenticated; calls `AuthInitService.initialize()` |
84
+ | Require company feature | `companyFeatureGuard` | Redirects to `/` if company feature is disabled in config |
642
85
 
643
- interface IMeResponse extends IUserInfo {}
644
- interface IRefreshTokenResponse { accessToken: string; }
645
- ```
86
+ ## 4. Read Auth State
646
87
 
647
- ### Entity DTOs
88
+ `AuthStateService` holds all auth state as read-only signals. The access token is memory-only (never persisted).
648
89
 
649
90
  ```typescript
650
- interface IUser {
651
- id: string;
652
- name: string;
653
- email: string;
654
- phone?: string;
655
- profilePictureId?: string;
656
- isActive: boolean;
657
- isEmailVerified: boolean;
658
- isPhoneVerified: boolean;
659
- lastLoginAt?: Date;
660
- createdAt: Date;
661
- updatedAt: Date;
662
- }
91
+ import { AuthStateService } from '@flusys/ng-auth';
663
92
 
664
- interface ICompany {
665
- id: string;
666
- name: string;
667
- slug: string;
668
- logoId?: string;
669
- address?: string;
670
- phone?: string;
671
- email?: string;
672
- website?: string;
673
- isActive: boolean;
674
- createdAt: Date;
675
- updatedAt: Date;
676
- }
93
+ @Component({ ... })
94
+ export class MyComponent {
95
+ private readonly authState = inject(AuthStateService);
677
96
 
678
- interface IBranch {
679
- id: string;
680
- name: string;
681
- slug: string;
682
- companyId: string;
683
- parentId?: string;
684
- logoId?: string;
685
- address?: string;
686
- phone?: string;
687
- email?: string;
688
- isActive: boolean;
689
- createdAt: Date;
690
- updatedAt: Date;
97
+ readonly user = this.authState.user; // Signal<IUserInfo | null>
98
+ readonly company = this.authState.company; // Signal<ICompanyInfo | null>
99
+ readonly branch = this.authState.branch; // Signal<IBranchInfo | null>
100
+ readonly isAuth = this.authState.isAuthenticated; // Signal<boolean>
101
+ readonly tokenExpired = this.authState.isTokenExpired; // Signal<boolean>
691
102
  }
692
103
  ```
693
104
 
694
- ### Permission DTOs
105
+ Key state methods:
695
106
 
696
107
  ```typescript
697
- interface IPermissionItem { targetId: string; isAdd: boolean; }
698
- interface IAssignUserCompanyRequest { userId: string; items: IPermissionItem[]; }
699
- interface IAssignUserBranchRequest { userId: string; items: IPermissionItem[]; }
700
- interface IBatchOperationResponse { success: boolean; message: string; }
701
- interface IUserCompanyPermission { id: string; userId: string; companyId: string; companyName: string; companySlug: string; }
702
- interface IUserBranchPermission { id: string; userId: string; branchId: string; branchName: string; branchSlug: string; companyId: string; }
703
- ```
704
-
705
- ---
706
-
707
- ## Best Practices
708
-
709
- ### Component Organization
710
-
711
- ```
712
- projects/ng-auth/
713
- ├── components/ ← Shared dialogs
714
- │ ├── user-company-permission-dialog.component.ts
715
- │ └── user-branch-permission-dialog.component.ts
716
- ├── pages/ ← Route-specific pages
717
- │ ├── login/
718
- │ ├── register/
719
- │ └── users/
720
- ├── services/
721
- ├── guards/
722
- ├── interceptors/
723
- └── adapters/
108
+ authState.setLoginState(data); // sets user + token + company + branch
109
+ authState.setSelectionState(data); // multi-company selection flow
110
+ authState.resetState(); // logout — clears all state + localStorage
111
+ authState.navigateToLogin(returnUrl?); // safe redirect (rejects external URLs)
724
112
  ```
725
113
 
726
- **Rule:** Dialog components go in `components/`, page components go in `pages/`.
114
+ ## 6. Session Restore
727
115
 
728
- ### Permission Filtering Pattern
729
-
730
- When building permission UI, filter by user's permitted scope:
116
+ `AuthInitService.initialize()` runs `POST /auth/refresh` (HTTP-only cookie) then `POST /auth/me` to restore session in memory. It is called automatically by `guestGuard`. Call it manually if needed:
731
117
 
732
118
  ```typescript
733
- // ✅ CORRECT - Filter by user's permitted companies
734
- const response = await firstValueFrom(permApi.getUserCompanies(userId));
735
- const userCompanies = response.data;
736
-
737
- // ❌ WRONG - Don't show all companies
738
- const allCompanies = await companyApi.fetchList(); // Security risk
739
- ```
740
-
741
- ### Token Management
742
-
743
- - Use httpOnly cookies for refresh tokens (backend)
744
- - Access tokens stored in memory via AuthStateService
745
- - TokenRefreshInterceptor handles automatic refresh
746
- - Auto-persistence for company/branch selection
747
-
748
- ### Multi-Company Flow
749
-
750
- 1. Login → Check if `requiresSelection: true`
751
- 2. If yes → Show Step 2 (company/branch selection)
752
- 3. Auto-select if only one company/branch
753
- 4. Save selection → Navigate to dashboard
754
-
755
- ---
756
-
757
- ## Common Issues
758
-
759
- ### Guards Not Working
119
+ import { AuthInitService } from '@flusys/ng-auth';
760
120
 
761
- **Problem:** Routes accessible without authentication.
121
+ const authInit = inject(AuthInitService);
762
122
 
763
- **Solution:** Ensure authGuard is on parent route and session is restored:
764
- ```typescript
765
- {
766
- path: '',
767
- canActivate: [authGuard], // Parent guard
768
- children: [...]
769
- }
123
+ authInit.initialize().subscribe(isAuthenticated => {
124
+ // safe to read authState signals now
125
+ });
770
126
  ```
771
127
 
772
- ### Token Not Sent
773
-
774
- **Problem:** 401 on all requests.
775
-
776
- **Solution:** Ensure interceptor order is correct:
777
- ```typescript
778
- withInterceptors([
779
- authInterceptor, // First: adds token
780
- tokenRefreshInterceptor, // Second: handles 401
781
- errorCatchingInterceptor // Last: shows errors
782
- ])
783
- ```
128
+ The `initialized` signal is `true` after the first call completes (success or failure).
784
129
 
785
- ### Layout Not Showing User
130
+ ## 7. Extend Registration / Profile (Optional)
786
131
 
787
- **Problem:** User info missing from topbar.
132
+ Inject custom fields and sections via extension tokens:
788
133
 
789
- **Solution:** Ensure layout integration is provided:
790
134
  ```typescript
135
+ import { AUTH_REGISTRATION_EXTENSION, AUTH_PROFILE_EXTENSION, AUTH_USER_LIST_EXTENSION, AUTH_VALIDATION_CONFIG } from '@flusys/ng-auth';
136
+
791
137
  providers: [
792
- ...provideAuthLayoutIntegration()
138
+ { provide: AUTH_REGISTRATION_EXTENSION, useClass: MyRegistrationExtension }, // extra fields on register form
139
+ { provide: AUTH_PROFILE_EXTENSION, useClass: MyProfileExtension }, // extra sections on profile page
140
+ { provide: AUTH_USER_LIST_EXTENSION, useClass: MyUserListExtension }, // extra columns/actions on user list
141
+ {
142
+ provide: AUTH_VALIDATION_CONFIG,
143
+ useValue: {
144
+ password: { minLength: 10, requireUppercase: true, requireSpecialChars: true },
145
+ },
146
+ },
793
147
  ]
794
148
  ```
795
149
 
796
- ### Company Switcher Shows All Companies
797
-
798
- **Problem:** Switcher shows companies user doesn't have access to.
799
-
800
- **Solution:** `AuthLayoutApiAdapter.getUserCompanies()` uses `UserPermissionApiService.getUserCompanies(userId)` internally - ensure user has company permissions assigned.
801
-
802
- ---
803
-
804
- ## See Also
805
-
806
- - [CORE-GUIDE.md](./CORE-GUIDE.md) - APP_CONFIG configuration
807
- - [SHARED-GUIDE.md](./SHARED-GUIDE.md) - Provider interfaces
808
- - [LAYOUT-GUIDE.md](./LAYOUT-GUIDE.md) - Layout integration
809
- - [IAM-GUIDE.md](./IAM-GUIDE.md) - Permission management
810
- - [../FLUSYS_NEST/docs/AUTH-GUIDE.md](../../FLUSYS_NEST/docs/AUTH-GUIDE.md) - Backend auth module
811
-
812
- ---
150
+ ## License
813
151
 
814
- **Last Updated:** 2026-02-16
815
- **Angular Version:** 21
152
+ MIT © FLUSYS