@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 +90 -753
- package/fesm2022/flusys-ng-auth.mjs +6469 -4221
- package/fesm2022/flusys-ng-auth.mjs.map +1 -1
- package/package.json +11 -11
- package/types/flusys-ng-auth.d.ts +544 -905
package/README.md
CHANGED
|
@@ -1,815 +1,152 @@
|
|
|
1
|
-
# @flusys/ng-auth
|
|
1
|
+
# @flusys/ng-auth
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
JWT authentication library for FLUSYS Angular apps — login, session restore, token refresh, multi-company selection, and provider adapter registration.
|
|
4
4
|
|
|
5
|
-
|
|
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
|
+
[](https://www.npmjs.com/package/@flusys/ng-auth)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
47
7
|
|
|
48
8
|
---
|
|
49
9
|
|
|
50
|
-
##
|
|
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
|
-
|
|
162
|
-
|
|
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
|
-
|
|
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
|
-
|
|
357
|
-
|
|
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
|
-
|
|
582
|
-
...provideAuthLayoutIntegration(),
|
|
583
|
-
|
|
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
|
-
|
|
37
|
+
Interceptor order matters: `authInterceptor` must come before `tokenRefreshInterceptor`.
|
|
38
|
+
|
|
39
|
+
## 2. Add Routes
|
|
591
40
|
|
|
592
41
|
```typescript
|
|
593
|
-
|
|
594
|
-
import {
|
|
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
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
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
|
-
|
|
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
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
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
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
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
|
-
|
|
644
|
-
interface IRefreshTokenResponse { accessToken: string; }
|
|
645
|
-
```
|
|
86
|
+
## 4. Read Auth State
|
|
646
87
|
|
|
647
|
-
|
|
88
|
+
`AuthStateService` holds all auth state as read-only signals. The access token is memory-only (never persisted).
|
|
648
89
|
|
|
649
90
|
```typescript
|
|
650
|
-
|
|
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
|
-
|
|
665
|
-
|
|
666
|
-
|
|
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
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
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
|
-
|
|
105
|
+
Key state methods:
|
|
695
106
|
|
|
696
107
|
```typescript
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
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
|
-
|
|
114
|
+
## 6. Session Restore
|
|
727
115
|
|
|
728
|
-
|
|
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
|
-
|
|
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
|
-
|
|
121
|
+
const authInit = inject(AuthInitService);
|
|
762
122
|
|
|
763
|
-
|
|
764
|
-
|
|
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
|
-
|
|
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
|
-
|
|
130
|
+
## 7. Extend Registration / Profile (Optional)
|
|
786
131
|
|
|
787
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
815
|
-
**Angular Version:** 21
|
|
152
|
+
MIT © FLUSYS
|