Re_Backend/docs/RBAC_IMPLEMENTATION.md

633 lines
16 KiB
Markdown

# Role-Based Access Control (RBAC) Implementation
## Overview
The system now supports **three user roles** for granular access control:
| Role | Access Level | Use Case |
|------|--------------|----------|
| **USER** | Standard | Default role for all users - create/view own requests |
| **MANAGEMENT** | Enhanced Read | View all requests across organization (read-only) |
| **ADMIN** | Full Access | System configuration, user management, all workflows |
---
## User Roles
### 1. USER (Default)
**Permissions:**
- ✅ Create new workflow requests
- ✅ View own requests
- ✅ Participate in assigned workflows (as approver/spectator)
- ✅ Add work notes to requests they're involved in
- ✅ Upload documents to own requests
- ❌ Cannot view other users' requests (unless added as participant)
- ❌ Cannot access system configuration
- ❌ Cannot manage users or roles
**Use Case:** Regular employees creating and managing their workflow requests
---
### 2. MANAGEMENT
**Permissions:**
- ✅ All USER permissions
- ✅ View ALL requests across organization (read-only)
- ✅ Access comprehensive dashboards with organization-wide analytics
- ✅ Export reports across all departments
- ✅ View TAT performance metrics for all approvers
- ❌ Cannot approve/reject requests (unless explicitly added as approver)
- ❌ Cannot modify system configuration
- ❌ Cannot manage user roles
**Use Case:** Department heads, managers, auditors needing visibility into all workflows
---
### 3. ADMIN
**Permissions:**
- ✅ All MANAGEMENT permissions
- ✅ All USER permissions
- ✅ Manage system configuration
- ✅ Assign user roles
- ✅ Manage holiday calendar
- ✅ Configure email/notification settings
- ✅ Access audit logs
- ✅ Manage AI provider settings
**Use Case:** System administrators, IT staff managing the workflow platform
---
## Database Schema
### Migration Applied
```sql
-- Create ENUM type for roles
CREATE TYPE user_role_enum AS ENUM ('USER', 'MANAGEMENT', 'ADMIN');
-- Add role column to users table
ALTER TABLE users
ADD COLUMN role user_role_enum NOT NULL DEFAULT 'USER';
-- Migrate existing data
UPDATE users
SET role = CASE
WHEN is_admin = true THEN 'ADMIN'
ELSE 'USER'
END;
-- Create index for performance
CREATE INDEX idx_users_role ON users(role);
```
### Updated Users Table
```
users {
uuid user_id PK
varchar email UK
varchar display_name
varchar department
varchar designation
boolean is_active
user_role_enum role ← NEW FIELD
boolean is_admin ← DEPRECATED (kept for compatibility)
timestamp created_at
timestamp updated_at
}
```
---
## Backend Implementation
### Model (User.ts)
```typescript
export type UserRole = 'USER' | 'MANAGEMENT' | 'ADMIN';
interface UserAttributes {
// ... other fields
role: UserRole; // RBAC role
isAdmin: boolean; // DEPRECATED
}
class User extends Model<UserAttributes> {
public role!: UserRole;
// Helper methods
public isUserRole(): boolean {
return this.role === 'USER';
}
public isManagementRole(): boolean {
return this.role === 'MANAGEMENT';
}
public isAdminRole(): boolean {
return this.role === 'ADMIN';
}
public hasManagementAccess(): boolean {
return this.role === 'MANAGEMENT' || this.role === 'ADMIN';
}
public hasAdminAccess(): boolean {
return this.role === 'ADMIN';
}
}
```
---
## Middleware Usage
### 1. Require Admin Only
```typescript
import { requireAdmin } from '@middlewares/authorization.middleware';
// Only ADMIN can access
router.post('/admin/config', authenticate, requireAdmin, adminController.updateConfig);
router.post('/admin/users/:userId/role', authenticate, requireAdmin, adminController.updateUserRole);
router.post('/admin/holidays', authenticate, requireAdmin, adminController.addHoliday);
```
### 2. Require Management or Admin
```typescript
import { requireManagement } from '@middlewares/authorization.middleware';
// MANAGEMENT and ADMIN can access (read-only for management)
router.get('/reports/all-requests', authenticate, requireManagement, reportController.getAllRequests);
router.get('/analytics/department', authenticate, requireManagement, analyticsController.getDepartmentStats);
router.get('/dashboard/organization', authenticate, requireManagement, dashboardController.getOrgWideStats);
```
### 3. Flexible Role Checking
```typescript
import { requireRole } from '@middlewares/authorization.middleware';
// Multiple role options
router.get('/workflows/search', authenticate, requireRole(['MANAGEMENT', 'ADMIN']), workflowController.search);
router.post('/workflows/export', authenticate, requireRole(['MANAGEMENT', 'ADMIN']), workflowController.export);
// Any authenticated user
router.get('/profile', authenticate, requireRole(['USER', 'MANAGEMENT', 'ADMIN']), userController.getProfile);
```
### 4. Programmatic Role Checking in Controllers
```typescript
import { hasManagementAccess, hasAdminAccess } from '@middlewares/authorization.middleware';
export async function getWorkflows(req: Request, res: Response) {
const user = req.user;
// Management and Admin can see ALL workflows
if (hasManagementAccess(user)) {
const allWorkflows = await WorkflowRequest.findAll();
return res.json({ success: true, data: allWorkflows });
}
// Regular users only see their own workflows
const userWorkflows = await WorkflowRequest.findAll({
where: { initiatorId: user.userId }
});
return res.json({ success: true, data: userWorkflows });
}
```
---
## Example Route Implementations
### Admin Routes (ADMIN only)
```typescript
// src/routes/admin.routes.ts
import { Router } from 'express';
import { authenticate } from '@middlewares/auth.middleware';
import { requireAdmin } from '@middlewares/authorization.middleware';
import * as adminController from '@controllers/admin.controller';
const router = Router();
// All admin routes require ADMIN role
router.use(authenticate, requireAdmin);
// System configuration
router.get('/config', adminController.getConfig);
router.put('/config', adminController.updateConfig);
// User role management
router.put('/users/:userId/role', adminController.updateUserRole);
router.get('/users/admins', adminController.getAllAdmins);
router.get('/users/management', adminController.getAllManagement);
// Holiday management
router.post('/holidays', adminController.createHoliday);
router.delete('/holidays/:holidayId', adminController.deleteHoliday);
export default router;
```
### Management Routes (MANAGEMENT + ADMIN)
```typescript
// src/routes/management.routes.ts
import { Router } from 'express';
import { authenticate } from '@middlewares/auth.middleware';
import { requireManagement } from '@middlewares/authorization.middleware';
import * as managementController from '@controllers/management.controller';
const router = Router();
// All management routes require MANAGEMENT or ADMIN role
router.use(authenticate, requireManagement);
// Organization-wide dashboards (read-only)
router.get('/dashboard/organization', managementController.getOrgDashboard);
router.get('/requests/all', managementController.getAllRequests);
router.get('/analytics/tat-performance', managementController.getTATPerformance);
router.get('/analytics/approver-stats', managementController.getApproverStats);
router.get('/reports/export', managementController.exportReports);
// Department-wise analytics
router.get('/analytics/department/:deptName', managementController.getDepartmentAnalytics);
export default router;
```
### Workflow Routes (Mixed Permissions)
```typescript
// src/routes/workflow.routes.ts
import { Router } from 'express';
import { authenticate } from '@middlewares/auth.middleware';
import { requireManagement, requireRole } from '@middlewares/authorization.middleware';
import * as workflowController from '@controllers/workflow.controller';
const router = Router();
// USER: Create own request (all roles can do this)
router.post('/workflows', authenticate, workflowController.create);
// USER: View own requests (filtered by role in controller)
router.get('/workflows/my-requests', authenticate, workflowController.getMyRequests);
// MANAGEMENT + ADMIN: Search all requests
router.get('/workflows/search', authenticate, requireManagement, workflowController.searchAll);
// ADMIN: Delete workflow
router.delete('/workflows/:id', authenticate, requireRole(['ADMIN']), workflowController.delete);
export default router;
```
---
## Controller Implementation Examples
### Example 1: Dashboard with Role-Based Data
```typescript
// src/controllers/dashboard.controller.ts
import { hasManagementAccess } from '@middlewares/authorization.middleware';
export async function getDashboard(req: Request, res: Response) {
const user = req.user;
// MANAGEMENT and ADMIN: See organization-wide stats
if (hasManagementAccess(user)) {
const stats = await dashboardService.getOrganizationStats();
return res.json({
success: true,
data: {
...stats,
scope: 'organization', // Indicates full visibility
userRole: user.role
}
});
}
// USER: See only personal stats
const stats = await dashboardService.getUserStats(user.userId);
return res.json({
success: true,
data: {
...stats,
scope: 'personal', // Indicates limited visibility
userRole: user.role
}
});
}
```
### Example 2: User Role Update (ADMIN only)
```typescript
// src/controllers/admin.controller.ts
export async function updateUserRole(req: Request, res: Response) {
const { userId } = req.params;
const { role } = req.body;
// Validate role
if (!['USER', 'MANAGEMENT', 'ADMIN'].includes(role)) {
return res.status(400).json({
success: false,
error: 'Invalid role. Must be USER, MANAGEMENT, or ADMIN'
});
}
// Update user role
const user = await User.findByPk(userId);
if (!user) {
return res.status(404).json({
success: false,
error: 'User not found'
});
}
const oldRole = user.role;
user.role = role;
// Sync is_admin for backward compatibility
user.isAdmin = (role === 'ADMIN');
await user.save();
// Log role change
console.log(`✅ User role updated: ${user.email} - ${oldRole}${role}`);
return res.json({
success: true,
message: `User role updated from ${oldRole} to ${role}`,
data: {
userId: user.userId,
email: user.email,
role: user.role
}
});
}
```
---
## Frontend Integration
### Update Auth Context
```typescript
// Frontend: src/contexts/AuthContext.tsx
interface User {
userId: string;
email: string;
displayName: string;
role: 'USER' | 'MANAGEMENT' | 'ADMIN'; // ← Add role
}
// Helper functions
export function isAdmin(user: User | null): boolean {
return user?.role === 'ADMIN';
}
export function isManagement(user: User | null): boolean {
return user?.role === 'MANAGEMENT' || user?.role === 'ADMIN';
}
export function hasManagementAccess(user: User | null): boolean {
return user?.role === 'MANAGEMENT' || user?.role === 'ADMIN';
}
```
### Role-Based UI Rendering
```typescript
// Show admin menu only for ADMIN
{user?.role === 'ADMIN' && (
<NavItem to="/admin/config">
<Settings /> System Configuration
</NavItem>
)}
// Show management dashboard for MANAGEMENT and ADMIN
{(user?.role === 'MANAGEMENT' || user?.role === 'ADMIN') && (
<NavItem to="/dashboard/organization">
<TrendingUp /> Organization Dashboard
</NavItem>
)}
// Show all requests for MANAGEMENT and ADMIN
{hasManagementAccess(user) && (
<NavItem to="/requests/all">
<FileText /> All Requests
</NavItem>
)}
```
---
## Migration Guide
### Running the Migration
```bash
# Run migration to add role column
npm run migrate
# Verify migration
psql -d royal_enfield_db -c "SELECT email, role, is_admin FROM users LIMIT 10;"
```
### Expected Results
```
Before Migration:
+-------------------------+-----------+
| email | is_admin |
+-------------------------+-----------+
| admin@royalenfield.com | true |
| user1@royalenfield.com | false |
+-------------------------+-----------+
After Migration:
+-------------------------+-----------+-----------+
| email | role | is_admin |
+-------------------------+-----------+-----------+
| admin@royalenfield.com | ADMIN | true |
| user1@royalenfield.com | USER | false |
+-------------------------+-----------+-----------+
```
---
## Assigning Roles
### Via SQL (Direct Database)
```sql
-- Make user a MANAGEMENT role
UPDATE users
SET role = 'MANAGEMENT', is_admin = false
WHERE email = 'manager@royalenfield.com';
-- Make user an ADMIN role
UPDATE users
SET role = 'ADMIN', is_admin = true
WHERE email = 'admin@royalenfield.com';
-- Revert to USER role
UPDATE users
SET role = 'USER', is_admin = false
WHERE email = 'user@royalenfield.com';
```
### Via API (Admin Endpoint)
```bash
# Update user role (ADMIN only)
POST /api/v1/admin/users/:userId/role
Authorization: Bearer <admin-token>
Content-Type: application/json
{
"role": "MANAGEMENT"
}
```
---
## Testing
### Test Scenarios
```typescript
describe('RBAC Tests', () => {
test('USER cannot access admin config', async () => {
const response = await request(app)
.get('/api/v1/admin/config')
.set('Authorization', `Bearer ${userToken}`);
expect(response.status).toBe(403);
expect(response.body.error).toContain('Admin access required');
});
test('MANAGEMENT can view all requests', async () => {
const response = await request(app)
.get('/api/v1/management/requests/all')
.set('Authorization', `Bearer ${managementToken}`);
expect(response.status).toBe(200);
expect(response.body.data).toBeInstanceOf(Array);
});
test('ADMIN can update user roles', async () => {
const response = await request(app)
.put(`/api/v1/admin/users/${userId}/role`)
.set('Authorization', `Bearer ${adminToken}`)
.send({ role: 'MANAGEMENT' });
expect(response.status).toBe(200);
expect(response.body.data.role).toBe('MANAGEMENT');
});
});
```
---
## Best Practices
### 1. Always Use Role Column
```typescript
// ✅ GOOD: Use new role system
if (user.role === 'ADMIN') {
// Admin logic
}
// ❌ BAD: Don't use deprecated is_admin
if (user.isAdmin) {
// Deprecated approach
}
```
### 2. Use Helper Functions
```typescript
// ✅ GOOD: Use provided helpers
if (user.hasManagementAccess()) {
// Management or Admin logic
}
// ❌ BAD: Manual checking
if (user.role === 'MANAGEMENT' || user.role === 'ADMIN') {
// Verbose
}
```
### 3. Route Protection
```typescript
// ✅ GOOD: Clear role requirements
router.get('/sensitive-data', authenticate, requireManagement, controller.getData);
// ❌ BAD: Role checking in controller only
router.get('/sensitive-data', authenticate, controller.getData); // No middleware check
```
---
## Backward Compatibility
The `is_admin` field is **DEPRECATED** but kept for backward compatibility:
- ✅ Existing code using `is_admin` will continue to work
- ✅ Migration automatically syncs `role` and `is_admin`
- ⚠️ New code should use `role` instead of `is_admin`
- 📅 `is_admin` will be removed in future version
### Sync Logic
```typescript
// When updating role, sync is_admin
user.role = 'ADMIN';
user.isAdmin = true; // Auto-sync
user.role = 'USER';
user.isAdmin = false; // Auto-sync
```
---
## Quick Reference
| Task | Code Example |
|------|--------------|
| Check if ADMIN | `user.role === 'ADMIN'` or `user.isAdminRole()` |
| Check if MANAGEMENT | `user.role === 'MANAGEMENT'` or `user.isManagementRole()` |
| Check if USER | `user.role === 'USER'` or `user.isUserRole()` |
| Check Management+ | `user.hasManagementAccess()` |
| Middleware: Admin only | `requireAdmin` |
| Middleware: Management+ | `requireManagement` |
| Middleware: Custom roles | `requireRole(['ADMIN', 'MANAGEMENT'])` |
| Update role (SQL) | `UPDATE users SET role = 'MANAGEMENT' WHERE email = '...'` |
| Update role (API) | `PUT /admin/users/:userId/role { role: 'MANAGEMENT' }` |
---
## Support
For questions or issues:
- Check migration logs: `logs/migration.log`
- Review user roles: `SELECT email, role FROM users;`
- Test role access: Use provided test scenarios
**Migration File:** `src/migrations/20251112-add-user-roles.ts`
**Model File:** `src/models/User.ts`
**Middleware File:** `src/middlewares/authorization.middleware.ts`