@flusys/ng-iam 1.1.0 → 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 +44 -1179
- package/fesm2022/flusys-ng-iam-action-form-page.component-Vc5Toouu.mjs +502 -0
- package/fesm2022/flusys-ng-iam-action-form-page.component-Vc5Toouu.mjs.map +1 -0
- package/fesm2022/flusys-ng-iam-action-list-page.component-UGKclpFx.mjs +348 -0
- package/fesm2022/flusys-ng-iam-action-list-page.component-UGKclpFx.mjs.map +1 -0
- package/fesm2022/{flusys-ng-iam-flusys-ng-iam-CJAQT60K.mjs → flusys-ng-iam-flusys-ng-iam-CrycDULz.mjs} +1814 -1622
- package/fesm2022/flusys-ng-iam-flusys-ng-iam-CrycDULz.mjs.map +1 -0
- package/fesm2022/flusys-ng-iam-iam-container.component-D0m8u6nu.mjs +105 -0
- package/fesm2022/flusys-ng-iam-iam-container.component-D0m8u6nu.mjs.map +1 -0
- package/fesm2022/{flusys-ng-iam-permission-page.component-DcgT7L3_.mjs → flusys-ng-iam-permission-page.component-x88X4AgJ.mjs} +38 -32
- package/fesm2022/flusys-ng-iam-permission-page.component-x88X4AgJ.mjs.map +1 -0
- package/fesm2022/flusys-ng-iam-role-form-page.component-InsxdriD.mjs +335 -0
- package/fesm2022/flusys-ng-iam-role-form-page.component-InsxdriD.mjs.map +1 -0
- package/fesm2022/flusys-ng-iam-role-list-page.component-8NGXCeSZ.mjs +389 -0
- package/fesm2022/flusys-ng-iam-role-list-page.component-8NGXCeSZ.mjs.map +1 -0
- package/fesm2022/flusys-ng-iam.mjs +1 -1
- package/package.json +11 -10
- package/types/flusys-ng-iam.d.ts +53 -252
- package/fesm2022/flusys-ng-iam-action-form-page.component-eXpZNJ_H.mjs +0 -389
- package/fesm2022/flusys-ng-iam-action-form-page.component-eXpZNJ_H.mjs.map +0 -1
- package/fesm2022/flusys-ng-iam-action-list-page.component-BtJlGcTj.mjs +0 -262
- package/fesm2022/flusys-ng-iam-action-list-page.component-BtJlGcTj.mjs.map +0 -1
- package/fesm2022/flusys-ng-iam-flusys-ng-iam-CJAQT60K.mjs.map +0 -1
- package/fesm2022/flusys-ng-iam-iam-container.component-UYJjqYV9.mjs +0 -92
- package/fesm2022/flusys-ng-iam-iam-container.component-UYJjqYV9.mjs.map +0 -1
- package/fesm2022/flusys-ng-iam-permission-page.component-DcgT7L3_.mjs.map +0 -1
- package/fesm2022/flusys-ng-iam-role-form-page.component-D_AAEay2.mjs +0 -272
- package/fesm2022/flusys-ng-iam-role-form-page.component-D_AAEay2.mjs.map +0 -1
- package/fesm2022/flusys-ng-iam-role-list-page.component-D4J1by6Q.mjs +0 -299
- package/fesm2022/flusys-ng-iam-role-list-page.component-D4J1by6Q.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -1,1230 +1,95 @@
|
|
|
1
|
-
# @flusys/ng-iam
|
|
1
|
+
# @flusys/ng-iam
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Identity and Access Management UI for FLUSYS Angular apps — role management, permission assignment, and reactive permission checking via signals.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
**Key Principle:** ng-iam is **100% independent** from ng-auth through the **Provider Interface Pattern**. No compile-time or runtime dependencies on ng-auth.
|
|
8
|
-
|
|
9
|
-
## Package Information
|
|
10
|
-
|
|
11
|
-
| Item | Value |
|
|
12
|
-
|------|-------|
|
|
13
|
-
| Package | `@flusys/ng-iam` |
|
|
14
|
-
| Version | 1.0.1 |
|
|
15
|
-
| Dependencies | ng-core, ng-shared, ng-layout (IMenuItem only) |
|
|
16
|
-
| NO dependency on | ng-auth (fully independent via Provider Pattern) |
|
|
17
|
-
| CSS Framework | Tailwind CSS (not PrimeFlex) |
|
|
18
|
-
| Build | `npm run build:ng-iam` |
|
|
19
|
-
| Entry point | `public-api.ts` |
|
|
20
|
-
|
|
21
|
-
---
|
|
22
|
-
|
|
23
|
-
## Table of Contents
|
|
24
|
-
|
|
25
|
-
1. [Architecture](#architecture)
|
|
26
|
-
2. [Provider Interfaces](#provider-interfaces)
|
|
27
|
-
3. [Integration Options](#integration-options)
|
|
28
|
-
4. [Permission Modes](#permission-modes)
|
|
29
|
-
5. [Routes](#routes)
|
|
30
|
-
6. [Services](#services)
|
|
31
|
-
7. [Components](#components)
|
|
32
|
-
8. [Page Components](#page-components)
|
|
33
|
-
9. [Interfaces](#interfaces)
|
|
34
|
-
10. [Utils & Constants](#utils--constants)
|
|
35
|
-
11. [Backend Integration](#backend-integration)
|
|
36
|
-
12. [Best Practices](#best-practices)
|
|
37
|
-
13. [Common Issues](#common-issues)
|
|
38
|
-
|
|
39
|
-
---
|
|
40
|
-
|
|
41
|
-
## Architecture
|
|
42
|
-
|
|
43
|
-
```
|
|
44
|
-
ng-shared (defines interfaces + tokens)
|
|
45
|
-
|
|
|
46
|
-
Your Auth System (implements interfaces with adapters)
|
|
47
|
-
|
|
|
48
|
-
ng-iam (consumes interfaces via DI)
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
ng-iam resolves all auth-related data at runtime via DI tokens defined in ng-shared. If a required token is not provided, Angular throws a clear error at injection time.
|
|
5
|
+
[](https://www.npmjs.com/package/@flusys/ng-iam)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
52
7
|
|
|
53
8
|
---
|
|
54
9
|
|
|
55
|
-
##
|
|
56
|
-
|
|
57
|
-
ng-iam requires these provider interfaces:
|
|
58
|
-
|
|
59
|
-
| Token | Source | Purpose | Required? |
|
|
60
|
-
|-------|--------|---------|-----------|
|
|
61
|
-
| `USER_PROVIDER` | `@flusys/ng-shared` | User list for role/action assignment | Yes |
|
|
62
|
-
| `LAYOUT_AUTH_STATE` | `@flusys/ng-layout` | Current company/branch context | Yes (if company feature enabled) |
|
|
63
|
-
| `COMPANY_API_PROVIDER` | `@flusys/ng-shared` | Company list for whitelisting | Optional (for CompanyActionSelector) |
|
|
64
|
-
| `USER_PERMISSION_PROVIDER` | `@flusys/ng-shared` | User branch permissions | Optional (for branch-scoped permissions) |
|
|
65
|
-
|
|
66
|
-
### How Components Use Providers
|
|
67
|
-
|
|
68
|
-
User selection uses `lib-user-select` component from ng-shared (encapsulates USER_PROVIDER):
|
|
69
|
-
|
|
70
|
-
```html
|
|
71
|
-
<!-- UserActionSelectorComponent / UserRoleSelectorComponent -->
|
|
72
|
-
<lib-user-select
|
|
73
|
-
[(value)]="selectedUserId"
|
|
74
|
-
[isEditMode]="true"
|
|
75
|
-
placeHolder="Select a user"
|
|
76
|
-
/>
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
Company context is accessed via `LAYOUT_AUTH_STATE`:
|
|
80
|
-
|
|
81
|
-
```typescript
|
|
82
|
-
private readonly companyContext = inject(LAYOUT_AUTH_STATE);
|
|
83
|
-
|
|
84
|
-
// Get current company
|
|
85
|
-
const companyId = this.companyContext.currentCompanyInfo()?.id;
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
Branch permissions loaded from `USER_PERMISSION_PROVIDER`:
|
|
10
|
+
## Installation
|
|
89
11
|
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const response = await this.userPermissionProvider
|
|
94
|
-
.getUserBranchPermissions(userId)
|
|
95
|
-
.toPromise();
|
|
12
|
+
```bash
|
|
13
|
+
npm install @flusys/ng-iam @flusys/ng-core @flusys/ng-shared
|
|
96
14
|
```
|
|
97
15
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
## Integration Options
|
|
101
|
-
|
|
102
|
-
### Option 1: With FLUSYS ng-auth (Recommended)
|
|
103
|
-
|
|
104
|
-
ng-auth provides pre-built adapters for all required interfaces.
|
|
16
|
+
## 1. Register Providers
|
|
105
17
|
|
|
106
18
|
```typescript
|
|
107
19
|
// app.config.ts
|
|
108
|
-
import {
|
|
20
|
+
import { provideIamProviders } from '@flusys/ng-iam';
|
|
109
21
|
|
|
110
22
|
export const appConfig: ApplicationConfig = {
|
|
111
23
|
providers: [
|
|
112
|
-
|
|
24
|
+
...provideIamProviders(), // registers PROFILE_PERMISSION_PROVIDER for ng-shared's permission directives/guards
|
|
113
25
|
],
|
|
114
26
|
};
|
|
115
27
|
```
|
|
116
28
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
| Adapter | Interface | Path |
|
|
120
|
-
|---------|-----------|------|
|
|
121
|
-
| `AuthUserProviderAdapter` | `IUserProvider` | `projects/ng-auth/adapters/` |
|
|
122
|
-
| `AuthLayoutStateAdapter` | `ILayoutAuthState` | `projects/ng-auth/adapters/` |
|
|
123
|
-
| `AuthCompanyApiProviderAdapter` | `ICompanyApiProvider` | `projects/ng-auth/adapters/` |
|
|
124
|
-
| `AuthUserPermissionProviderAdapter` | `IUserPermissionProvider` | `projects/ng-auth/adapters/` |
|
|
125
|
-
|
|
126
|
-
### Option 2: With Custom Auth System
|
|
127
|
-
|
|
128
|
-
Implement the required interfaces with your own auth system. No ng-auth dependency needed.
|
|
129
|
-
|
|
130
|
-
```typescript
|
|
131
|
-
// Minimum configuration (basic IAM)
|
|
132
|
-
providers: [
|
|
133
|
-
{ provide: USER_PROVIDER, useClass: YourUserProvider },
|
|
134
|
-
{ provide: LAYOUT_AUTH_STATE, useClass: YourCompanyContextProvider },
|
|
135
|
-
]
|
|
136
|
-
|
|
137
|
-
// Full configuration (company + branch permissions)
|
|
138
|
-
providers: [
|
|
139
|
-
{ provide: USER_PROVIDER, useClass: YourUserProvider },
|
|
140
|
-
{ provide: LAYOUT_AUTH_STATE, useClass: YourCompanyContextProvider },
|
|
141
|
-
{ provide: COMPANY_API_PROVIDER, useClass: YourCompanyApiProvider },
|
|
142
|
-
{ provide: USER_PERMISSION_PROVIDER, useClass: YourUserPermissionProvider },
|
|
143
|
-
]
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
**Feature matrix:**
|
|
147
|
-
|
|
148
|
-
| Feature | Providers Needed |
|
|
149
|
-
|---------|-----------------|
|
|
150
|
-
| Basic role assignments | USER_PROVIDER only |
|
|
151
|
-
| Company-scoped permissions | + LAYOUT_AUTH_STATE, COMPANY_API_PROVIDER |
|
|
152
|
-
| Branch-scoped permissions | + LAYOUT_AUTH_STATE, USER_PERMISSION_PROVIDER |
|
|
153
|
-
| Direct user permissions | + USER_PERMISSION_PROVIDER |
|
|
154
|
-
|
|
155
|
-
All adapters must return `IListResponse<T>` or `ISingleResponse<T>` format from `@flusys/ng-shared`.
|
|
156
|
-
|
|
157
|
-
---
|
|
158
|
-
|
|
159
|
-
## Permission Modes
|
|
160
|
-
|
|
161
|
-
FLUSYS IAM supports three permission modes configured via `APP_CONFIG.iam.permissionMode`:
|
|
162
|
-
|
|
163
|
-
| Mode | Structure | Tabs Available | Use Case |
|
|
164
|
-
|------|-----------|---------------|----------|
|
|
165
|
-
| **DIRECT** | User -> Actions | User-Actions | Small apps, per-user permissions |
|
|
166
|
-
| **RBAC** | User -> Role -> Actions | Role-Actions, User-Roles | Standard apps, role templates |
|
|
167
|
-
| **FULL** | Both combined | Role-Actions, User-Roles, User-Actions | Maximum flexibility |
|
|
168
|
-
|
|
169
|
-
When company feature is enabled, a **Company-Actions** tab is added for whitelisting actions per company.
|
|
170
|
-
|
|
171
|
-
---
|
|
172
|
-
|
|
173
|
-
## Routes
|
|
174
|
-
|
|
175
|
-
**Export:** `IAM_ROUTES` from `@flusys/ng-iam`
|
|
176
|
-
|
|
177
|
-
```
|
|
178
|
-
/iam (IamContainerComponent - tabbed navigation)
|
|
179
|
-
/actions (ActionListPageComponent - hierarchical tree)
|
|
180
|
-
/actions/new (ActionFormPageComponent - create)
|
|
181
|
-
/actions/:id (ActionFormPageComponent - edit)
|
|
182
|
-
/roles (RoleListPageComponent - paginated table)
|
|
183
|
-
/roles/new (RoleFormPageComponent - create)
|
|
184
|
-
/roles/:id (RoleFormPageComponent - edit)
|
|
185
|
-
/permissions (PermissionPageComponent - tabbed assignment UI)
|
|
186
|
-
(default redirect -> /actions)
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
**Usage in app routes:**
|
|
29
|
+
## 2. Add IAM Routes
|
|
190
30
|
|
|
191
31
|
```typescript
|
|
32
|
+
// app.routes.ts
|
|
192
33
|
import { IAM_ROUTES } from '@flusys/ng-iam';
|
|
193
34
|
|
|
194
35
|
export const routes: Routes = [
|
|
195
|
-
{
|
|
36
|
+
{
|
|
37
|
+
path: 'iam',
|
|
38
|
+
loadChildren: () => IAM_ROUTES,
|
|
39
|
+
},
|
|
196
40
|
];
|
|
197
41
|
```
|
|
198
42
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
## Services
|
|
202
|
-
|
|
203
|
-
### PermissionStateService
|
|
204
|
-
|
|
205
|
-
Manages current user's permissions with signal-based state. Provided at root level.
|
|
206
|
-
|
|
207
|
-
**Dependencies:** `MyPermissionsApiService`, `PermissionValidatorService` (from ng-shared)
|
|
208
|
-
|
|
209
|
-
**Signals:**
|
|
210
|
-
|
|
211
|
-
| Signal | Type | Description |
|
|
212
|
-
|--------|------|-------------|
|
|
213
|
-
| `permissions` | `Signal<IMyPermissionsResponseDto \| null>` | Current user permissions (readonly) |
|
|
214
|
-
| `isLoading` | `Signal<boolean>` | Loading state (readonly) |
|
|
215
|
-
|
|
216
|
-
**Methods:**
|
|
217
|
-
|
|
218
|
-
| Method | Returns | Description |
|
|
219
|
-
|--------|---------|-------------|
|
|
220
|
-
| `loadPermissions(dto?)` | `Observable<void>` | Load permissions from `/iam/permissions/my-permissions` |
|
|
221
|
-
| `isLoaded()` | `boolean` | Check if permissions have been loaded |
|
|
222
|
-
|
|
223
|
-
Permission checking is delegated to `PermissionValidatorService` from ng-shared (hasAction, hasAllActions, hasAnyAction, getActionCodes).
|
|
224
|
-
|
|
225
|
-
```typescript
|
|
226
|
-
const permissionState = inject(PermissionStateService);
|
|
227
|
-
|
|
228
|
-
// Load permissions (typically in guard)
|
|
229
|
-
await firstValueFrom(permissionState.loadPermissions());
|
|
230
|
-
|
|
231
|
-
// Check permissions (via PermissionValidatorService)
|
|
232
|
-
const validator = inject(PermissionValidatorService);
|
|
233
|
-
validator.hasAction('user.create');
|
|
234
|
-
validator.hasAllActions(['user.create', 'user.update']);
|
|
235
|
-
validator.hasAnyAction(['user.view', 'user.list']);
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
---
|
|
239
|
-
|
|
240
|
-
### ActionApiService
|
|
241
|
-
|
|
242
|
-
Extends `ApiResourceService<IUpdateActionDto, IAction>` for action CRUD.
|
|
243
|
-
|
|
244
|
-
**Resource:** `iam/actions`
|
|
245
|
-
|
|
246
|
-
**Additional Methods:**
|
|
247
|
-
|
|
248
|
-
| Method | Endpoint | Description |
|
|
249
|
-
|--------|----------|-------------|
|
|
250
|
-
| `getTree(search?, isActive?, withDeleted?)` | `POST /iam/actions/tree` | Get hierarchical action tree |
|
|
251
|
-
| `getActionsForPermission()` | `POST /iam/actions/tree-for-permission` | Get actions filtered by company whitelist |
|
|
252
|
-
|
|
253
|
-
Inherits standard CRUD from `ApiResourceService`: `getAll`, `getById`, `insert`, `update`, `delete`.
|
|
254
|
-
|
|
255
|
-
---
|
|
256
|
-
|
|
257
|
-
### RoleApiService
|
|
258
|
-
|
|
259
|
-
Extends `ApiResourceService<IUpdateRoleDto, IRole>` for role CRUD.
|
|
260
|
-
|
|
261
|
-
**Resource:** `iam/roles`
|
|
262
|
-
|
|
263
|
-
Inherits standard CRUD from `ApiResourceService`. Only available in RBAC/FULL mode.
|
|
264
|
-
|
|
265
|
-
---
|
|
266
|
-
|
|
267
|
-
### PermissionApiService
|
|
268
|
-
|
|
269
|
-
Extends `BaseApiService` for all permission assignment operations.
|
|
270
|
-
|
|
271
|
-
**Base:** `super('iam')` - all paths resolved via `this.getUrl(...)` relative to `/iam/`
|
|
272
|
-
|
|
273
|
-
**User -> Action (Direct Permissions):**
|
|
274
|
-
|
|
275
|
-
| Method | Endpoint | Description |
|
|
276
|
-
|--------|----------|-------------|
|
|
277
|
-
| `assignUserActions(data)` | `POST /iam/permissions/user-actions/assign` | Assign/remove actions for user |
|
|
278
|
-
| `getUserActions(userId, query?)` | `GET /iam/permissions/user-actions/{userId}` | Get user's direct actions |
|
|
279
|
-
|
|
280
|
-
**User -> Role:**
|
|
281
|
-
|
|
282
|
-
| Method | Endpoint | Description |
|
|
283
|
-
|--------|----------|-------------|
|
|
284
|
-
| `assignUserRoles(data)` | `POST /iam/permissions/user-roles/assign` | Assign/remove roles for user |
|
|
285
|
-
| `getUserRoles(userId, query?)` | `GET /iam/permissions/user-roles/{userId}` | Get user's roles |
|
|
286
|
-
|
|
287
|
-
**Role -> Action:**
|
|
288
|
-
|
|
289
|
-
| Method | Endpoint | Description |
|
|
290
|
-
|--------|----------|-------------|
|
|
291
|
-
| `assignRoleActions(data)` | `POST /iam/permissions/role-actions/assign` | Assign/remove actions for role |
|
|
292
|
-
| `getRoleActions(roleId, query?)` | `GET /iam/permissions/role-actions/{roleId}` | Get role's actions |
|
|
293
|
-
|
|
294
|
-
**Company -> Action (Whitelisting):**
|
|
295
|
-
|
|
296
|
-
| Method | Endpoint | Description |
|
|
297
|
-
|--------|----------|-------------|
|
|
298
|
-
| `assignCompanyActions(data)` | `POST /iam/permissions/company-actions/assign` | Assign/remove company whitelist |
|
|
299
|
-
| `getCompanyActions(companyId)` | `GET /iam/permissions/company-actions/{companyId}` | Get company's whitelisted actions |
|
|
300
|
-
|
|
301
|
-
> **Note:** Read operations use GET (exception to the POST-only RPC convention). Write operations use POST.
|
|
302
|
-
|
|
303
|
-
---
|
|
304
|
-
|
|
305
|
-
### MyPermissionsApiService
|
|
306
|
-
|
|
307
|
-
Fetches current user's permissions.
|
|
308
|
-
|
|
309
|
-
**Method:**
|
|
310
|
-
|
|
311
|
-
| Method | Endpoint | Description |
|
|
312
|
-
|--------|----------|-------------|
|
|
313
|
-
| `getMyPermissions(dto?)` | `POST /iam/permissions/my-permissions` | Get current user's frontend actions and cached endpoint count |
|
|
314
|
-
|
|
315
|
-
---
|
|
316
|
-
|
|
317
|
-
### ActionPermissionLogicService
|
|
318
|
-
|
|
319
|
-
Manages smart prerequisite validation using AND/OR logic trees. Uses PrimeNG `ConfirmationService` and `MessageService` for dialogs.
|
|
320
|
-
|
|
321
|
-
**Core Methods:**
|
|
322
|
-
|
|
323
|
-
| Method | Description |
|
|
324
|
-
|--------|-------------|
|
|
325
|
-
| `handleCheck(action, selection, allActions, onUpdate, onCancel)` | Handle action selection with recursive prerequisite validation |
|
|
326
|
-
| `handleUncheck(action, selection, allActions, onUpdate)` | Handle deselection with dependency detection |
|
|
327
|
-
| `hasUnmetPrerequisites(action, selection, allActions)` | Check if action has unmet prerequisites |
|
|
328
|
-
| `getActionsWithUnmetPrerequisites(selection, allActions)` | Get all selected actions with unmet prerequisites |
|
|
329
|
-
| `showValidationErrorDialog(invalid, selection, allActions, onUpdate)` | Pre-save validation error dialog with auto-fix |
|
|
330
|
-
| `getPrerequisiteTooltip(action, selection, allActions)` | Get tooltip text with prerequisite info |
|
|
331
|
-
|
|
332
|
-
**Features:**
|
|
333
|
-
- Recursive deep-scan for cascading prerequisites
|
|
334
|
-
- Smart AND/OR optimization (selects minimum required actions)
|
|
335
|
-
- Visual formatting with status indicators
|
|
336
|
-
- Alternative suggestions for OR logic branches
|
|
337
|
-
- Pre-save validation with auto-fix capability
|
|
338
|
-
|
|
339
|
-
---
|
|
340
|
-
|
|
341
|
-
## Components
|
|
342
|
-
|
|
343
|
-
### LogicBuilderComponent
|
|
344
|
-
|
|
345
|
-
Visual AND/OR permission logic tree editor.
|
|
346
|
-
|
|
347
|
-
| Property | Value |
|
|
348
|
-
|----------|-------|
|
|
349
|
-
| Selector | `lib-logic-builder` |
|
|
350
|
-
| Standalone | Yes |
|
|
351
|
-
| Change Detection | OnPush |
|
|
352
|
-
|
|
353
|
-
**Inputs:**
|
|
354
|
-
|
|
355
|
-
| Input | Type | Description |
|
|
356
|
-
|-------|------|-------------|
|
|
357
|
-
| `logic` | `ILogicNode \| null` | Current permission logic tree |
|
|
358
|
-
| `actions` | `Array<{id, name}>` | Available actions for dropdowns |
|
|
359
|
-
|
|
360
|
-
**Outputs:**
|
|
361
|
-
|
|
362
|
-
| Output | Type | Description |
|
|
363
|
-
|--------|------|-------------|
|
|
364
|
-
| `logicChange` | `ILogicNode \| null` | Emitted when logic changes |
|
|
365
|
-
|
|
366
|
-
**Features:** Recursive tree builder, AND/OR operator toggling, action selector dropdowns, node add/remove.
|
|
367
|
-
|
|
368
|
-
---
|
|
369
|
-
|
|
370
|
-
### UserActionSelectorComponent
|
|
371
|
-
|
|
372
|
-
Assigns actions directly to users (DIRECT/FULL mode).
|
|
373
|
-
|
|
374
|
-
| Property | Value |
|
|
375
|
-
|----------|-------|
|
|
376
|
-
| Selector | `flusys-user-action-selector` |
|
|
377
|
-
| Standalone | Yes |
|
|
378
|
-
| Change Detection | OnPush |
|
|
379
|
-
|
|
380
|
-
**Dependencies:** `APP_CONFIG`, `LAYOUT_AUTH_STATE`, `USER_PERMISSION_PROVIDER`, `ActionApiService`, `ActionPermissionLogicService`, `PermissionApiService`, `MessageService`
|
|
381
|
-
|
|
382
|
-
**Features:**
|
|
383
|
-
- User dropdown (`lib-user-select` component from ng-shared)
|
|
384
|
-
- Branch selector (conditional, loaded from USER_PERMISSION_PROVIDER)
|
|
385
|
-
- Hierarchical TreeTable with checkboxes
|
|
386
|
-
- Responsive table with `overflow-x-auto` container
|
|
387
|
-
- Responsive columns (Code hidden on mobile, Description hidden until lg)
|
|
388
|
-
- Action type tags (backend/frontend/both)
|
|
389
|
-
- Prerequisite validation with tooltips
|
|
390
|
-
- Change tracking with pending add/remove summary
|
|
391
|
-
- Select/deselect all
|
|
392
|
-
- Pre-save validation
|
|
393
|
-
|
|
394
|
-
---
|
|
395
|
-
|
|
396
|
-
### UserRoleSelectorComponent
|
|
397
|
-
|
|
398
|
-
Assigns roles to users (RBAC/FULL mode).
|
|
399
|
-
|
|
400
|
-
| Property | Value |
|
|
401
|
-
|----------|-------|
|
|
402
|
-
| Selector | `flusys-user-role-selector` |
|
|
403
|
-
| Standalone | Yes |
|
|
404
|
-
| Change Detection | OnPush |
|
|
405
|
-
|
|
406
|
-
**Dependencies:** `APP_CONFIG`, `LAYOUT_AUTH_STATE`, `USER_PERMISSION_PROVIDER`, `RoleApiService`, `PermissionApiService`, `MessageService`
|
|
407
|
-
|
|
408
|
-
**Features:**
|
|
409
|
-
- User dropdown (`lib-user-select` component from ng-shared)
|
|
410
|
-
- Branch selector (conditional)
|
|
411
|
-
- DataTable with pagination and `overflow-x-auto` container
|
|
412
|
-
- Responsive columns (Code hidden on mobile, Description hidden until md)
|
|
413
|
-
- Change tracking with pending add/remove summary
|
|
414
|
-
- Select/deselect all
|
|
415
|
-
- No prerequisite validation (roles are simple assignments)
|
|
416
|
-
|
|
417
|
-
---
|
|
418
|
-
|
|
419
|
-
### RoleActionSelectorComponent
|
|
420
|
-
|
|
421
|
-
Assigns actions to roles (RBAC/FULL mode).
|
|
422
|
-
|
|
423
|
-
| Property | Value |
|
|
424
|
-
|----------|-------|
|
|
425
|
-
| Selector | `flusys-role-action-selector` |
|
|
426
|
-
| Standalone | Yes |
|
|
427
|
-
| Change Detection | OnPush |
|
|
428
|
-
|
|
429
|
-
**Dependencies:** `RoleApiService`, `ActionApiService`, `PermissionApiService`, `MessageService`, `ActionPermissionLogicService`
|
|
430
|
-
|
|
431
|
-
**Features:**
|
|
432
|
-
- Role dropdown with filter
|
|
433
|
-
- Hierarchical TreeTable with checkboxes and `overflow-x-auto` container
|
|
434
|
-
- Responsive columns (Code hidden until md, Requirements hidden until lg)
|
|
435
|
-
- Prerequisite validation with smart dependency handling
|
|
436
|
-
- Request cancellation with AbortController
|
|
437
|
-
- Pre-save validation with auto-fix dialog
|
|
438
|
-
- Change tracking with pending add/remove summary
|
|
439
|
-
|
|
440
|
-
---
|
|
441
|
-
|
|
442
|
-
### CompanyActionSelectorComponent
|
|
443
|
-
|
|
444
|
-
Manages company action whitelisting (when company feature enabled).
|
|
445
|
-
|
|
446
|
-
| Property | Value |
|
|
447
|
-
|----------|-------|
|
|
448
|
-
| Selector | `flusys-company-action-selector` |
|
|
449
|
-
| Standalone | Yes |
|
|
450
|
-
| Change Detection | OnPush |
|
|
451
|
-
|
|
452
|
-
**Dependencies:** `COMPANY_API_PROVIDER`, `ActionApiService`, `PermissionApiService`, `MessageService`, `ConfirmationService`, `ActionPermissionLogicService`
|
|
453
|
-
|
|
454
|
-
**Features:**
|
|
455
|
-
- Company dropdown with filter (loaded from COMPANY_API_PROVIDER)
|
|
456
|
-
- Full action tree (not filtered by company whitelist)
|
|
457
|
-
- Hierarchical TreeTable with `overflow-x-auto` container
|
|
458
|
-
- Responsive columns (Code hidden until md, Description hidden until lg)
|
|
459
|
-
- Prerequisite validation with tooltips
|
|
460
|
-
- Backend prerequisite error handling with auto-fix
|
|
461
|
-
- Request cancellation with AbortController
|
|
462
|
-
- Change tracking with pending whitelist/remove summary
|
|
463
|
-
|
|
464
|
-
---
|
|
465
|
-
|
|
466
|
-
## Page Components
|
|
467
|
-
|
|
468
|
-
> Page components are **not exported** from the public API. They are internal and accessed via `IAM_ROUTES` (lazy-loaded by the router).
|
|
469
|
-
|
|
470
|
-
### IamContainerComponent
|
|
471
|
-
|
|
472
|
-
Tabbed navigation container for IAM pages.
|
|
473
|
-
|
|
474
|
-
| Property | Value |
|
|
475
|
-
|----------|-------|
|
|
476
|
-
| Selector | `lib-iam-container` |
|
|
477
|
-
| Tabs | Actions (always), Roles (RBAC/FULL), Permissions (always) |
|
|
478
|
-
| Change Detection | OnPush |
|
|
479
|
-
|
|
480
|
-
**Features:**
|
|
481
|
-
- Responsive page title (`text-xl sm:text-2xl`)
|
|
482
|
-
- Horizontally scrollable tabs on mobile (hidden scrollbar)
|
|
483
|
-
- Uses `RouterLink` and `RouterLinkActive` for navigation
|
|
484
|
-
- Tab visibility based on permission mode
|
|
485
|
-
|
|
486
|
-
**Responsive Patterns:**
|
|
487
|
-
|
|
488
|
-
```html
|
|
489
|
-
<!-- Page title -->
|
|
490
|
-
<h1 class="text-xl sm:text-2xl font-bold m-0">Identity & Access Management</h1>
|
|
491
|
-
|
|
492
|
-
<!-- Horizontally scrollable tabs (hidden scrollbar) -->
|
|
493
|
-
<div class="scrollbar-hide flex gap-1 overflow-x-auto flex-nowrap">
|
|
494
|
-
<!-- tabs -->
|
|
495
|
-
</div>
|
|
496
|
-
```
|
|
497
|
-
|
|
498
|
-
**Scrollbar hide CSS:**
|
|
499
|
-
```css
|
|
500
|
-
.scrollbar-hide {
|
|
501
|
-
-ms-overflow-style: none;
|
|
502
|
-
scrollbar-width: none;
|
|
503
|
-
&::-webkit-scrollbar { display: none; }
|
|
504
|
-
}
|
|
505
|
-
```
|
|
506
|
-
|
|
507
|
-
---
|
|
508
|
-
|
|
509
|
-
### ActionListPageComponent
|
|
510
|
-
|
|
511
|
-
Hierarchical action list with CRUD operations.
|
|
512
|
-
|
|
513
|
-
| Property | Value |
|
|
514
|
-
|----------|-------|
|
|
515
|
-
| Selector | `lib-action-list-page` |
|
|
516
|
-
| Features | TreeTable display, create/edit/delete, read-only indicators, company context awareness |
|
|
517
|
-
|
|
518
|
-
**Responsive features:**
|
|
519
|
-
- TreeTable with `overflow-x-auto -mx-4 sm:mx-0` container
|
|
520
|
-
- Responsive columns: Code (hidden until md), Active (hidden until sm), Read Only (hidden until lg)
|
|
521
|
-
- Responsive header with stacked layout on mobile
|
|
522
|
-
|
|
523
|
-
Reloads actions when company context changes via effect.
|
|
524
|
-
|
|
525
|
-
---
|
|
526
|
-
|
|
527
|
-
### ActionFormPageComponent
|
|
528
|
-
|
|
529
|
-
Action create/edit form with LogicBuilder integration.
|
|
530
|
-
|
|
531
|
-
| Property | Value |
|
|
532
|
-
|----------|-------|
|
|
533
|
-
| Selector | `lib-action-form-page` |
|
|
534
|
-
| Features | Create/edit mode, parent action selector, ActionType enum, permissionLogic editor, display order, active flag |
|
|
535
|
-
|
|
536
|
-
**Form fields:** name (required), description, code, actionType, permissionLogic, parentId, serial, isActive, metadata.
|
|
537
|
-
|
|
538
|
-
---
|
|
539
|
-
|
|
540
|
-
### RoleListPageComponent
|
|
541
|
-
|
|
542
|
-
Paginated role list with CRUD operations.
|
|
543
|
-
|
|
544
|
-
| Property | Value |
|
|
545
|
-
|----------|-------|
|
|
546
|
-
| Selector | `lib-role-list-page` |
|
|
547
|
-
| Features | Paginated table (10/25/50), create/edit/delete, company context filtering, read-only indicators |
|
|
548
|
-
|
|
549
|
-
**Responsive features:**
|
|
550
|
-
- Table with `overflow-x-auto -mx-4 sm:mx-0` container
|
|
551
|
-
- Responsive columns: Description (hidden until md), Read Only (hidden until sm)
|
|
552
|
-
- Conditional paginator (only shown when data exists)
|
|
43
|
+
All routes are protected by `permissionGuard` using constants from `ng-shared`.
|
|
553
44
|
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
### RoleFormPageComponent
|
|
557
|
-
|
|
558
|
-
Role create/edit form.
|
|
559
|
-
|
|
560
|
-
| Property | Value |
|
|
561
|
-
|----------|-------|
|
|
562
|
-
| Selector | `lib-role-form-page` |
|
|
563
|
-
| Features | Create/edit mode, company auto-assignment on create, active flag, display order |
|
|
564
|
-
|
|
565
|
-
**Form fields:** name (required), description, serial, isActive. Company assigned automatically from context.
|
|
566
|
-
|
|
567
|
-
---
|
|
568
|
-
|
|
569
|
-
### PermissionPageComponent
|
|
570
|
-
|
|
571
|
-
Main permission management interface with tabbed layout.
|
|
572
|
-
|
|
573
|
-
| Property | Value |
|
|
574
|
-
|----------|-------|
|
|
575
|
-
| Selector | `lib-permission-page` |
|
|
576
|
-
| Features | Dynamic tabs based on permission mode and company feature |
|
|
577
|
-
|
|
578
|
-
**Tabs:**
|
|
579
|
-
- Role-Actions (RBAC/FULL) - `RoleActionSelectorComponent`
|
|
580
|
-
- User-Roles (RBAC/FULL) - `UserRoleSelectorComponent`
|
|
581
|
-
- User-Actions (DIRECT/FULL) - `UserActionSelectorComponent`
|
|
582
|
-
- Company-Actions (if company feature enabled) - `CompanyActionSelectorComponent`
|
|
583
|
-
|
|
584
|
-
---
|
|
585
|
-
|
|
586
|
-
## Interfaces
|
|
45
|
+
## 3. Load Permissions
|
|
587
46
|
|
|
588
|
-
|
|
47
|
+
Call `PermissionStateService.loadPermissions()` after login (e.g., in `appInitGuard` or after session restore). It fetches the current user's effective permissions from `POST /iam/permissions/my-permissions` and populates `PermissionValidatorService` from `ng-shared`.
|
|
589
48
|
|
|
590
49
|
```typescript
|
|
591
|
-
|
|
592
|
-
BACKEND = 'backend', // API endpoint permissions (cached by PermissionGuard)
|
|
593
|
-
FRONTEND = 'frontend', // UI feature permissions (returned in my-permissions)
|
|
594
|
-
BOTH = 'both', // Both backend and frontend
|
|
595
|
-
}
|
|
596
|
-
```
|
|
50
|
+
import { PermissionStateService } from '@flusys/ng-iam';
|
|
597
51
|
|
|
598
|
-
|
|
52
|
+
@Component({ ... })
|
|
53
|
+
export class AppShellComponent {
|
|
54
|
+
private readonly permState = inject(PermissionStateService);
|
|
599
55
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
readOnly: boolean;
|
|
603
|
-
name: string;
|
|
604
|
-
description: string | null;
|
|
605
|
-
code: string | null;
|
|
606
|
-
actionType: ActionType;
|
|
607
|
-
permissionLogic: ILogicNode | null; // AND/OR prerequisite rules
|
|
608
|
-
parentId: string | null; // For hierarchy
|
|
609
|
-
serial: number | null; // Display order
|
|
610
|
-
isActive: boolean;
|
|
611
|
-
metadata: Record<string, any> | null;
|
|
612
|
-
}
|
|
613
|
-
```
|
|
614
|
-
|
|
615
|
-
### IActionTreeDto (extends IAction)
|
|
56
|
+
readonly permissions = this.permState.permissions; // Signal<IMyPermissionsResponseDto | null>
|
|
57
|
+
readonly isLoading = this.permState.isLoading; // Signal<boolean>
|
|
616
58
|
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
children: IActionTreeDto[];
|
|
620
|
-
}
|
|
621
|
-
```
|
|
622
|
-
|
|
623
|
-
### ICreateActionDto / IUpdateActionDto
|
|
624
|
-
|
|
625
|
-
```typescript
|
|
626
|
-
interface ICreateActionDto {
|
|
627
|
-
name: string;
|
|
628
|
-
description?: string;
|
|
629
|
-
code?: string;
|
|
630
|
-
actionType?: ActionType;
|
|
631
|
-
permissionLogic?: ILogicNode;
|
|
632
|
-
parentId?: string;
|
|
633
|
-
serial?: number;
|
|
634
|
-
isActive?: boolean;
|
|
635
|
-
metadata?: Record<string, any>;
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
interface IUpdateActionDto extends Partial<ICreateActionDto> {
|
|
639
|
-
id: string;
|
|
640
|
-
}
|
|
641
|
-
```
|
|
642
|
-
|
|
643
|
-
### IRole (extends IBaseEntity)
|
|
644
|
-
|
|
645
|
-
```typescript
|
|
646
|
-
interface IRole extends IBaseEntity {
|
|
647
|
-
readOnly: boolean;
|
|
648
|
-
name: string;
|
|
649
|
-
description: string | null;
|
|
650
|
-
companyId: string | null;
|
|
651
|
-
isActive: boolean;
|
|
652
|
-
serial: number | null;
|
|
653
|
-
metadata: Record<string, any> | null;
|
|
654
|
-
}
|
|
655
|
-
```
|
|
656
|
-
|
|
657
|
-
### ICreateRoleDto / IUpdateRoleDto / IRoleQueryDto
|
|
658
|
-
|
|
659
|
-
```typescript
|
|
660
|
-
interface ICreateRoleDto {
|
|
661
|
-
name: string;
|
|
662
|
-
description?: string;
|
|
663
|
-
companyId?: string;
|
|
664
|
-
isActive?: boolean;
|
|
665
|
-
serial?: number;
|
|
666
|
-
metadata?: Record<string, any>;
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
interface IUpdateRoleDto extends Partial<ICreateRoleDto> {
|
|
670
|
-
id: string;
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
interface IRoleQueryDto {
|
|
674
|
-
companyId?: string;
|
|
675
|
-
isActive?: boolean;
|
|
676
|
-
}
|
|
677
|
-
```
|
|
678
|
-
|
|
679
|
-
### Permission Interfaces
|
|
680
|
-
|
|
681
|
-
```typescript
|
|
682
|
-
type PermissionAction = 'add' | 'remove';
|
|
683
|
-
type PermissionMode = 'rbac' | 'direct' | 'full';
|
|
684
|
-
|
|
685
|
-
interface IPermissionItemDto {
|
|
686
|
-
id: string;
|
|
687
|
-
action: PermissionAction;
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
// Assignment DTOs
|
|
691
|
-
interface IAssignUserActionsDto {
|
|
692
|
-
userId: string;
|
|
693
|
-
companyId?: string;
|
|
694
|
-
branchId?: string; // null = company-wide
|
|
695
|
-
items: IPermissionItemDto[];
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
interface IAssignCompanyActionsDto {
|
|
699
|
-
companyId: string;
|
|
700
|
-
items: IPermissionItemDto[];
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
interface IAssignRoleActionsDto {
|
|
704
|
-
roleId: string;
|
|
705
|
-
items: IPermissionItemDto[];
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
interface IAssignUserRolesDto {
|
|
709
|
-
userId: string;
|
|
710
|
-
companyId?: string;
|
|
711
|
-
branchId?: string;
|
|
712
|
-
items: IPermissionItemDto[];
|
|
713
|
-
}
|
|
714
|
-
```
|
|
715
|
-
|
|
716
|
-
### Response DTOs
|
|
717
|
-
|
|
718
|
-
```typescript
|
|
719
|
-
interface IUserActionResponseDto {
|
|
720
|
-
id: string;
|
|
721
|
-
userId: string;
|
|
722
|
-
actionId: string;
|
|
723
|
-
actionCode: string;
|
|
724
|
-
actionName: string;
|
|
725
|
-
branchId: string | null;
|
|
726
|
-
createdAt: Date;
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
interface IRoleActionResponseDto {
|
|
730
|
-
id: string;
|
|
731
|
-
roleId: string;
|
|
732
|
-
actionId: string;
|
|
733
|
-
actionCode: string;
|
|
734
|
-
actionName: string;
|
|
735
|
-
createdAt: Date;
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
interface ICompanyActionResponseDto {
|
|
739
|
-
id: string;
|
|
740
|
-
companyId: string;
|
|
741
|
-
actionId: string;
|
|
742
|
-
actionCode: string;
|
|
743
|
-
actionName: string;
|
|
744
|
-
createdAt: Date;
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
interface IUserRoleResponseDto {
|
|
748
|
-
id: string;
|
|
749
|
-
userId: string;
|
|
750
|
-
roleId: string;
|
|
751
|
-
roleCode: string;
|
|
752
|
-
roleName: string;
|
|
753
|
-
branchId: string | null; // Always null - roles are NOT branch-scoped
|
|
754
|
-
createdAt: Date;
|
|
755
|
-
}
|
|
756
|
-
```
|
|
757
|
-
|
|
758
|
-
### My Permissions Response
|
|
759
|
-
|
|
760
|
-
```typescript
|
|
761
|
-
interface IMyPermissionsResponseDto {
|
|
762
|
-
frontendActions: Array<{ id: string; code: string; name: string; description: string }>;
|
|
763
|
-
cachedEndpoints: number; // Endpoint count for backend PermissionGuard
|
|
764
|
-
}
|
|
765
|
-
```
|
|
766
|
-
|
|
767
|
-
### Menu & Logic Interfaces
|
|
768
|
-
|
|
769
|
-
```typescript
|
|
770
|
-
interface IMenuAction {
|
|
771
|
-
id: string;
|
|
772
|
-
code: string;
|
|
773
|
-
name: string;
|
|
774
|
-
route: string | null;
|
|
775
|
-
icon: string | null;
|
|
776
|
-
iconType: number | null;
|
|
777
|
-
serial: number | null;
|
|
778
|
-
parentId: string | null;
|
|
779
|
-
children?: IMenuAction[];
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
// ILogicNode is imported from @flusys/ng-shared
|
|
783
|
-
```
|
|
784
|
-
|
|
785
|
-
### Prerequisite Interfaces
|
|
786
|
-
|
|
787
|
-
```typescript
|
|
788
|
-
interface IPrerequisiteActionDto {
|
|
789
|
-
actionId: string;
|
|
790
|
-
actionCode: string;
|
|
791
|
-
actionName: string;
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
interface IPrerequisiteValidationError {
|
|
795
|
-
actionId: string;
|
|
796
|
-
actionCode: string;
|
|
797
|
-
actionName: string;
|
|
798
|
-
requiredActions: IPrerequisiteActionDto[];
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
interface IPermissionOperationResultDto {
|
|
802
|
-
success: boolean;
|
|
803
|
-
added: number;
|
|
804
|
-
removed: number;
|
|
805
|
-
message: string;
|
|
806
|
-
prerequisiteErrors?: IPrerequisiteValidationError[];
|
|
807
|
-
}
|
|
808
|
-
```
|
|
809
|
-
|
|
810
|
-
### Simple Models
|
|
811
|
-
|
|
812
|
-
```typescript
|
|
813
|
-
interface IUser {
|
|
814
|
-
id: string;
|
|
815
|
-
name: string;
|
|
816
|
-
email: string;
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
interface IBranch {
|
|
820
|
-
id: string;
|
|
821
|
-
name: string;
|
|
822
|
-
companyId: string;
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
interface ICompany {
|
|
826
|
-
id: string;
|
|
827
|
-
name: string;
|
|
828
|
-
slug?: string;
|
|
829
|
-
}
|
|
830
|
-
```
|
|
831
|
-
|
|
832
|
-
### Query DTOs
|
|
833
|
-
|
|
834
|
-
```typescript
|
|
835
|
-
interface IGetUserActionsDto {
|
|
836
|
-
branchId?: string;
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
interface IGetRoleActionsDto {}
|
|
840
|
-
|
|
841
|
-
interface IGetUserRolesDto {
|
|
842
|
-
branchId?: string; // Ignored by backend
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
interface IGetMyPermissionsDto {
|
|
846
|
-
parentCodes?: string[];
|
|
847
|
-
}
|
|
848
|
-
```
|
|
849
|
-
|
|
850
|
-
---
|
|
851
|
-
|
|
852
|
-
## Utils & Constants
|
|
853
|
-
|
|
854
|
-
> Utils are **internal** (not exported from the public API). They are used by components and services within the package.
|
|
855
|
-
|
|
856
|
-
### permission-logic.util.ts
|
|
857
|
-
|
|
858
|
-
| Function | Description |
|
|
859
|
-
|----------|-------------|
|
|
860
|
-
| `validateActionPrerequisites(action, selectedIds, allActions)` | Validate prerequisites respecting AND/OR. Returns `{valid, missingActions}` |
|
|
861
|
-
| `extractRequiredActionIds(logic, allActions)` | Extract all action IDs from a logic tree |
|
|
862
|
-
| `describePrerequisites(logic, allActions)` | Human-readable prerequisite description |
|
|
863
|
-
|
|
864
|
-
### tree-utils.ts
|
|
865
|
-
|
|
866
|
-
| Function | Description |
|
|
867
|
-
|----------|-------------|
|
|
868
|
-
| `flattenTree<T>(tree)` | Flatten hierarchical tree into flat array |
|
|
869
|
-
| `buildItemMap<T>(items)` | Build ID-to-item `Map` for O(1) lookup |
|
|
870
|
-
| `buildTreeFromFlat<T>(flatList)` | Build tree from flat list using `parentId` |
|
|
871
|
-
| `convertActionToTreeNode(actions)` | Convert `IActionTreeDto[]` to PrimeNG `TreeNode<IAction>[]` |
|
|
872
|
-
|
|
873
|
-
### Constants
|
|
874
|
-
|
|
875
|
-
```typescript
|
|
876
|
-
export const MAX_DROPDOWN_ITEMS = 100; // Max items for dropdown lists (users, companies, roles, branches)
|
|
877
|
-
```
|
|
878
|
-
|
|
879
|
-
Exported directly as `MAX_DROPDOWN_ITEMS` from the package.
|
|
880
|
-
|
|
881
|
-
---
|
|
882
|
-
|
|
883
|
-
## Backend Integration
|
|
884
|
-
|
|
885
|
-
### Required Endpoints
|
|
886
|
-
|
|
887
|
-
**Action CRUD:**
|
|
888
|
-
|
|
889
|
-
```
|
|
890
|
-
POST /iam/actions/tree # Get hierarchical action tree
|
|
891
|
-
POST /iam/actions/tree-for-permission # Get actions filtered by company whitelist
|
|
892
|
-
POST /iam/actions/get-all # List all actions (flat)
|
|
893
|
-
POST /iam/actions/get/:id # Get action by ID
|
|
894
|
-
POST /iam/actions/insert # Create action
|
|
895
|
-
POST /iam/actions/update # Update action
|
|
896
|
-
POST /iam/actions/delete # Delete action
|
|
897
|
-
```
|
|
898
|
-
|
|
899
|
-
**Role CRUD:**
|
|
900
|
-
|
|
901
|
-
```
|
|
902
|
-
POST /iam/roles/get-all # List roles (with pagination + company filter)
|
|
903
|
-
POST /iam/roles/get/:id # Get role by ID
|
|
904
|
-
POST /iam/roles/insert # Create role
|
|
905
|
-
POST /iam/roles/update # Update role
|
|
906
|
-
POST /iam/roles/delete # Delete role
|
|
907
|
-
```
|
|
908
|
-
|
|
909
|
-
**Permission Assignments:**
|
|
910
|
-
|
|
911
|
-
```
|
|
912
|
-
POST /iam/permissions/my-permissions # Current user's permissions
|
|
913
|
-
POST /iam/permissions/user-actions/assign # Assign/remove user actions
|
|
914
|
-
GET /iam/permissions/user-actions/:id # Get user's direct actions
|
|
915
|
-
POST /iam/permissions/user-roles/assign # Assign/remove user roles
|
|
916
|
-
GET /iam/permissions/user-roles/:id # Get user's roles
|
|
917
|
-
POST /iam/permissions/role-actions/assign # Assign/remove role actions
|
|
918
|
-
GET /iam/permissions/role-actions/:id # Get role's actions
|
|
919
|
-
POST /iam/permissions/company-actions/assign # Assign/remove company whitelist
|
|
920
|
-
GET /iam/permissions/company-actions/:id # Get company's whitelisted actions
|
|
921
|
-
```
|
|
922
|
-
|
|
923
|
-
### My Permissions Response Format
|
|
924
|
-
|
|
925
|
-
```json
|
|
926
|
-
{
|
|
927
|
-
"success": true,
|
|
928
|
-
"data": {
|
|
929
|
-
"frontendActions": [
|
|
930
|
-
{
|
|
931
|
-
"id": "uuid",
|
|
932
|
-
"code": "user.create",
|
|
933
|
-
"name": "Create User",
|
|
934
|
-
"description": "Allows creating new users"
|
|
935
|
-
}
|
|
936
|
-
],
|
|
937
|
-
"cachedEndpoints": 42
|
|
59
|
+
loadOnInit() {
|
|
60
|
+
this.permState.loadPermissions().subscribe();
|
|
938
61
|
}
|
|
939
|
-
}
|
|
940
|
-
```
|
|
941
|
-
|
|
942
|
-
---
|
|
943
|
-
|
|
944
|
-
## Responsive Table Patterns
|
|
945
|
-
|
|
946
|
-
All IAM tables and tree tables use consistent responsive patterns with Tailwind CSS.
|
|
947
|
-
|
|
948
|
-
### Table Container Pattern
|
|
949
|
-
|
|
950
|
-
```html
|
|
951
|
-
<!-- Wrapper enables horizontal scroll on mobile, full-width on desktop -->
|
|
952
|
-
<div class="overflow-x-auto -mx-4 sm:mx-0">
|
|
953
|
-
<p-treeTable
|
|
954
|
-
[value]="treeNodes()"
|
|
955
|
-
dataKey="id"
|
|
956
|
-
styleClass="p-treetable-sm"
|
|
957
|
-
[tableStyle]="{ 'min-width': '50rem' }"
|
|
958
|
-
>
|
|
959
|
-
<!-- ... -->
|
|
960
|
-
</p-treeTable>
|
|
961
|
-
</div>
|
|
962
|
-
```
|
|
963
|
-
|
|
964
|
-
**Key properties:**
|
|
965
|
-
- `overflow-x-auto` - Enables horizontal scroll when content exceeds container
|
|
966
|
-
- `-mx-4 sm:mx-0` - Edge-to-edge on mobile, normal margins on desktop
|
|
967
|
-
- `[tableStyle]="{ 'min-width': '50rem' }"` - Minimum width ensures consistent layout
|
|
968
|
-
|
|
969
|
-
### Responsive Column Visibility
|
|
970
|
-
|
|
971
|
-
```html
|
|
972
|
-
<ng-template #header>
|
|
973
|
-
<tr>
|
|
974
|
-
<th class="w-12"><!-- checkbox --></th>
|
|
975
|
-
<th>Name</th> <!-- Always visible -->
|
|
976
|
-
<th class="hidden sm:table-cell">Code</th> <!-- Hidden on xs -->
|
|
977
|
-
<th>Type</th> <!-- Always visible -->
|
|
978
|
-
<th class="hidden md:table-cell">Status</th> <!-- Hidden until md -->
|
|
979
|
-
<th class="hidden lg:table-cell">Description</th> <!-- Hidden until lg -->
|
|
980
|
-
</tr>
|
|
981
|
-
</ng-template>
|
|
982
|
-
```
|
|
983
|
-
|
|
984
|
-
**Breakpoint classes:**
|
|
985
|
-
| Class | Visibility |
|
|
986
|
-
|-------|------------|
|
|
987
|
-
| (none) | Always visible |
|
|
988
|
-
| `hidden sm:table-cell` | Visible at 640px+ |
|
|
989
|
-
| `hidden md:table-cell` | Visible at 768px+ |
|
|
990
|
-
| `hidden lg:table-cell` | Visible at 1024px+ |
|
|
991
|
-
|
|
992
|
-
### Header Layout Pattern
|
|
993
|
-
|
|
994
|
-
```html
|
|
995
|
-
<div class="flex flex-col md:flex-row justify-between items-start md:items-center gap-3 mb-4">
|
|
996
|
-
<div>
|
|
997
|
-
<h5 class="m-0 mb-1">Section Title</h5>
|
|
998
|
-
<p class="text-sm text-muted-color m-0">{{ count }} items available</p>
|
|
999
|
-
</div>
|
|
1000
|
-
<div class="flex flex-wrap gap-2">
|
|
1001
|
-
<!-- Action buttons -->
|
|
1002
|
-
</div>
|
|
1003
|
-
</div>
|
|
1004
|
-
```
|
|
1005
|
-
|
|
1006
|
-
### Dark Mode Styling
|
|
1007
|
-
|
|
1008
|
-
```css
|
|
1009
|
-
/* Light mode */
|
|
1010
|
-
.validation-warning {
|
|
1011
|
-
background-color: var(--p-yellow-50, #fefce8);
|
|
1012
|
-
border-left: 4px solid var(--p-yellow-500, #eab308);
|
|
1013
|
-
color: var(--p-yellow-700, #a16207);
|
|
1014
|
-
}
|
|
1015
|
-
|
|
1016
|
-
/* Dark mode */
|
|
1017
|
-
:host-context(.p-dark) .validation-warning {
|
|
1018
|
-
background-color: rgba(234, 179, 8, 0.1);
|
|
1019
|
-
color: var(--p-yellow-400, #facc15);
|
|
1020
|
-
}
|
|
1021
|
-
```
|
|
1022
|
-
|
|
1023
|
-
**Color classes (Tailwind):**
|
|
1024
|
-
| Purpose | Class |
|
|
1025
|
-
|---------|-------|
|
|
1026
|
-
| Muted text | `text-muted-color` |
|
|
1027
|
-
| Success | `text-green-500` |
|
|
1028
|
-
| Danger | `text-red-500` |
|
|
1029
|
-
| Warning | `text-orange-500` |
|
|
1030
|
-
| Primary | `text-primary` |
|
|
1031
|
-
|
|
1032
|
-
---
|
|
1033
62
|
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
### Permission Loading
|
|
1037
|
-
|
|
1038
|
-
Load permissions in a guard to block navigation until ready:
|
|
1039
|
-
|
|
1040
|
-
```typescript
|
|
1041
|
-
// appInitGuard
|
|
1042
|
-
if (!permissionState.isLoaded()) {
|
|
1043
|
-
try {
|
|
1044
|
-
await firstValueFrom(permissionState.loadPermissions());
|
|
1045
|
-
} catch (error) {
|
|
1046
|
-
console.error('Failed to load permissions:', error);
|
|
1047
|
-
// Continue with graceful degradation - don't block the app
|
|
63
|
+
isLoaded() {
|
|
64
|
+
return this.permState.isLoaded(); // true when permissions are set in PermissionValidatorService
|
|
1048
65
|
}
|
|
1049
66
|
}
|
|
1050
|
-
return true;
|
|
1051
67
|
```
|
|
1052
68
|
|
|
1053
|
-
|
|
1054
|
-
- Clear permissions on logout
|
|
1055
|
-
- Never store permissions in localStorage (security risk)
|
|
1056
|
-
- Never load permissions in component `ngOnInit` (causes race conditions)
|
|
1057
|
-
|
|
1058
|
-
### Permission Checks
|
|
1059
|
-
|
|
1060
|
-
```typescript
|
|
1061
|
-
// Single action
|
|
1062
|
-
validator.hasAction('user.create');
|
|
69
|
+
## 4. Check Permissions in Templates
|
|
1063
70
|
|
|
1064
|
-
|
|
1065
|
-
validator.hasAllActions(['user.create', 'user.update']);
|
|
1066
|
-
|
|
1067
|
-
// ANY required
|
|
1068
|
-
validator.hasAnyAction(['user.view', 'user.list']);
|
|
1069
|
-
```
|
|
1070
|
-
|
|
1071
|
-
Cache check results in computed signals rather than calling in templates or tight loops.
|
|
1072
|
-
|
|
1073
|
-
### Menu Filtering
|
|
1074
|
-
|
|
1075
|
-
```typescript
|
|
1076
|
-
// Set menu with requiredAction or logicNode
|
|
1077
|
-
this.layoutService.setMenu(menuItems);
|
|
1078
|
-
|
|
1079
|
-
// Set permission checker (layout handles filtering automatically)
|
|
1080
|
-
this.layoutService.setPermissionChecker((actionCode) =>
|
|
1081
|
-
validator.hasAction(actionCode)
|
|
1082
|
-
);
|
|
1083
|
-
```
|
|
1084
|
-
|
|
1085
|
-
Do not filter menus manually - let layout handle it.
|
|
1086
|
-
|
|
1087
|
-
### Action Naming
|
|
1088
|
-
|
|
1089
|
-
Format: `module.operation` or `module.entity.operation`
|
|
1090
|
-
|
|
1091
|
-
```typescript
|
|
1092
|
-
// Good
|
|
1093
|
-
'user.create'
|
|
1094
|
-
'user.update'
|
|
1095
|
-
'report.export'
|
|
1096
|
-
'admin.system.config'
|
|
71
|
+
Once `provideIamProviders()` is registered and `loadPermissions()` has been called, permission directives from `ng-shared` work automatically:
|
|
1097
72
|
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
'
|
|
1101
|
-
'users' // Entity name, not action
|
|
1102
|
-
```
|
|
1103
|
-
|
|
1104
|
-
### LogicNode Pattern
|
|
1105
|
-
|
|
1106
|
-
Complex permission rules using AND/OR operators:
|
|
1107
|
-
|
|
1108
|
-
```typescript
|
|
1109
|
-
// User needs 'user.create' AND ('user.update' OR 'user.delete')
|
|
1110
|
-
const logic: ILogicNode = {
|
|
1111
|
-
type: 'AND',
|
|
1112
|
-
children: [
|
|
1113
|
-
{ type: 'ACTION', value: 'user.create' },
|
|
1114
|
-
{
|
|
1115
|
-
type: 'OR',
|
|
1116
|
-
children: [
|
|
1117
|
-
{ type: 'ACTION', value: 'user.update' },
|
|
1118
|
-
{ type: 'ACTION', value: 'user.delete' },
|
|
1119
|
-
],
|
|
1120
|
-
},
|
|
1121
|
-
],
|
|
1122
|
-
};
|
|
1123
|
-
```
|
|
1124
|
-
|
|
1125
|
-
---
|
|
1126
|
-
|
|
1127
|
-
## Common Issues
|
|
1128
|
-
|
|
1129
|
-
| Problem | Cause | Solution |
|
|
1130
|
-
|---------|-------|----------|
|
|
1131
|
-
| `permissionState.isLoaded()` returns false | IAM not enabled or endpoint failing | Check `services.iam.enabled: true` in environment, verify `POST /iam/permissions/my-permissions` works |
|
|
1132
|
-
| Empty menu after login | Permissions not loaded or no matching actions | Check `permissionState.isLoaded()`, verify user has frontend actions, check `requiredAction` matches action codes |
|
|
1133
|
-
| Menu items not filtered | Permission checker not set | Call `layoutService.setPermissionChecker()` after loading permissions |
|
|
1134
|
-
| 401 on IAM endpoints with external auth | Auth token not forwarded | Ensure your auth interceptor adds `Authorization` header to IAM requests |
|
|
1135
|
-
| Provider not configured error | Missing DI registration | Add required providers to `app.config.ts` (USER_PROVIDER, LAYOUT_AUTH_STATE) |
|
|
1136
|
-
| Empty user/company dropdowns | API response format mismatch | Ensure adapters return `IListResponse<T>` format with `data` array |
|
|
1137
|
-
| TreeTable has excessive whitespace | Using `scrollHeight="flex"` without fixed-height parent | Remove `scrollHeight="flex"` and `[scrollable]="true"`, use `overflow-x-auto` container instead |
|
|
1138
|
-
| Table not horizontally scrollable on mobile | Missing responsive wrapper | Add `<div class="overflow-x-auto -mx-4 sm:mx-0">` around table |
|
|
1139
|
-
| Columns visible on mobile causing overflow | Missing responsive column classes | Add `hidden sm:table-cell` or `hidden md:table-cell` to non-essential columns |
|
|
1140
|
-
|
|
1141
|
-
---
|
|
1142
|
-
|
|
1143
|
-
## Configuration
|
|
1144
|
-
|
|
1145
|
-
### Frontend Environment
|
|
1146
|
-
|
|
1147
|
-
```typescript
|
|
1148
|
-
// environment.ts
|
|
1149
|
-
services: {
|
|
1150
|
-
iam: { baseUrl: 'http://localhost:2002/iam', enabled: true },
|
|
1151
|
-
}
|
|
1152
|
-
```
|
|
1153
|
-
|
|
1154
|
-
### Backend Environment
|
|
1155
|
-
|
|
1156
|
-
```bash
|
|
1157
|
-
# .env
|
|
1158
|
-
IAM_PERMISSION_MODE=FULL # DIRECT, RBAC, or FULL
|
|
1159
|
-
```
|
|
1160
|
-
|
|
1161
|
-
| Scenario | services.iam.enabled | Result |
|
|
1162
|
-
|----------|---------------------|--------|
|
|
1163
|
-
| Full IAM | `true` | Permission-based menu |
|
|
1164
|
-
| IAM Disabled | `false` | Static menu (all items visible) |
|
|
1165
|
-
| Service Down | `true` (API fails) | Graceful fallback to static menu |
|
|
1166
|
-
|
|
1167
|
-
---
|
|
1168
|
-
|
|
1169
|
-
## Provider Functions
|
|
1170
|
-
|
|
1171
|
-
### provideIamProviders()
|
|
1172
|
-
|
|
1173
|
-
Registers provider adapters that enable other packages (ng-auth) to access IAM functionality without direct dependencies.
|
|
1174
|
-
|
|
1175
|
-
```typescript
|
|
1176
|
-
import { provideIamProviders } from '@flusys/ng-iam';
|
|
1177
|
-
|
|
1178
|
-
// app.config.ts
|
|
1179
|
-
export const appConfig: ApplicationConfig = {
|
|
1180
|
-
providers: [
|
|
1181
|
-
...provideIamProviders(), // Register IAM providers
|
|
1182
|
-
],
|
|
1183
|
-
};
|
|
1184
|
-
```
|
|
1185
|
-
|
|
1186
|
-
**Registers:**
|
|
1187
|
-
|
|
1188
|
-
| Token | Adapter | Purpose |
|
|
1189
|
-
|-------|---------|---------|
|
|
1190
|
-
| `PROFILE_PERMISSION_PROVIDER` | `ProfilePermissionProviderAdapter` | User roles/actions for profile page |
|
|
1191
|
-
|
|
1192
|
-
### ProfilePermissionProviderAdapter
|
|
1193
|
-
|
|
1194
|
-
Implements `IProfilePermissionProvider` from ng-shared. Enables ng-auth's profile page to display user permissions.
|
|
73
|
+
```html
|
|
74
|
+
<!-- show element if user has the action code -->
|
|
75
|
+
<button *hasPermission="'product.update'">Edit</button>
|
|
1195
76
|
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
getUserRoles(userId: string, branchId?: string): Observable<ISingleResponse<IProfileRoleInfo[]>>;
|
|
1199
|
-
getUserActions(userId: string, branchId?: string): Observable<ISingleResponse<IProfileActionInfo[]>>;
|
|
1200
|
-
}
|
|
77
|
+
<!-- show element if user has any of these action codes -->
|
|
78
|
+
<div *hasPermission="['admin.manage', 'product.delete']">Admin area</div>
|
|
1201
79
|
```
|
|
1202
80
|
|
|
1203
|
-
|
|
81
|
+
Use `permissionGuard` from `ng-shared` to protect routes:
|
|
1204
82
|
|
|
1205
83
|
```typescript
|
|
1206
|
-
|
|
1207
|
-
private readonly permissionProvider = inject(PROFILE_PERMISSION_PROVIDER, { optional: true });
|
|
84
|
+
import { permissionGuard } from '@flusys/ng-shared';
|
|
1208
85
|
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
});
|
|
86
|
+
{
|
|
87
|
+
path: 'admin',
|
|
88
|
+
canActivate: [permissionGuard('admin.manage')],
|
|
89
|
+
loadComponent: () => import('./admin.component'),
|
|
1214
90
|
}
|
|
1215
91
|
```
|
|
1216
92
|
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
## See Also
|
|
1220
|
-
|
|
1221
|
-
- **[CORE-GUIDE.md](./CORE-GUIDE.md)** - Configuration system (APP_CONFIG)
|
|
1222
|
-
- **[AUTH-GUIDE.md](./AUTH-GUIDE.md)** - Authentication and provider adapters
|
|
1223
|
-
- **[SHARED-GUIDE.md](./SHARED-GUIDE.md)** - Provider interface definitions
|
|
1224
|
-
- **[LAYOUT-GUIDE.md](./LAYOUT-GUIDE.md)** - Menu system and filtering
|
|
1225
|
-
|
|
1226
|
-
---
|
|
93
|
+
## License
|
|
1227
94
|
|
|
1228
|
-
|
|
1229
|
-
**Version:** 1.1.0
|
|
1230
|
-
**Angular Version:** 21
|
|
95
|
+
MIT © FLUSYS
|