@flusys/ng-iam 4.0.2 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +301 -458
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,608 +1,451 @@
|
|
|
1
|
-
# @flusys/ng-iam
|
|
1
|
+
# @flusys/ng-iam
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
`@flusys/ng-iam` provides Identity and Access Management (IAM) for FLUSYS applications. It includes permission state management, role-based access control (RBAC), action management with AND/OR prerequisite logic, and dynamic menu filtering based on user permissions.
|
|
6
|
-
|
|
7
|
-
**Key Principle:** ng-iam is **100% independent** from ng-auth through the **Provider Interface Pattern**.
|
|
3
|
+
> Identity and Access Management UI library for the FLUSYS Angular platform — role management, permission assignment, permission evaluation, and logic builder.
|
|
8
4
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
| Dependencies | ng-core, ng-shared, ng-layout (IMenuItem only) |
|
|
14
|
-
| CSS Framework | Tailwind CSS (not PrimeFlex) |
|
|
15
|
-
| Build | `npm run build:ng-iam` |
|
|
5
|
+
[](https://www.npmjs.com/package/@flusys/ng-iam)
|
|
6
|
+
[](https://angular.io)
|
|
7
|
+
[](https://www.typescriptlang.org)
|
|
8
|
+
[](LICENSE)
|
|
16
9
|
|
|
17
10
|
---
|
|
18
11
|
|
|
19
|
-
##
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
12
|
+
## Table of Contents
|
|
13
|
+
|
|
14
|
+
- [Overview](#overview)
|
|
15
|
+
- [Features](#features)
|
|
16
|
+
- [Compatibility](#compatibility)
|
|
17
|
+
- [Installation](#installation)
|
|
18
|
+
- [Quick Start](#quick-start)
|
|
19
|
+
- [Permission Modes](#permission-modes)
|
|
20
|
+
- [RBAC Mode](#rbac-mode)
|
|
21
|
+
- [DIRECT Mode](#direct-mode)
|
|
22
|
+
- [FULL Mode](#full-mode)
|
|
23
|
+
- [Module Registration](#module-registration)
|
|
24
|
+
- [Permission State Service](#permission-state-service)
|
|
25
|
+
- [IAM API Services](#iam-api-services)
|
|
26
|
+
- [Components](#components)
|
|
27
|
+
- [PermissionPageComponent](#permissionpagecomponent)
|
|
28
|
+
- [LogicBuilderComponent](#logicbuildercomponent)
|
|
29
|
+
- [Integration](#integration)
|
|
30
|
+
- [API Endpoints](#api-endpoints)
|
|
31
|
+
- [Troubleshooting](#troubleshooting)
|
|
32
|
+
- [License](#license)
|
|
30
33
|
|
|
31
34
|
---
|
|
32
35
|
|
|
33
|
-
##
|
|
36
|
+
## Overview
|
|
34
37
|
|
|
35
|
-
|
|
36
|
-
|-------|--------|---------|-----------|
|
|
37
|
-
| `USER_PROVIDER` | ng-shared | User list for role/action assignment | Yes |
|
|
38
|
-
| `LAYOUT_AUTH_STATE` | ng-layout | Current company/branch context | Yes (if company enabled) |
|
|
39
|
-
| `COMPANY_API_PROVIDER` | ng-shared | Company list for whitelisting | Optional |
|
|
40
|
-
| `USER_PERMISSION_PROVIDER` | ng-shared | User branch permissions | Optional |
|
|
38
|
+
`@flusys/ng-iam` provides the complete Identity and Access Management UI for FLUSYS applications. It supports three independent permission modes (RBAC, DIRECT, FULL) and integrates with the backend `nestjs-iam` module.
|
|
41
39
|
|
|
42
|
-
|
|
43
|
-
// Provider usage
|
|
44
|
-
const companyId = inject(LAYOUT_AUTH_STATE).currentCompanyInfo()?.id;
|
|
45
|
-
const response = await inject(USER_PERMISSION_PROVIDER).getUserBranchPermissions(userId).toPromise();
|
|
46
|
-
```
|
|
40
|
+
The package uses the Provider Interface Pattern — it never imports from `ng-auth` directly. Instead it injects `USER_PROVIDER` and `COMPANY_PROVIDER` from `ng-shared` to get current user and company context.
|
|
47
41
|
|
|
48
42
|
---
|
|
49
43
|
|
|
50
|
-
##
|
|
51
|
-
|
|
52
|
-
### With FLUSYS ng-auth (Recommended)
|
|
53
|
-
|
|
54
|
-
```typescript
|
|
55
|
-
import { provideAuthProviders } from '@flusys/ng-auth';
|
|
56
|
-
export const appConfig: ApplicationConfig = {
|
|
57
|
-
providers: [provideAuthProviders()], // Registers all adapters
|
|
58
|
-
};
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
### With Custom Auth System
|
|
62
|
-
|
|
63
|
-
```typescript
|
|
64
|
-
providers: [
|
|
65
|
-
{ provide: USER_PROVIDER, useClass: YourUserProvider },
|
|
66
|
-
{ provide: LAYOUT_AUTH_STATE, useClass: YourCompanyContextProvider },
|
|
67
|
-
{ provide: COMPANY_API_PROVIDER, useClass: YourCompanyApiProvider }, // Optional
|
|
68
|
-
{ provide: USER_PERMISSION_PROVIDER, useClass: YourUserPermissionProvider }, // Optional
|
|
69
|
-
]
|
|
70
|
-
```
|
|
44
|
+
## Features
|
|
71
45
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
46
|
+
- ✅ Three permission modes: RBAC, DIRECT, FULL
|
|
47
|
+
- ✅ `PermissionStateService` — signal-based permission state with 1-hour cache
|
|
48
|
+
- ✅ Role management UI (create, assign users, set permissions)
|
|
49
|
+
- ✅ Direct permission assignment UI (assign permissions directly to users)
|
|
50
|
+
- ✅ `LogicBuilderComponent` — visual AND/OR/nested logic builder for complex permissions
|
|
51
|
+
- ✅ Permission cache auto-invalidation on role/permission change
|
|
52
|
+
- ✅ `provideIamProviders()` — registers permission evaluator with `ng-shared`
|
|
53
|
+
- ✅ Lazy-loaded IAM pages
|
|
77
54
|
|
|
78
55
|
---
|
|
79
56
|
|
|
80
|
-
##
|
|
81
|
-
|
|
82
|
-
Configured via `APP_CONFIG.iam.permissionMode`:
|
|
57
|
+
## Compatibility
|
|
83
58
|
|
|
84
|
-
|
|
|
85
|
-
|
|
86
|
-
|
|
|
87
|
-
|
|
|
88
|
-
|
|
|
89
|
-
|
|
90
|
-
When company feature is enabled, a **Company-Actions** tab is added for whitelisting.
|
|
59
|
+
| Package | Version |
|
|
60
|
+
|---------|---------|
|
|
61
|
+
| Angular | 21+ |
|
|
62
|
+
| @flusys/ng-core | 4.x |
|
|
63
|
+
| @flusys/ng-shared | 4.x |
|
|
91
64
|
|
|
92
65
|
---
|
|
93
66
|
|
|
94
|
-
##
|
|
95
|
-
|
|
96
|
-
**Export:** `IAM_ROUTES` from `@flusys/ng-iam`
|
|
67
|
+
## Installation
|
|
97
68
|
|
|
69
|
+
```bash
|
|
70
|
+
npm install @flusys/ng-iam @flusys/ng-core @flusys/ng-shared
|
|
98
71
|
```
|
|
99
|
-
/iam → IamContainerComponent (tabbed navigation)
|
|
100
|
-
/actions → ActionListPageComponent (hierarchical tree)
|
|
101
|
-
/actions/new → ActionFormPageComponent (create)
|
|
102
|
-
/actions/:id → ActionFormPageComponent (edit)
|
|
103
|
-
/roles → RoleListPageComponent (paginated table)
|
|
104
|
-
/roles/new → RoleFormPageComponent (create)
|
|
105
|
-
/roles/:id → RoleFormPageComponent (edit)
|
|
106
|
-
/permissions → PermissionPageComponent (tabbed assignment UI)
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
| Route | Guard | Permission |
|
|
110
|
-
|-------|-------|------------|
|
|
111
|
-
| `/actions` | permissionGuard | `ACTION_PERMISSIONS.READ` |
|
|
112
|
-
| `/roles` | permissionGuard | `ROLE_PERMISSIONS.READ` |
|
|
113
|
-
| `/permissions` | anyPermissionGuard | Multiple READ permissions |
|
|
114
72
|
|
|
115
73
|
---
|
|
116
74
|
|
|
117
|
-
##
|
|
118
|
-
|
|
119
|
-
### PermissionStateService
|
|
120
|
-
|
|
121
|
-
**File:** `services/permission-state.service.ts` | Provided at root level
|
|
122
|
-
|
|
123
|
-
Manages current user's permissions with signal-based state.
|
|
75
|
+
## Quick Start
|
|
124
76
|
|
|
125
|
-
|
|
126
|
-
|-----|------|-------------|
|
|
127
|
-
| `permissions` | `Signal<IMyPermissionsResponseDto \| null>` | Current user permissions (readonly) |
|
|
128
|
-
| `isLoading` | `Signal<boolean>` | Loading state (readonly) |
|
|
129
|
-
| `loadPermissions(dto?)` | `Observable<void>` | Load from `/iam/permissions/my-permissions` |
|
|
130
|
-
| `isLoaded()` | `boolean` | Check if permissions loaded |
|
|
131
|
-
|
|
132
|
-
Permission checking delegated to `PermissionValidatorService` from ng-shared.
|
|
77
|
+
### 1. Enable IAM in Config
|
|
133
78
|
|
|
134
79
|
```typescript
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
80
|
+
// environments/environment.ts
|
|
81
|
+
export const environment = {
|
|
82
|
+
permissionMode: 'FULL', // 'RBAC' | 'DIRECT' | 'FULL'
|
|
83
|
+
services: {
|
|
84
|
+
auth: { enabled: true },
|
|
85
|
+
iam: { enabled: true },
|
|
86
|
+
},
|
|
87
|
+
};
|
|
138
88
|
```
|
|
139
89
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
### ActionApiService
|
|
143
|
-
|
|
144
|
-
**File:** `services/action-api.service.ts` | Extends `ApiResourceService<IUpdateActionDto, IAction>`
|
|
145
|
-
|
|
146
|
-
**Resource:** `iam/actions`
|
|
147
|
-
|
|
148
|
-
| Method | Endpoint | Description |
|
|
149
|
-
|--------|----------|-------------|
|
|
150
|
-
| `getTree(search?, isActive?, withDeleted?)` | `POST /iam/actions/tree` | Hierarchical action tree |
|
|
151
|
-
| `getActionsForPermission()` | `POST /iam/actions/tree-for-permission` | Actions filtered by company whitelist |
|
|
90
|
+
### 2. Register IAM Providers
|
|
152
91
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
92
|
+
```typescript
|
|
93
|
+
// app.config.ts
|
|
94
|
+
import { provideIamProviders } from '@flusys/ng-iam';
|
|
156
95
|
|
|
157
|
-
|
|
96
|
+
export const appConfig: ApplicationConfig = {
|
|
97
|
+
providers: [
|
|
98
|
+
...provideIamProviders(),
|
|
99
|
+
// Provides: PERMISSION_VALIDATOR (used by HasPermissionDirective, PermissionGuard)
|
|
100
|
+
],
|
|
101
|
+
};
|
|
102
|
+
```
|
|
158
103
|
|
|
159
|
-
|
|
104
|
+
### 3. Add IAM Routes
|
|
160
105
|
|
|
161
|
-
|
|
106
|
+
```typescript
|
|
107
|
+
// app.routes.ts
|
|
108
|
+
import { IAM_ROUTES } from '@flusys/ng-iam';
|
|
109
|
+
|
|
110
|
+
export const routes: Routes = [
|
|
111
|
+
{
|
|
112
|
+
path: 'iam',
|
|
113
|
+
loadChildren: () => IAM_ROUTES,
|
|
114
|
+
},
|
|
115
|
+
];
|
|
116
|
+
```
|
|
162
117
|
|
|
163
118
|
---
|
|
164
119
|
|
|
165
|
-
|
|
120
|
+
## Permission Modes
|
|
166
121
|
|
|
167
|
-
|
|
122
|
+
### RBAC Mode
|
|
168
123
|
|
|
169
|
-
|
|
124
|
+
Role-Based Access Control. Users are assigned roles; roles hold permissions.
|
|
170
125
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
| `getUserActions(userId, query?)` | `POST /iam/permissions/get-user-actions` | Get user's direct actions |
|
|
175
|
-
| `assignUserRoles(data)` | `POST /iam/permissions/user-roles/assign` | Assign/remove roles for user |
|
|
176
|
-
| `getUserRoles(userId, query?)` | `POST /iam/permissions/get-user-roles` | Get user's roles |
|
|
177
|
-
| `assignRoleActions(data)` | `POST /iam/permissions/role-actions/assign` | Assign/remove actions for role |
|
|
178
|
-
| `getRoleActions(roleId, query?)` | `POST /iam/permissions/get-role-actions` | Get role's actions |
|
|
179
|
-
| `assignCompanyActions(data)` | `POST /iam/permissions/company-actions/assign` | Assign/remove company whitelist |
|
|
180
|
-
| `getCompanyActions(companyId)` | `POST /iam/permissions/get-company-actions` | Get company's whitelisted actions |
|
|
126
|
+
```
|
|
127
|
+
User ──assigned──► Role ──has──► Permissions
|
|
128
|
+
```
|
|
181
129
|
|
|
182
|
-
|
|
130
|
+
**Active controllers:**
|
|
131
|
+
- Role management (create roles, assign permissions to roles)
|
|
132
|
+
- Role assignment (assign roles to users)
|
|
183
133
|
|
|
184
|
-
|
|
134
|
+
**Config:**
|
|
135
|
+
```typescript
|
|
136
|
+
{ permissionMode: 'RBAC' }
|
|
137
|
+
```
|
|
185
138
|
|
|
186
|
-
|
|
139
|
+
### DIRECT Mode
|
|
187
140
|
|
|
188
|
-
|
|
141
|
+
Permissions assigned directly to users. No roles.
|
|
189
142
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
| `handleUncheck(action, selection, allActions, onUpdate)` | Handle deselection with dependency detection |
|
|
194
|
-
| `hasUnmetPrerequisites(action, selection, allActions)` | Check if action has unmet prerequisites |
|
|
195
|
-
| `showValidationErrorDialog(invalid, selection, allActions, onUpdate)` | Pre-save validation with auto-fix |
|
|
196
|
-
| `getPrerequisiteTooltip(action, selection, allActions)` | Get tooltip text with prerequisite info |
|
|
143
|
+
```
|
|
144
|
+
User ──directly assigned──► Permissions
|
|
145
|
+
```
|
|
197
146
|
|
|
198
|
-
|
|
147
|
+
**Active controllers:**
|
|
148
|
+
- User permission assignment
|
|
199
149
|
|
|
200
|
-
|
|
150
|
+
**Config:**
|
|
151
|
+
```typescript
|
|
152
|
+
{ permissionMode: 'DIRECT' }
|
|
153
|
+
```
|
|
201
154
|
|
|
202
|
-
|
|
155
|
+
### FULL Mode
|
|
203
156
|
|
|
204
|
-
|
|
157
|
+
Both RBAC and DIRECT combined. User gets union of role permissions + direct permissions.
|
|
205
158
|
|
|
206
|
-
|
|
159
|
+
```
|
|
160
|
+
User ──assigned──► Role ──has──► Permissions
|
|
161
|
+
└──directly──────────────► Permissions
|
|
162
|
+
```
|
|
207
163
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
| `logicChange` (output) | `ILogicNode \| null` | Emitted when logic changes |
|
|
164
|
+
**Config:**
|
|
165
|
+
```typescript
|
|
166
|
+
{ permissionMode: 'FULL' }
|
|
167
|
+
```
|
|
213
168
|
|
|
214
169
|
---
|
|
215
170
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
All selector components share a common pattern with these signals:
|
|
171
|
+
## Module Registration
|
|
219
172
|
|
|
220
|
-
|
|
221
|
-
|--------|-------------|
|
|
222
|
-
| `selectedXxxId` | Selected entity ID (user/role/company) |
|
|
223
|
-
| `treeNodes` / `items` | Available items (TreeNode[] or flat array) |
|
|
224
|
-
| `flatActions` | Flattened action list (for action selectors) |
|
|
225
|
-
| `selectedIds` | Currently selected IDs (Set) |
|
|
226
|
-
| `originalSelectedIds` | Original state for change tracking |
|
|
227
|
-
| `isLoading` / `isSaving` | Loading/saving states |
|
|
228
|
-
| `pendingAdd` / `pendingRemove` | Computed: items to add/remove |
|
|
229
|
-
| `hasChanges` | Computed: whether there are pending changes |
|
|
173
|
+
### `provideIamProviders()`
|
|
230
174
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
---
|
|
234
|
-
|
|
235
|
-
### UserActionSelectorComponent
|
|
175
|
+
```typescript
|
|
176
|
+
import { provideIamProviders } from '@flusys/ng-iam';
|
|
236
177
|
|
|
237
|
-
|
|
178
|
+
// app.config.ts
|
|
179
|
+
providers: [
|
|
180
|
+
...provideIamProviders(),
|
|
181
|
+
]
|
|
182
|
+
```
|
|
238
183
|
|
|
239
|
-
|
|
240
|
-
-
|
|
241
|
-
- Hierarchical TreeTable with checkboxes
|
|
242
|
-
- Prerequisite validation with tooltips
|
|
184
|
+
Registers:
|
|
185
|
+
- `PERMISSION_VALIDATOR` — implementation of `IPermissionValidator` used by `HasPermissionDirective` and `PermissionGuard` from `ng-shared`
|
|
243
186
|
|
|
244
187
|
---
|
|
245
188
|
|
|
246
|
-
|
|
189
|
+
## Permission State Service
|
|
247
190
|
|
|
248
|
-
|
|
191
|
+
`PermissionStateService` is the signal-based store for the current user's loaded permissions. It caches permissions for **1 hour** and auto-invalidates when roles or direct permissions change.
|
|
249
192
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
193
|
+
```typescript
|
|
194
|
+
import { PermissionStateService } from '@flusys/ng-iam';
|
|
195
|
+
|
|
196
|
+
@Component({ ... })
|
|
197
|
+
export class MyComponent {
|
|
198
|
+
private permState = inject(PermissionStateService);
|
|
199
|
+
|
|
200
|
+
// Signals
|
|
201
|
+
permissions = this.permState.permissions; // Signal<string[]>
|
|
202
|
+
isLoaded = this.permState.isLoaded; // Signal<boolean>
|
|
203
|
+
isLoading = this.permState.isLoading; // Signal<boolean>
|
|
204
|
+
|
|
205
|
+
// Check permission reactively
|
|
206
|
+
canEdit = computed(() =>
|
|
207
|
+
this.permState.hasPermission('product:update')
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
canDelete = computed(() =>
|
|
211
|
+
this.permState.hasAnyPermission(['product:delete', 'admin:manage'])
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
// Load permissions (called by appInitGuard after login)
|
|
215
|
+
loadPermissions(): void {
|
|
216
|
+
this.permState.load();
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Invalidate cache (call after role/permission changes)
|
|
220
|
+
invalidate(): void {
|
|
221
|
+
this.permState.invalidate();
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
```
|
|
257
225
|
|
|
258
|
-
**
|
|
226
|
+
**PermissionStateService API:**
|
|
259
227
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
228
|
+
| Member | Type | Description |
|
|
229
|
+
|--------|------|-------------|
|
|
230
|
+
| `permissions` | `Signal<string[]>` | Current user's permission list |
|
|
231
|
+
| `isLoaded` | `Signal<boolean>` | True when permissions are loaded |
|
|
232
|
+
| `isLoading` | `Signal<boolean>` | True when loading is in progress |
|
|
233
|
+
| `hasPermission(key)` | `boolean` | Single permission check |
|
|
234
|
+
| `hasAllPermissions(keys[])` | `boolean` | AND logic — all must match |
|
|
235
|
+
| `hasAnyPermission(keys[])` | `boolean` | OR logic — at least one must match |
|
|
236
|
+
| `load()` | `Observable<void>` | Load permissions from backend |
|
|
237
|
+
| `invalidate()` | `void` | Clear cache and force reload |
|
|
263
238
|
|
|
264
|
-
|
|
239
|
+
### Permission Key Format
|
|
265
240
|
|
|
266
|
-
|
|
241
|
+
```
|
|
242
|
+
resource:action
|
|
243
|
+
```
|
|
267
244
|
|
|
268
|
-
|
|
245
|
+
Examples:
|
|
246
|
+
```
|
|
247
|
+
product:read
|
|
248
|
+
product:create
|
|
249
|
+
product:update
|
|
250
|
+
product:delete
|
|
251
|
+
admin:manage
|
|
252
|
+
user:read
|
|
253
|
+
report:export
|
|
254
|
+
```
|
|
269
255
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
256
|
+
Wildcard support:
|
|
257
|
+
```
|
|
258
|
+
* — all permissions
|
|
259
|
+
product:* — all product actions
|
|
260
|
+
```
|
|
273
261
|
|
|
274
262
|
---
|
|
275
263
|
|
|
276
|
-
##
|
|
264
|
+
## IAM API Services
|
|
277
265
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
| Component | Selector | Features |
|
|
281
|
-
|-----------|----------|----------|
|
|
282
|
-
| `IamContainerComponent` | `lib-iam-container` | Tabbed navigation, responsive tabs |
|
|
283
|
-
| `ActionListPageComponent` | `lib-action-list-page` | TreeTable, CRUD, read-only indicators |
|
|
284
|
-
| `ActionFormPageComponent` | `lib-action-form-page` | Signal forms, LogicBuilder integration |
|
|
285
|
-
| `RoleListPageComponent` | `lib-role-list-page` | Paginated table (10/25/50), CRUD |
|
|
286
|
-
| `RoleFormPageComponent` | `lib-role-form-page` | Signal forms, company auto-assignment |
|
|
287
|
-
| `PermissionPageComponent` | `lib-permission-page` | Dynamic tabs based on permission mode |
|
|
288
|
-
|
|
289
|
-
---
|
|
290
|
-
|
|
291
|
-
## Interfaces
|
|
266
|
+
### ActionApiService
|
|
292
267
|
|
|
293
|
-
|
|
268
|
+
Manages available permission actions (resource:action pairs):
|
|
294
269
|
|
|
295
270
|
```typescript
|
|
296
|
-
|
|
297
|
-
type PermissionAction = 'add' | 'remove';
|
|
298
|
-
type PermissionMode = 'rbac' | 'direct' | 'full';
|
|
299
|
-
```
|
|
271
|
+
import { ActionApiService } from '@flusys/ng-iam';
|
|
300
272
|
|
|
301
|
-
|
|
273
|
+
@Injectable({ ... })
|
|
274
|
+
export class MyService {
|
|
275
|
+
private actionApi = inject(ActionApiService);
|
|
302
276
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
name: string;
|
|
307
|
-
description: string | null;
|
|
308
|
-
code: string | null;
|
|
309
|
-
actionType: ActionType;
|
|
310
|
-
permissionLogic: ILogicNode | null; // AND/OR prerequisite rules
|
|
311
|
-
parentId: string | null;
|
|
312
|
-
serial: number | null;
|
|
313
|
-
isActive: boolean;
|
|
314
|
-
metadata: Record<string, unknown> | null;
|
|
277
|
+
getActions(): Observable<IListResponse<IAction>> {
|
|
278
|
+
return this.actionApi.getAll({ pageSize: 100 });
|
|
279
|
+
}
|
|
315
280
|
}
|
|
316
|
-
interface IActionTreeDto extends IAction { children: IActionTreeDto[]; }
|
|
317
281
|
```
|
|
318
282
|
|
|
319
|
-
###
|
|
283
|
+
### RoleApiService
|
|
284
|
+
|
|
285
|
+
Manages roles and their permission assignments:
|
|
320
286
|
|
|
321
287
|
```typescript
|
|
322
|
-
|
|
323
|
-
readOnly: boolean;
|
|
324
|
-
name: string;
|
|
325
|
-
description: string | null;
|
|
326
|
-
companyId: string | null;
|
|
327
|
-
isActive: boolean;
|
|
328
|
-
serial: number | null;
|
|
329
|
-
metadata: Record<string, unknown> | null;
|
|
330
|
-
}
|
|
331
|
-
```
|
|
288
|
+
import { RoleApiService } from '@flusys/ng-iam';
|
|
332
289
|
|
|
333
|
-
|
|
290
|
+
@Injectable({ ... })
|
|
291
|
+
export class MyService {
|
|
292
|
+
private roleApi = inject(RoleApiService);
|
|
334
293
|
|
|
335
|
-
|
|
336
|
-
|
|
294
|
+
createRole(name: string, permissions: string[]): Observable<ISingleResponse<IRole>> {
|
|
295
|
+
return this.roleApi.insert({ name, permissions });
|
|
296
|
+
}
|
|
337
297
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
}
|
|
341
|
-
interface IAssignRoleActionsDto { roleId: string; items: IPermissionItemDto[]; }
|
|
342
|
-
interface IAssignCompanyActionsDto { companyId: string; items: IPermissionItemDto[]; }
|
|
343
|
-
interface IAssignUserRolesDto {
|
|
344
|
-
userId: string; companyId?: string; branchId?: string; items: IPermissionItemDto[];
|
|
298
|
+
assignRoleToUser(userId: string, roleId: string): Observable<IMessageResponse> {
|
|
299
|
+
return this.roleApi.assignToUser({ userId, roleId });
|
|
300
|
+
}
|
|
345
301
|
}
|
|
346
302
|
```
|
|
347
303
|
|
|
348
|
-
###
|
|
304
|
+
### UserPermissionApiService
|
|
349
305
|
|
|
350
|
-
|
|
306
|
+
Manages direct permission assignments to users:
|
|
351
307
|
|
|
352
308
|
```typescript
|
|
353
|
-
|
|
354
|
-
frontendActions: Array<{ id: string; code: string; name: string; description: string }>;
|
|
355
|
-
cachedEndpoints: number; // Endpoint count for backend PermissionGuard
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
interface IPermissionOperationResultDto {
|
|
359
|
-
success: boolean; added: number; removed: number; message: string;
|
|
360
|
-
prerequisiteErrors?: IPrerequisiteValidationError[];
|
|
361
|
-
}
|
|
362
|
-
```
|
|
309
|
+
import { UserPermissionApiService } from '@flusys/ng-iam';
|
|
363
310
|
|
|
364
|
-
|
|
311
|
+
@Injectable({ ... })
|
|
312
|
+
export class MyService {
|
|
313
|
+
private userPermApi = inject(UserPermissionApiService);
|
|
365
314
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
315
|
+
assignPermissions(userId: string, permissions: string[]): Observable<IMessageResponse> {
|
|
316
|
+
return this.userPermApi.assign({ userId, permissions });
|
|
317
|
+
}
|
|
318
|
+
}
|
|
370
319
|
```
|
|
371
320
|
|
|
372
321
|
---
|
|
373
322
|
|
|
374
|
-
##
|
|
375
|
-
|
|
376
|
-
Utils are **internal** (not exported from public API).
|
|
377
|
-
|
|
378
|
-
### permission-logic.util.ts
|
|
379
|
-
|
|
380
|
-
| Function | Description |
|
|
381
|
-
|----------|-------------|
|
|
382
|
-
| `validateActionPrerequisites(action, selectedIds, allActions)` | Validate prerequisites respecting AND/OR |
|
|
383
|
-
| `extractRequiredActionIds(logic, allActions)` | Extract all action IDs from logic tree |
|
|
323
|
+
## Components
|
|
384
324
|
|
|
385
|
-
###
|
|
325
|
+
### PermissionPageComponent
|
|
386
326
|
|
|
387
|
-
|
|
388
|
-
|----------|-------------|
|
|
389
|
-
| `flattenTree<T>(tree)` | Flatten hierarchical tree into flat array |
|
|
390
|
-
| `buildTreeFromFlat<T>(flatList)` | Build tree from flat list using `parentId` |
|
|
391
|
-
| `convertActionToTreeNode(actions)` | Convert to PrimeNG `TreeNode<IAction>[]` |
|
|
327
|
+
Full-page IAM management UI. Renders different panels based on `permissionMode`:
|
|
392
328
|
|
|
393
|
-
|
|
329
|
+
| Mode | Panels Shown |
|
|
330
|
+
|------|-------------|
|
|
331
|
+
| `RBAC` | Roles list, Role editor, User-role assignment |
|
|
332
|
+
| `DIRECT` | User permission editor |
|
|
333
|
+
| `FULL` | All RBAC panels + User permission editor |
|
|
394
334
|
|
|
395
335
|
```typescript
|
|
396
|
-
|
|
336
|
+
// In routes:
|
|
337
|
+
{
|
|
338
|
+
path: 'iam',
|
|
339
|
+
loadComponent: () =>
|
|
340
|
+
import('@flusys/ng-iam').then(m => m.PermissionPageComponent),
|
|
341
|
+
}
|
|
397
342
|
```
|
|
398
343
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
## Responsive Table Patterns
|
|
344
|
+
### LogicBuilderComponent
|
|
402
345
|
|
|
403
|
-
|
|
346
|
+
Visual drag-and-drop AND/OR nested logic builder for complex permission rules.
|
|
404
347
|
|
|
405
348
|
```html
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
<!-- Responsive columns -->
|
|
412
|
-
<th>Name</th> <!-- Always visible -->
|
|
413
|
-
<th class="hidden sm:table-cell">Code</th> <!-- 640px+ -->
|
|
414
|
-
<th class="hidden md:table-cell">Status</th> <!-- 768px+ -->
|
|
415
|
-
<th class="hidden lg:table-cell">Description</th> <!-- 1024px+ -->
|
|
349
|
+
<flusys-logic-builder
|
|
350
|
+
[availableActions]="actions"
|
|
351
|
+
[(logicTree)]="permissionLogic"
|
|
352
|
+
(logicChange)="onLogicChange($event)"
|
|
353
|
+
/>
|
|
416
354
|
```
|
|
417
355
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
356
|
+
**Supports:**
|
|
357
|
+
- AND groups (all conditions must match)
|
|
358
|
+
- OR groups (any condition must match)
|
|
359
|
+
- Nesting (AND inside OR, etc.)
|
|
360
|
+
- Individual permission conditions
|
|
361
|
+
- Visual drag-to-reorder
|
|
424
362
|
|
|
425
363
|
---
|
|
426
364
|
|
|
427
|
-
##
|
|
428
|
-
|
|
429
|
-
```typescript
|
|
430
|
-
import { form, required } from '@angular/forms';
|
|
431
|
-
|
|
432
|
-
readonly formModel = {
|
|
433
|
-
name: '',
|
|
434
|
-
description: '',
|
|
435
|
-
code: '',
|
|
436
|
-
actionType: ActionType.FRONTEND,
|
|
437
|
-
parentId: null as string | null,
|
|
438
|
-
serial: null as number | null,
|
|
439
|
-
isActive: true,
|
|
440
|
-
permissionLogic: null as ILogicNode | null,
|
|
441
|
-
};
|
|
442
|
-
|
|
443
|
-
readonly actionForm = form(this.formModel, (f) => {
|
|
444
|
-
required(f.name, { message: 'Name is required' });
|
|
445
|
-
});
|
|
365
|
+
## Integration
|
|
446
366
|
|
|
447
|
-
|
|
448
|
-
```
|
|
449
|
-
|
|
450
|
-
---
|
|
367
|
+
### With HasPermissionDirective (from ng-shared)
|
|
451
368
|
|
|
452
|
-
|
|
369
|
+
Once `provideIamProviders()` is registered, `HasPermissionDirective` works automatically:
|
|
453
370
|
|
|
454
|
-
|
|
371
|
+
```html
|
|
372
|
+
<button *hasPermission="'product:delete'">Delete</button>
|
|
455
373
|
|
|
374
|
+
<div *hasPermission="['admin:manage', 'user:read']">
|
|
375
|
+
Admin Section
|
|
376
|
+
</div>
|
|
456
377
|
```
|
|
457
|
-
# Action CRUD
|
|
458
|
-
POST /iam/actions/tree
|
|
459
|
-
POST /iam/actions/tree-for-permission
|
|
460
|
-
POST /iam/actions/get-all
|
|
461
|
-
POST /iam/actions/get/:id
|
|
462
|
-
POST /iam/actions/insert
|
|
463
|
-
POST /iam/actions/update
|
|
464
|
-
POST /iam/actions/delete
|
|
465
|
-
|
|
466
|
-
# Role CRUD
|
|
467
|
-
POST /iam/roles/get-all
|
|
468
|
-
POST /iam/roles/get/:id
|
|
469
|
-
POST /iam/roles/insert
|
|
470
|
-
POST /iam/roles/update
|
|
471
|
-
POST /iam/roles/delete
|
|
472
|
-
|
|
473
|
-
# Permission Assignments
|
|
474
|
-
POST /iam/permissions/my-permissions
|
|
475
|
-
POST /iam/permissions/user-actions/assign
|
|
476
|
-
POST /iam/permissions/get-user-actions
|
|
477
|
-
POST /iam/permissions/user-roles/assign
|
|
478
|
-
POST /iam/permissions/get-user-roles
|
|
479
|
-
POST /iam/permissions/role-actions/assign
|
|
480
|
-
POST /iam/permissions/get-role-actions
|
|
481
|
-
POST /iam/permissions/company-actions/assign
|
|
482
|
-
POST /iam/permissions/get-company-actions
|
|
483
|
-
```
|
|
484
|
-
|
|
485
|
-
---
|
|
486
378
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
### Permission Loading
|
|
379
|
+
### With PermissionGuard (from ng-shared)
|
|
490
380
|
|
|
491
381
|
```typescript
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
382
|
+
{
|
|
383
|
+
path: 'admin',
|
|
384
|
+
canActivate: [PermissionGuard],
|
|
385
|
+
data: { permission: 'admin:manage' },
|
|
386
|
+
loadComponent: () => import('./admin.component'),
|
|
495
387
|
}
|
|
496
388
|
```
|
|
497
389
|
|
|
498
|
-
|
|
499
|
-
- Clear on logout
|
|
500
|
-
- Never store in localStorage (security risk)
|
|
501
|
-
- Never load in component `ngOnInit` (race conditions)
|
|
502
|
-
|
|
503
|
-
### Permission Checks
|
|
504
|
-
|
|
505
|
-
```typescript
|
|
506
|
-
validator.hasAction('user.create'); // Single
|
|
507
|
-
validator.hasAllActions(['user.create', 'user.update']); // ALL required
|
|
508
|
-
validator.hasAnyAction(['user.view', 'user.list']); // ANY required
|
|
509
|
-
```
|
|
510
|
-
|
|
511
|
-
Cache check results in computed signals, not in templates.
|
|
390
|
+
### With appInitGuard (from ng-auth)
|
|
512
391
|
|
|
513
|
-
|
|
392
|
+
`appInitGuard` automatically loads permissions after session restoration when `iam.enabled: true`:
|
|
514
393
|
|
|
515
394
|
```typescript
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
```
|
|
519
|
-
|
|
520
|
-
### Action Naming
|
|
521
|
-
|
|
522
|
-
Format: `module.operation` or `module.entity.operation`
|
|
523
|
-
|
|
524
|
-
```typescript
|
|
525
|
-
// Good: 'user.create', 'report.export', 'admin.system.config'
|
|
526
|
-
// Bad: 'create' (generic), 'userCreate' (not dot notation)
|
|
527
|
-
```
|
|
528
|
-
|
|
529
|
-
### LogicNode Pattern
|
|
530
|
-
|
|
531
|
-
```typescript
|
|
532
|
-
// User needs 'user.create' AND ('user.update' OR 'user.delete')
|
|
533
|
-
const logic: ILogicNode = {
|
|
534
|
-
type: 'AND',
|
|
535
|
-
children: [
|
|
536
|
-
{ type: 'ACTION', value: 'user.create' },
|
|
537
|
-
{ type: 'OR', children: [
|
|
538
|
-
{ type: 'ACTION', value: 'user.update' },
|
|
539
|
-
{ type: 'ACTION', value: 'user.delete' },
|
|
540
|
-
]},
|
|
541
|
-
],
|
|
542
|
-
};
|
|
395
|
+
// The guard calls PermissionStateService.load() automatically
|
|
396
|
+
// No manual setup needed
|
|
543
397
|
```
|
|
544
398
|
|
|
545
399
|
---
|
|
546
400
|
|
|
547
|
-
##
|
|
401
|
+
## API Endpoints
|
|
548
402
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
403
|
+
| Method | Endpoint | Description |
|
|
404
|
+
|--------|----------|-------------|
|
|
405
|
+
| POST | `/iam/action/get-all` | List all permission actions |
|
|
406
|
+
| POST | `/iam/action/insert` | Create action |
|
|
407
|
+
| POST | `/iam/action/update` | Update action |
|
|
408
|
+
| POST | `/iam/action/delete` | Delete action |
|
|
409
|
+
| POST | `/iam/role/get-all` | List all roles |
|
|
410
|
+
| POST | `/iam/role/get/:id` | Get role by ID |
|
|
411
|
+
| POST | `/iam/role/insert` | Create role |
|
|
412
|
+
| POST | `/iam/role/update` | Update role |
|
|
413
|
+
| POST | `/iam/role/delete` | Delete role |
|
|
414
|
+
| POST | `/iam/role/assign-user` | Assign role to user |
|
|
415
|
+
| POST | `/iam/role/unassign-user` | Remove role from user |
|
|
416
|
+
| POST | `/iam/user-permission/get/:userId` | Get user's direct permissions |
|
|
417
|
+
| POST | `/iam/user-permission/assign` | Assign direct permissions to user |
|
|
418
|
+
| POST | `/iam/permission/get-my-permissions` | Get current user's effective permissions |
|
|
553
419
|
|
|
554
|
-
|
|
555
|
-
# Backend: .env
|
|
556
|
-
IAM_PERMISSION_MODE=FULL # DIRECT, RBAC, or FULL
|
|
557
|
-
```
|
|
420
|
+
---
|
|
558
421
|
|
|
559
|
-
|
|
560
|
-
|----------|---------------------|--------|
|
|
561
|
-
| Full IAM | `true` | Permission-based menu |
|
|
562
|
-
| IAM Disabled | `false` | Static menu (all items visible) |
|
|
563
|
-
| Service Down | `true` (API fails) | Graceful fallback to static menu |
|
|
422
|
+
## Troubleshooting
|
|
564
423
|
|
|
565
|
-
|
|
424
|
+
**`HasPermissionDirective` always hides content**
|
|
566
425
|
|
|
567
|
-
|
|
426
|
+
Permissions aren't loaded. Check that:
|
|
427
|
+
1. `provideIamProviders()` is in `app.config.ts`
|
|
428
|
+
2. `services.iam.enabled: true` in environment
|
|
429
|
+
3. `appInitGuard` runs and calls `PermissionStateService.load()`
|
|
568
430
|
|
|
569
|
-
|
|
431
|
+
**Permissions don't refresh after role change**
|
|
570
432
|
|
|
571
|
-
|
|
433
|
+
Call `PermissionStateService.invalidate()` after modifying roles/permissions:
|
|
572
434
|
|
|
573
435
|
```typescript
|
|
574
|
-
|
|
575
|
-
providers: [...provideIamProviders()]
|
|
436
|
+
this.permState.invalidate(); // Clears 1-hour cache, forces reload
|
|
576
437
|
```
|
|
577
438
|
|
|
578
|
-
|
|
439
|
+
**`FULL` mode shows RBAC UI but not direct permissions UI**
|
|
579
440
|
|
|
580
|
-
|
|
441
|
+
Verify `permissionMode: 'FULL'` in your environment and that `PermissionPageComponent` reads the mode from `APP_CONFIG`.
|
|
581
442
|
|
|
582
|
-
|
|
443
|
+
**Permission check for wildcards (`product:*`) not working**
|
|
583
444
|
|
|
584
|
-
|
|
585
|
-
|---------|----------|
|
|
586
|
-
| `permissionState.isLoaded()` returns false | Check `services.iam.enabled: true`, verify `/iam/permissions/my-permissions` works |
|
|
587
|
-
| Empty menu after login | Verify user has frontend actions, check `requiredAction` matches action codes |
|
|
588
|
-
| Menu items not filtered | Call `layoutService.setPermissionChecker()` after loading permissions |
|
|
589
|
-
| 401 on IAM endpoints | Ensure auth interceptor adds `Authorization` header |
|
|
590
|
-
| Provider not configured error | Add required providers to `app.config.ts` |
|
|
591
|
-
| Empty dropdowns | Ensure adapters return `IListResponse<T>` format |
|
|
592
|
-
| Table not scrollable on mobile | Add `overflow-x-auto -mx-4 sm:mx-0` wrapper |
|
|
593
|
-
| Columns overflow on mobile | Add `hidden sm:table-cell` to non-essential columns |
|
|
445
|
+
Ensure the backend sends flat expanded permissions, not wildcards. The client evaluates exact string matches plus wildcard expansion.
|
|
594
446
|
|
|
595
447
|
---
|
|
596
448
|
|
|
597
|
-
##
|
|
598
|
-
|
|
599
|
-
- [CORE-GUIDE.md](./CORE-GUIDE.md) - Configuration system (APP_CONFIG)
|
|
600
|
-
- [AUTH-GUIDE.md](./AUTH-GUIDE.md) - Authentication and provider adapters
|
|
601
|
-
- [SHARED-GUIDE.md](./SHARED-GUIDE.md) - Provider interface definitions
|
|
602
|
-
- [LAYOUT-GUIDE.md](./LAYOUT-GUIDE.md) - Menu system and filtering
|
|
603
|
-
|
|
604
|
-
---
|
|
449
|
+
## License
|
|
605
450
|
|
|
606
|
-
|
|
607
|
-
**Version:** 3.0.1
|
|
608
|
-
**Angular Version:** 21
|
|
451
|
+
MIT © FLUSYS
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flusys/ng-iam",
|
|
3
|
-
"version": "4.0
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "Identity and Access Management (IAM) for Angular - Independent from ng-auth through Provider Interface Pattern",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"peerDependencies": {
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
"@angular/core": ">=21.0.0",
|
|
9
9
|
"@angular/forms": ">=21.0.0",
|
|
10
10
|
"@angular/router": ">=21.0.0",
|
|
11
|
-
"@flusys/ng-core": ">=4.0
|
|
12
|
-
"@flusys/ng-layout": ">=4.0
|
|
13
|
-
"@flusys/ng-shared": ">=4.0
|
|
11
|
+
"@flusys/ng-core": ">=4.1.0",
|
|
12
|
+
"@flusys/ng-layout": ">=4.1.0",
|
|
13
|
+
"@flusys/ng-shared": ">=4.1.0",
|
|
14
14
|
"@primeuix/themes": ">=1.0.0",
|
|
15
15
|
"primeicons": ">=7.0.0",
|
|
16
16
|
"primeng": ">=21.0.0",
|