imeshuek commited on
Commit
0939458
Β·
verified Β·
1 Parent(s): 39d82a7

Upload api/middleware/rbac.ts

Browse files
Files changed (1) hide show
  1. api/middleware/rbac.ts +132 -0
api/middleware/rbac.ts ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // RBAC Middleware for Express/Fastify API
2
+
3
+ export enum Role {
4
+ CLIENT = 'CLIENT',
5
+ VENDOR = 'VENDOR',
6
+ ADMIN = 'ADMIN',
7
+ }
8
+
9
+ export enum Permission {
10
+ // Client permissions
11
+ VIEW_OWN_BOOKINGS = 'view_own_bookings',
12
+ CREATE_BOOKING = 'create_booking',
13
+ SIGN_CONTRACT = 'sign_contract',
14
+ VIEW_OWN_CONTRACTS = 'view_own_contracts',
15
+ REQUEST_AMENDMENT = 'request_amendment',
16
+
17
+ // Vendor permissions
18
+ MANAGE_PROFILE = 'manage_profile',
19
+ MANAGE_PACKAGES = 'manage_packages',
20
+ VIEW_LEADS = 'view_leads',
21
+ CREATE_CONTRACT = 'create_contract',
22
+ SEND_CONTRACT = 'send_contract',
23
+ MANAGE_DELIVERABLES = 'manage_deliverables',
24
+
25
+ // Admin permissions
26
+ MODERATE_VENDORS = 'moderate_vendors',
27
+ MODERATE_CONTENT = 'moderate_content',
28
+ OVERSEE_CONTRACTS = 'oversee_contracts',
29
+ MEDIATE_DISPUTES = 'mediate_disputes',
30
+ EXPORT_AUDIT_TRAIL = 'export_audit_trail',
31
+ MANAGE_CATEGORIES = 'manage_categories',
32
+ IMPERSONATE_USERS = 'impersonate_users',
33
+ MANAGE_USERS = 'manage_users',
34
+ }
35
+
36
+ const rolePermissions: Record<Role, Permission[]> = {
37
+ [Role.CLIENT]: [
38
+ Permission.VIEW_OWN_BOOKINGS,
39
+ Permission.CREATE_BOOKING,
40
+ Permission.SIGN_CONTRACT,
41
+ Permission.VIEW_OWN_CONTRACTS,
42
+ Permission.REQUEST_AMENDMENT,
43
+ ],
44
+ [Role.VENDOR]: [
45
+ Permission.MANAGE_PROFILE,
46
+ Permission.MANAGE_PACKAGES,
47
+ Permission.VIEW_LEADS,
48
+ Permission.CREATE_CONTRACT,
49
+ Permission.SEND_CONTRACT,
50
+ Permission.MANAGE_DELIVERABLES,
51
+ ],
52
+ [Role.ADMIN]: Object.values(Permission),
53
+ }
54
+
55
+ export function hasPermission(role: Role, permission: Permission): boolean {
56
+ return rolePermissions[role]?.includes(permission) ?? false
57
+ }
58
+
59
+ export function requirePermission(permission: Permission) {
60
+ return function (req: any, res: any, next: Function) {
61
+ const role = req.user?.role as Role
62
+ if (!role || !hasPermission(role, permission)) {
63
+ return res.status(403).json({
64
+ error: 'Forbidden',
65
+ message: `Missing required permission: ${permission}`,
66
+ })
67
+ }
68
+ next()
69
+ }
70
+ }
71
+
72
+ export function requireRole(...roles: Role[]) {
73
+ return function (req: any, res: any, next: Function) {
74
+ const role = req.user?.role as Role
75
+ if (!role || !roles.includes(role)) {
76
+ return res.status(403).json({
77
+ error: 'Forbidden',
78
+ message: `Access restricted to: ${roles.join(', ')}`,
79
+ })
80
+ }
81
+ next()
82
+ }
83
+ }
84
+
85
+ // Contract-specific RBAC: only the vendor who created it or the client who received it can access
86
+ export function requireContractAccess(contractIdParam: string = 'contractId') {
87
+ return async function (req: any, res: any, next: Function) {
88
+ const contractId = req.params[contractIdParam]
89
+ const userId = req.user?.id
90
+ const role = req.user?.role as Role
91
+
92
+ if (role === Role.ADMIN && hasPermission(role, Permission.OVERSEE_CONTRACTS)) {
93
+ return next()
94
+ }
95
+
96
+ // Fetch contract and verify ownership
97
+ try {
98
+ const contract = await fetchContract(contractId)
99
+ if (!contract) return res.status(404).json({ error: 'Contract not found' })
100
+
101
+ const hasAccess =
102
+ (role === Role.VENDOR && contract.vendorId === userId) ||
103
+ (role === Role.CLIENT && contract.clientId === userId)
104
+
105
+ if (!hasAccess) return res.status(403).json({ error: 'Access denied' })
106
+ next()
107
+ } catch (err) {
108
+ return res.status(500).json({ error: 'Internal error' })
109
+ }
110
+ }
111
+ }
112
+
113
+ async function fetchContract(id: string) {
114
+ // Replace with actual DB call
115
+ return { id, vendorId: '', clientId: '' }
116
+ }
117
+
118
+ // ── Impersonation (admin-only) ──
119
+ // Admins can impersonate with TTL-bound sessions and audit reason field
120
+
121
+ export async function impersonateUser(adminId: string, targetUserId: string, reason: string, ttlMinutes: number = 30) {
122
+ // 1. Verify admin has IMPERSONATE_USERS permission
123
+ // 2. Create session with role=targetUser.role, token expires in ttlMinutes
124
+ // 3. Log to AdminAuditLog:
125
+ // { adminId, action: 'impersonate_start', target: targetUserId, detail: reason, ttl: ttlMinutes }
126
+ // 4. Return impersonation session token
127
+ }
128
+
129
+ // ── Rate Limiting ──
130
+ // Keyed on IP + method + pathname, backed by Redis
131
+ // Implementation: use rate-limiter-flexible with Redis store
132
+ // export const rateLimiter = new RateLimiterRedis({...})