File size: 4,339 Bytes
0939458
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
// RBAC Middleware for Express/Fastify API

export enum Role {
  CLIENT  = 'CLIENT',
  VENDOR  = 'VENDOR',
  ADMIN   = 'ADMIN',
}

export enum Permission {
  // Client permissions
  VIEW_OWN_BOOKINGS     = 'view_own_bookings',
  CREATE_BOOKING        = 'create_booking',
  SIGN_CONTRACT         = 'sign_contract',
  VIEW_OWN_CONTRACTS    = 'view_own_contracts',
  REQUEST_AMENDMENT     = 'request_amendment',

  // Vendor permissions
  MANAGE_PROFILE        = 'manage_profile',
  MANAGE_PACKAGES       = 'manage_packages',
  VIEW_LEADS            = 'view_leads',
  CREATE_CONTRACT       = 'create_contract',
  SEND_CONTRACT         = 'send_contract',
  MANAGE_DELIVERABLES   = 'manage_deliverables',

  // Admin permissions
  MODERATE_VENDORS      = 'moderate_vendors',
  MODERATE_CONTENT      = 'moderate_content',
  OVERSEE_CONTRACTS     = 'oversee_contracts',
  MEDIATE_DISPUTES      = 'mediate_disputes',
  EXPORT_AUDIT_TRAIL    = 'export_audit_trail',
  MANAGE_CATEGORIES     = 'manage_categories',
  IMPERSONATE_USERS     = 'impersonate_users',
  MANAGE_USERS          = 'manage_users',
}

const rolePermissions: Record<Role, Permission[]> = {
  [Role.CLIENT]: [
    Permission.VIEW_OWN_BOOKINGS,
    Permission.CREATE_BOOKING,
    Permission.SIGN_CONTRACT,
    Permission.VIEW_OWN_CONTRACTS,
    Permission.REQUEST_AMENDMENT,
  ],
  [Role.VENDOR]: [
    Permission.MANAGE_PROFILE,
    Permission.MANAGE_PACKAGES,
    Permission.VIEW_LEADS,
    Permission.CREATE_CONTRACT,
    Permission.SEND_CONTRACT,
    Permission.MANAGE_DELIVERABLES,
  ],
  [Role.ADMIN]: Object.values(Permission),
}

export function hasPermission(role: Role, permission: Permission): boolean {
  return rolePermissions[role]?.includes(permission) ?? false
}

export function requirePermission(permission: Permission) {
  return function (req: any, res: any, next: Function) {
    const role = req.user?.role as Role
    if (!role || !hasPermission(role, permission)) {
      return res.status(403).json({
        error: 'Forbidden',
        message: `Missing required permission: ${permission}`,
      })
    }
    next()
  }
}

export function requireRole(...roles: Role[]) {
  return function (req: any, res: any, next: Function) {
    const role = req.user?.role as Role
    if (!role || !roles.includes(role)) {
      return res.status(403).json({
        error: 'Forbidden',
        message: `Access restricted to: ${roles.join(', ')}`,
      })
    }
    next()
  }
}

// Contract-specific RBAC: only the vendor who created it or the client who received it can access
export function requireContractAccess(contractIdParam: string = 'contractId') {
  return async function (req: any, res: any, next: Function) {
    const contractId = req.params[contractIdParam]
    const userId = req.user?.id
    const role = req.user?.role as Role

    if (role === Role.ADMIN && hasPermission(role, Permission.OVERSEE_CONTRACTS)) {
      return next()
    }

    // Fetch contract and verify ownership
    try {
      const contract = await fetchContract(contractId)
      if (!contract) return res.status(404).json({ error: 'Contract not found' })

      const hasAccess =
        (role === Role.VENDOR && contract.vendorId === userId) ||
        (role === Role.CLIENT && contract.clientId === userId)

      if (!hasAccess) return res.status(403).json({ error: 'Access denied' })
      next()
    } catch (err) {
      return res.status(500).json({ error: 'Internal error' })
    }
  }
}

async function fetchContract(id: string) {
  // Replace with actual DB call
  return { id, vendorId: '', clientId: '' }
}

// ── Impersonation (admin-only) ──
// Admins can impersonate with TTL-bound sessions and audit reason field

export async function impersonateUser(adminId: string, targetUserId: string, reason: string, ttlMinutes: number = 30) {
  // 1. Verify admin has IMPERSONATE_USERS permission
  // 2. Create session with role=targetUser.role, token expires in ttlMinutes
  // 3. Log to AdminAuditLog:
  //    { adminId, action: 'impersonate_start', target: targetUserId, detail: reason, ttl: ttlMinutes }
  // 4. Return impersonation session token
}

// ── Rate Limiting ──
// Keyed on IP + method + pathname, backed by Redis
// Implementation: use rate-limiter-flexible with Redis store
// export const rateLimiter = new RateLimiterRedis({...})