Access Control
Role-Based Access Control Overview
The Lofty Golden Oil ERP system implements Role-Based Access Control (RBAC) to restrict system access based on organizational roles. Each user is assigned exactly one role that determines which modules, features, and actions they can perform.
Enforcement Mechanisms:
- Server-side (authoritative): Express middleware validates the JWT token's role claim against the route's allowed roles before processing any request. This is the primary security boundary.
- Client-side (UX-only): React conditionally renders navigation menus, buttons, and forms based on the user's role. This improves usability but does not provide security — all access control is enforced server-side.
Key Principle: The client-side conditional rendering is a convenience, not a security measure. A user with a valid JWT can attempt any API call regardless of what the UI shows them. The server always rejects unauthorized requests.
Role Definitions
admin — Full System Administrator
The administrator has unrestricted access to all system modules and features. This role is intended for system owners, IT administrators, and senior management.
Capabilities:
- Full CRUD on all entities (users, inventory, purchases, production, sales, customers, suppliers, expenses, invoices, payments, requisitions)
- User management (create, update, deactivate accounts)
- Role assignment
- System settings management
- Audit log access and review
- Dashboard and all reports
- Accounting module access
- Approval authority on all workflows
Typical Users: System administrator, Company owner/CEO
production — Production Manager
The production role manages inventory and manufacturing operations. Access is focused on supply chain and production modules with read-only access to customer and supplier data.
Capabilities:
- Dashboard access (statistics, charts, alerts)
- Inventory: Full CRUD + stock adjustments + low-stock alerts
- Purchases: Full CRUD (recording procurement transactions)
- Production Runs: Full CRUD + date-based filtering
- Customers: Read-only (view list and details for order context)
- Suppliers: Read-only (view list and details for procurement context)
- Requisitions: Can create and approve at supervisor level
Restricted From: Sales operations, invoicing, payments, accounting, user management, audit logs, system settings
Typical Users: Production manager, Warehouse supervisor, Operations lead
sales — Sales Manager
The sales role manages customer relationships, sales transactions, and accounts receivable. Access is focused on customer-facing and revenue modules.
Capabilities:
- Dashboard access (statistics, charts, alerts)
- Customers: Full CRUD + account statements
- Sales: Full CRUD (recording sales transactions)
- Invoicing: Create, update, send invoices via email
- Payments: Record and manage AR payments
- Inventory: Read-only (view stock levels for order availability)
- Suppliers: Read-only (view supplier details)
- Requisitions: Can create and approve at supervisor level
Restricted From: Inventory management, production operations, purchasing, accounting, user management, audit logs, system settings
Typical Users: Sales manager, Sales representative, Account manager
accountant — Financial Accountant
The accountant role manages all financial operations including expenses, general ledger, and financial reporting.
Capabilities:
- Dashboard access (statistics, charts, alerts)
- Expenses: Full CRUD + approval workflow
- Accounting Module (full access):
- General Ledger (GL) with date filtering
- Journal entries (create, list, reverse)
- Payment vouchers (create, list)
- Trial Balance report
- Profit & Loss statement
- Balance Sheet report
- Requisitions: Can approve at accounts level
- Consolidated Reports: Full access
Restricted From: Inventory management, production, sales operations, customer management, user management, audit logs, system settings
Typical Users: Chief accountant, Financial controller, Accounts officer
Permission Matrix
| Module | Feature | Admin | Production | Sales | Accountant |
|---|---|---|---|---|---|
| Dashboard | View statistics | ✅ | ✅ | ✅ | ✅ |
| View charts | ✅ | ✅ | ✅ | ✅ | |
| View alerts | ✅ | ✅ | ✅ | ✅ | |
| Users | List users | ✅ | ❌ | ❌ | ❌ |
| Create user | ✅ | ❌ | ❌ | ❌ | |
| Update user | ✅ | ❌ | ❌ | ❌ | |
| Delete user | ✅ | ❌ | ❌ | ❌ | |
| Inventory | List/View | ✅ | ✅ | ✅ (read) | ❌ |
| Create item | ✅ | ✅ | ❌ | ❌ | |
| Update item | ✅ | ✅ | ❌ | ❌ | |
| Delete item | ✅ | ✅ | ❌ | ❌ | |
| Adjust stock | ✅ | ✅ | ❌ | ❌ | |
| View alerts | ✅ | ✅ | ❌ | ❌ | |
| Purchases | List/View | ✅ | ✅ | ❌ | ❌ |
| Create purchase | ✅ | ✅ | ❌ | ❌ | |
| Update purchase | ✅ | ✅ | ❌ | ❌ | |
| Delete purchase | ✅ | ✅ | ❌ | ❌ | |
| Production | List/View | ✅ | ✅ | ❌ | ❌ |
| Create run | ✅ | ✅ | ❌ | ❌ | |
| Update run | ✅ | ✅ | ❌ | ❌ | |
| Delete run | ✅ | ✅ | ❌ | ❌ | |
| Sales | List/View | ✅ | ❌ | ✅ | ❌ |
| Create sale | ✅ | ❌ | ✅ | ❌ | |
| Update sale | ✅ | ❌ | ✅ | ❌ | |
| Delete sale | ✅ | ❌ | ✅ | ❌ | |
| Customers | List/View | ✅ | ✅ (read) | ✅ | ❌ |
| Create customer | ✅ | ❌ | ✅ | ❌ | |
| Update customer | ✅ | ❌ | ✅ | ❌ | |
| Delete customer | ✅ | ❌ | ✅ | ❌ | |
| View statement | ✅ | ❌ | ✅ | ❌ | |
| Suppliers | List/View | ✅ | ✅ (read) | ✅ (read) | ❌ |
| Create supplier | ✅ | ❌ | ❌ | ❌ | |
| Update supplier | ✅ | ❌ | ❌ | ❌ | |
| Delete supplier | ✅ | ❌ | ❌ | ❌ | |
| Expenses | List/View | ✅ | ❌ | ❌ | ✅ |
| Create expense | ✅ | ❌ | ❌ | ✅ | |
| Update expense | ✅ | ❌ | ❌ | ✅ | |
| Delete expense | ✅ | ❌ | ❌ | ✅ | |
| Approve/Reject | ✅ | ❌ | ❌ | ✅ | |
| AR Invoices | List/View | ✅ | ❌ | ✅ | ❌ |
| Create invoice | ✅ | ❌ | ✅ | ❌ | |
| Update invoice | ✅ | ❌ | ✅ | ❌ | |
| Delete invoice | ✅ | ❌ | ✅ | ❌ | |
| Send email | ✅ | ❌ | ✅ | ❌ | |
| AR Payments | List/View | ✅ | ❌ | ✅ | ❌ |
| Record payment | ✅ | ❌ | ✅ | ❌ | |
| Delete payment | ✅ | ❌ | ✅ | ❌ | |
| Accounting | General Ledger | ✅ | ❌ | ❌ | ✅ |
| Trial Balance | ✅ | ❌ | ❌ | ✅ | |
| Profit & Loss | ✅ | ❌ | ❌ | ✅ | |
| Balance Sheet | ✅ | ❌ | ❌ | ✅ | |
| Journal Entries (C) | ✅ | ❌ | ❌ | ✅ | |
| Journal Entries (R) | ✅ | ❌ | ❌ | ✅ | |
| Reverse Entry | ✅ | ❌ | ❌ | ✅ | |
| Vouchers (C) | ✅ | ❌ | ❌ | ✅ | |
| Vouchers (R) | ✅ | ❌ | ❌ | ✅ | |
| Requisitions | Create | ✅ | ✅ | ✅ | ✅ |
| Supervisor approve | ✅ | ✅ | ✅ | ✅ | |
| Manager approve | ✅ | ✅ | ✅ | ✅ | |
| Accounts approve | ✅ | ❌ | ❌ | ✅ | |
| MD approve | ✅ | ❌ | ❌ | ❌ | |
| Reports | Consolidated | ✅ | ✅ | ✅ | ✅ |
| Customer statement | ✅ | ❌ | ✅ | ❌ | |
| Activity | View audit log | ✅ | ❌ | ❌ | ❌ |
| Settings | Manage settings | ✅ | ❌ | ❌ | ❌ |
Legend: ✅ = Full access (Read + Write + Delete), ✅ (read) = Read-only, ❌ = No access
Route-Level Protection
Every protected API route has server-side middleware that validates:
- Authentication: A valid JWT token is present in the
Authorizationheader. - Role Authorization: The token's
roleclaim matches one of the route's allowed roles.
Implementation Pattern
// Authentication middleware — validates JWT
const authenticate = (req, res, next) => {
const token = req.headers.authorization?.split(' ')[1];
if (!token) return res.status(401).json({ error: 'Unauthorized' });
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (err) {
return res.status(401).json({ error: 'Invalid token' });
}
};
// Authorization middleware — checks role
const authorize = (...roles) => {
return (req, res, next) => {
if (!roles.includes(req.user.role)) {
return res.status(403).json({ error: 'Forbidden' });
}
next();
};
};
// Usage: admin and production roles can access this route
router.get('/api/inventory', authenticate, authorize('admin', 'production'), getInventory);
Role-Route Mapping
| Route Pattern | Allowed Roles |
|---|---|
POST /api/auth/login | Public (no auth) |
POST /api/auth/forgot-password | Public (no auth) |
POST /api/auth/reset-password | Public (no auth) |
GET /api/auth/me | Any authenticated user |
GET/POST/PUT/DELETE /api/users/* | admin |
GET /api/inventory | admin, production, sales |
POST/PUT/DELETE /api/inventory/* | admin, production |
POST /api/inventory/:id/adjust | admin, production |
GET /api/purchases | admin, production |
POST/PUT/DELETE /api/purchases/* | admin, production |
GET /api/production | admin, production |
POST/PUT/DELETE /api/production/* | admin, production |
GET /api/sales | admin, sales |
POST/PUT/DELETE /api/sales/* | admin, sales |
GET /api/customers | admin, production, sales |
POST/PUT/DELETE /api/customers/* | admin, sales |
GET /api/suppliers | admin, production, sales |
POST/PUT/DELETE /api/suppliers/* | admin |
GET /api/expenses | admin, accountant |
POST/PUT/DELETE /api/expenses/* | admin, accountant |
GET /api/ar/* | admin, sales |
POST/PUT/DELETE /api/ar/* | admin, sales |
GET /api/accounting/* | admin, accountant |
POST /api/accounting/* | admin, accountant |
GET /api/dashboard/* | Any authenticated user |
GET /api/activity | admin |
GET /api/reports/* | Any authenticated user |
GET /api/customers/:id/statement | Any authenticated user |
Component-Level Protection
Client-side rendering uses role-based conditional checks to show/hide UI elements. This is a user experience enhancement, not a security mechanism.
Pattern
const Dashboard = () => {
const { user } = useAuth();
return (
<div>
<h1>Dashboard</h1>
{/* Only admins see the Activity Log link */}
{user.role === 'admin' && (
<NavLink to="/activity">Activity Log</NavLink>
)}
{/* Only production role sees inventory management */}
{(user.role === 'admin' || user.role === 'production') && (
<NavLink to="/inventory">Inventory</NavLink>
)}
{/* Only sales role sees customer management */}
{(user.role === 'admin' || user.role === 'sales') && (
<NavLink to="/customers">Customers</NavLink>
)}
{/* Only accountant sees accounting module */}
{(user.role === 'admin' || user.role === 'accountant') && (
<NavLink to="/accounting">Accounting</NavLink>
)}
</div>
);
};
Important Warning
Client-side checks are for UX convenience only. A user with a valid JWT can bypass all UI restrictions by making direct API calls (e.g., via
curlor browser DevTools). All security enforcement must happen server-side.
Data Isolation
Each role sees only the data relevant to their function:
| Role | Data Visibility |
|---|---|
| admin | All data across all modules |
| production | Full inventory and production data; customer and supplier names only (for context) |
| sales | Full customer, sales, and invoice data; inventory view-only (for availability checking) |
| accountant | Full expense and accounting data; no direct access to inventory or sales transaction details |
Implementation: Data filtering is applied at the query level. For example, the sales endpoint returns customer names and product names to provide context, but the accountant endpoint does not expose individual sales transaction details — only aggregated financial data.
Requisition Approval Workflow
The requisition system implements a multi-level approval workflow where each level must approve before the next level can act.
Workflow Stages
┌──────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ Requester │────►│ Supervisor │────►│ Manager │
│ (creates) │ │ (first review) │ │ (second review) │
└──────────────┘ └──────────────────┘ └──────────────────┘
│
┌──────────────────────────────┘
▼
┌──────────────────┐ ┌──────────────────┐
│ Accounts │────►│ MD │
│ (financial) │ │ (final approval)│
└──────────────────┘ └──────────────────┘
Approval Roles
| Stage | Role Required | Actions | Notes |
|---|---|---|---|
| Create | Any user | Submit requisition | Sets initial status to pending |
| Supervisor | Supervisor assigned | Approve or Reject | Status → supervisor_approved or rejected |
| Manager | Manager assigned | Approve or Reject | Only after supervisor approval |
| Accounts | Accountant role | Approve or Reject | Only after manager approval |
| MD | Managing Director | Approve or Reject | Only after accounts approval — final authority |
Status Progression
pending → supervisor_approved → manager_approved → accounts_approved → md_approved
│ │ │ │ │
└──► rejected ◄┘──────► rejected ◄┘──────► rejected ◄┘──────► rejected ◄┘
Key Rules
- Sequential enforcement: Each level can only act after the previous level has approved.
- Rejection at any stage: Rejecting a requisition immediately terminates the workflow — the requisition cannot proceed further.
- Any role can reject: Even if the current approver is not the assigned person, the admin can override.
- Notes required: Each approver can add notes explaining their decision.
- Timestamp tracking: Each approval/rejection is timestamped for audit purposes.
Security Recommendations
Principle of Least Privilege
Every user should have the minimum level of access necessary to perform their job function.
Implementation:
- Assign roles based on job responsibilities, not seniority.
- Default new accounts to the most restrictive role.
- Require explicit approval for role changes.
- Periodically review role assignments against actual job functions.
Regular Access Reviews
| Review Type | Frequency | Responsible |
|---|---|---|
| User account audit | Monthly | Admin |
| Role assignment review | Quarterly | Admin + HR |
| Inactive account cleanup | Monthly | Admin |
| Privileged access review | Quarterly | Admin |
| API key rotation | Quarterly | Admin / DevOps |
Immediate Deactivation
When an employee leaves the company or changes roles:
- Immediately set their account status to
inactive(within 1 hour). - Review all API access tokens issued to the user.
- Check for any data exports or unusual activity in the audit log.
- Reassign any pending requisitions or approvals to their successor.
- If the user had admin access, rotate all system secrets (JWT, API keys).
Separation of Duties for Financial Operations
Financial controls require that no single individual can both initiate and approve financial transactions:
| Operation | Initiator | Approver |
|---|---|---|
| Expense payment | Accountant | Admin or senior accountant |
| Requisition | Any user | Supervisor → Manager → Accounts → MD |
| Journal entry reversal | Accountant | Different accountant or Admin |
| Credit limit change | Sales | Admin |
Rationale: Separation of duties prevents fraud and errors by ensuring that no single person can complete a financial transaction without oversight.
Access Control Maintenance Checklist
- All user accounts have exactly one role
- No shared user accounts exist
- Inactive accounts are disabled within 24 hours of employee departure
- Admin accounts are limited to essential personnel only
- Password policies are enforced
- Audit logs are reviewed weekly for unauthorized access attempts
- API keys are rotated quarterly
- Database access is restricted to application and admin users only
- SSH access is limited to authorized personnel
- Role changes require documented approval