npm package: @flusys/ng-iam
Description: Identity and Access Management (IAM) for Angular - Independent from ng-auth through Provider Interface Pattern
Latest version: 4.1.1

# @flusys/ng-iam

> Identity and Access Management UI library for the FLUSYS Angular platform — role management, permission assignment, permission evaluation, and logic builder.

[![npm version](https://img.shields.io/npm/v/@flusys/ng-iam.svg)](https://www.npmjs.com/package/@flusys/ng-iam)
[![Angular](https://img.shields.io/badge/Angular-21-red.svg)](https://angular.io)
[![TypeScript](https://img.shields.io/badge/TypeScript-5.x-blue.svg)](https://www.typescriptlang.org)
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)

---

## Table of Contents

- [Overview](#overview)
- [Features](#features)
- [Compatibility](#compatibility)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Permission Modes](#permission-modes)
  - [RBAC Mode](#rbac-mode)
  - [DIRECT Mode](#direct-mode)
  - [FULL Mode](#full-mode)
- [Module Registration](#module-registration)
- [Permission State Service](#permission-state-service)
- [IAM API Services](#iam-api-services)
- [Components](#components)
  - [PermissionPageComponent](#permissionpagecomponent)
  - [LogicBuilderComponent](#logicbuildercomponent)
- [Integration](#integration)
- [API Endpoints](#api-endpoints)
- [Troubleshooting](#troubleshooting)
- [License](#license)

---

## Overview

`@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.

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.

---

## Features

- ✅ Three permission modes: RBAC, DIRECT, FULL
- ✅ `PermissionStateService` — signal-based permission state with 1-hour cache
- ✅ Role management UI (create, assign users, set permissions)
- ✅ Direct permission assignment UI (assign permissions directly to users)
- ✅ `LogicBuilderComponent` — visual AND/OR/nested logic builder for complex permissions
- ✅ Permission cache auto-invalidation on role/permission change
- ✅ `provideIamProviders()` — registers permission evaluator with `ng-shared`
- ✅ Lazy-loaded IAM pages

---

## Compatibility

| Package | Version |
|---------|---------|
| Angular | 21+ |
| @flusys/ng-core | 4.x |
| @flusys/ng-shared | 4.x |

---

## Installation

```bash
npm install @flusys/ng-iam @flusys/ng-core @flusys/ng-shared
```

---

## Quick Start

### 1. Enable IAM in Config

```typescript
// environments/environment.ts
export const environment = {
  permissionMode: 'FULL',  // 'RBAC' | 'DIRECT' | 'FULL'
  services: {
    auth: { enabled: true },
    iam: { enabled: true },
  },
};
```

### 2. Register IAM Providers

```typescript
// app.config.ts
import { provideIamProviders } from '@flusys/ng-iam';

export const appConfig: ApplicationConfig = {
  providers: [
    ...provideIamProviders(),
    // Provides: PERMISSION_VALIDATOR (used by HasPermissionDirective, PermissionGuard)
  ],
};
```

### 3. Add IAM Routes

```typescript
// app.routes.ts
import { IAM_ROUTES } from '@flusys/ng-iam';

export const routes: Routes = [
  {
    path: 'iam',
    loadChildren: () => IAM_ROUTES,
  },
];
```

---

## Permission Modes

### RBAC Mode

Role-Based Access Control. Users are assigned roles; roles hold permissions.

```
User ──assigned──► Role ──has──► Permissions
```

**Active controllers:**
- Role management (create roles, assign permissions to roles)
- Role assignment (assign roles to users)

**Config:**
```typescript
{ permissionMode: 'RBAC' }
```

### DIRECT Mode

Permissions assigned directly to users. No roles.

```
User ──directly assigned──► Permissions
```

**Active controllers:**
- User permission assignment

**Config:**
```typescript
{ permissionMode: 'DIRECT' }
```

### FULL Mode

Both RBAC and DIRECT combined. User gets union of role permissions + direct permissions.

```
User ──assigned──► Role ──has──► Permissions
     └──directly──────────────► Permissions
```

**Config:**
```typescript
{ permissionMode: 'FULL' }
```

---

## Module Registration

### `provideIamProviders()`

```typescript
import { provideIamProviders } from '@flusys/ng-iam';

// app.config.ts
providers: [
  ...provideIamProviders(),
]
```

Registers:
- `PERMISSION_VALIDATOR` — implementation of `IPermissionValidator` used by `HasPermissionDirective` and `PermissionGuard` from `ng-shared`

---

## Permission State Service

`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.

```typescript
import { PermissionStateService } from '@flusys/ng-iam';

@Component({ ... })
export class MyComponent {
  private permState = inject(PermissionStateService);

  // Signals
  permissions = this.permState.permissions;       // Signal<string[]>
  isLoaded = this.permState.isLoaded;             // Signal<boolean>
  isLoading = this.permState.isLoading;           // Signal<boolean>

  // Check permission reactively
  canEdit = computed(() =>
    this.permState.hasPermission('product:update')
  );

  canDelete = computed(() =>
    this.permState.hasAnyPermission(['product:delete', 'admin:manage'])
  );

  // Load permissions (called by appInitGuard after login)
  loadPermissions(): void {
    this.permState.load();
  }

  // Invalidate cache (call after role/permission changes)
  invalidate(): void {
    this.permState.invalidate();
  }
}
```

**PermissionStateService API:**

| Member | Type | Description |
|--------|------|-------------|
| `permissions` | `Signal<string[]>` | Current user's permission list |
| `isLoaded` | `Signal<boolean>` | True when permissions are loaded |
| `isLoading` | `Signal<boolean>` | True when loading is in progress |
| `hasPermission(key)` | `boolean` | Single permission check |
| `hasAllPermissions(keys[])` | `boolean` | AND logic — all must match |
| `hasAnyPermission(keys[])` | `boolean` | OR logic — at least one must match |
| `load()` | `Observable<void>` | Load permissions from backend |
| `invalidate()` | `void` | Clear cache and force reload |

### Permission Key Format

```
resource:action
```

Examples:
```
product:read
product:create
product:update
product:delete
admin:manage
user:read
report:export
```

Wildcard support:
```
*         — all permissions
product:* — all product actions
```

---

## IAM API Services

### ActionApiService

Manages available permission actions (resource:action pairs):

```typescript
import { ActionApiService } from '@flusys/ng-iam';

@Injectable({ ... })
export class MyService {
  private actionApi = inject(ActionApiService);

  getActions(): Observable<IListResponse<IAction>> {
    return this.actionApi.getAll({ pageSize: 100 });
  }
}
```

### RoleApiService

Manages roles and their permission assignments:

```typescript
import { RoleApiService } from '@flusys/ng-iam';

@Injectable({ ... })
export class MyService {
  private roleApi = inject(RoleApiService);

  createRole(name: string, permissions: string[]): Observable<ISingleResponse<IRole>> {
    return this.roleApi.insert({ name, permissions });
  }

  assignRoleToUser(userId: string, roleId: string): Observable<IMessageResponse> {
    return this.roleApi.assignToUser({ userId, roleId });
  }
}
```

### UserPermissionApiService

Manages direct permission assignments to users:

```typescript
import { UserPermissionApiService } from '@flusys/ng-iam';

@Injectable({ ... })
export class MyService {
  private userPermApi = inject(UserPermissionApiService);

  assignPermissions(userId: string, permissions: string[]): Observable<IMessageResponse> {
    return this.userPermApi.assign({ userId, permissions });
  }
}
```

---

## Components

### PermissionPageComponent

Full-page IAM management UI. Renders different panels based on `permissionMode`:

| Mode | Panels Shown |
|------|-------------|
| `RBAC` | Roles list, Role editor, User-role assignment |
| `DIRECT` | User permission editor |
| `FULL` | All RBAC panels + User permission editor |

```typescript
// In routes:
{
  path: 'iam',
  loadComponent: () =>
    import('@flusys/ng-iam').then(m => m.PermissionPageComponent),
}
```

### LogicBuilderComponent

Visual drag-and-drop AND/OR nested logic builder for complex permission rules.

```html
<flusys-logic-builder
  [availableActions]="actions"
  [(logicTree)]="permissionLogic"
  (logicChange)="onLogicChange($event)"
/>
```

**Supports:**
- AND groups (all conditions must match)
- OR groups (any condition must match)
- Nesting (AND inside OR, etc.)
- Individual permission conditions
- Visual drag-to-reorder

---

## Integration

### With HasPermissionDirective (from ng-shared)

Once `provideIamProviders()` is registered, `HasPermissionDirective` works automatically:

```html
<button *hasPermission="'product:delete'">Delete</button>

<div *hasPermission="['admin:manage', 'user:read']">
  Admin Section
</div>
```

### With PermissionGuard (from ng-shared)

```typescript
{
  path: 'admin',
  canActivate: [PermissionGuard],
  data: { permission: 'admin:manage' },
  loadComponent: () => import('./admin.component'),
}
```

### With appInitGuard (from ng-auth)

`appInitGuard` automatically loads permissions after session restoration when `iam.enabled: true`:

```typescript
// The guard calls PermissionStateService.load() automatically
// No manual setup needed
```

---

## API Endpoints

| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | `/iam/action/get-all` | List all permission actions |
| POST | `/iam/action/insert` | Create action |
| POST | `/iam/action/update` | Update action |
| POST | `/iam/action/delete` | Delete action |
| POST | `/iam/role/get-all` | List all roles |
| POST | `/iam/role/get/:id` | Get role by ID |
| POST | `/iam/role/insert` | Create role |
| POST | `/iam/role/update` | Update role |
| POST | `/iam/role/delete` | Delete role |
| POST | `/iam/role/assign-user` | Assign role to user |
| POST | `/iam/role/unassign-user` | Remove role from user |
| POST | `/iam/user-permission/get/:userId` | Get user's direct permissions |
| POST | `/iam/user-permission/assign` | Assign direct permissions to user |
| POST | `/iam/permission/get-my-permissions` | Get current user's effective permissions |

---

## Troubleshooting

**`HasPermissionDirective` always hides content**

Permissions aren't loaded. Check that:
1. `provideIamProviders()` is in `app.config.ts`
2. `services.iam.enabled: true` in environment
3. `appInitGuard` runs and calls `PermissionStateService.load()`

**Permissions don't refresh after role change**

Call `PermissionStateService.invalidate()` after modifying roles/permissions:

```typescript
this.permState.invalidate(); // Clears 1-hour cache, forces reload
```

**`FULL` mode shows RBAC UI but not direct permissions UI**

Verify `permissionMode: 'FULL'` in your environment and that `PermissionPageComponent` reads the mode from `APP_CONFIG`.

**Permission check for wildcards (`product:*`) not working**

Ensure the backend sends flat expanded permissions, not wildcards. The client evaluates exact string matches plus wildcard expansion.

---

## License

MIT © FLUSYS