Skip to main content

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

ModuleFeatureAdminProductionSalesAccountant
DashboardView statistics
View charts
View alerts
UsersList users
Create user
Update user
Delete user
InventoryList/View✅ (read)
Create item
Update item
Delete item
Adjust stock
View alerts
PurchasesList/View
Create purchase
Update purchase
Delete purchase
ProductionList/View
Create run
Update run
Delete run
SalesList/View
Create sale
Update sale
Delete sale
CustomersList/View✅ (read)
Create customer
Update customer
Delete customer
View statement
SuppliersList/View✅ (read)✅ (read)
Create supplier
Update supplier
Delete supplier
ExpensesList/View
Create expense
Update expense
Delete expense
Approve/Reject
AR InvoicesList/View
Create invoice
Update invoice
Delete invoice
Send email
AR PaymentsList/View
Record payment
Delete payment
AccountingGeneral Ledger
Trial Balance
Profit & Loss
Balance Sheet
Journal Entries (C)
Journal Entries (R)
Reverse Entry
Vouchers (C)
Vouchers (R)
RequisitionsCreate
Supervisor approve
Manager approve
Accounts approve
MD approve
ReportsConsolidated
Customer statement
ActivityView audit log
SettingsManage 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:

  1. Authentication: A valid JWT token is present in the Authorization header.
  2. Role Authorization: The token's role claim 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 PatternAllowed Roles
POST /api/auth/loginPublic (no auth)
POST /api/auth/forgot-passwordPublic (no auth)
POST /api/auth/reset-passwordPublic (no auth)
GET /api/auth/meAny authenticated user
GET/POST/PUT/DELETE /api/users/*admin
GET /api/inventoryadmin, production, sales
POST/PUT/DELETE /api/inventory/*admin, production
POST /api/inventory/:id/adjustadmin, production
GET /api/purchasesadmin, production
POST/PUT/DELETE /api/purchases/*admin, production
GET /api/productionadmin, production
POST/PUT/DELETE /api/production/*admin, production
GET /api/salesadmin, sales
POST/PUT/DELETE /api/sales/*admin, sales
GET /api/customersadmin, production, sales
POST/PUT/DELETE /api/customers/*admin, sales
GET /api/suppliersadmin, production, sales
POST/PUT/DELETE /api/suppliers/*admin
GET /api/expensesadmin, 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/activityadmin
GET /api/reports/*Any authenticated user
GET /api/customers/:id/statementAny 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 curl or browser DevTools). All security enforcement must happen server-side.


Data Isolation

Each role sees only the data relevant to their function:

RoleData Visibility
adminAll data across all modules
productionFull inventory and production data; customer and supplier names only (for context)
salesFull customer, sales, and invoice data; inventory view-only (for availability checking)
accountantFull 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

StageRole RequiredActionsNotes
CreateAny userSubmit requisitionSets initial status to pending
SupervisorSupervisor assignedApprove or RejectStatus → supervisor_approved or rejected
ManagerManager assignedApprove or RejectOnly after supervisor approval
AccountsAccountant roleApprove or RejectOnly after manager approval
MDManaging DirectorApprove or RejectOnly 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 TypeFrequencyResponsible
User account auditMonthlyAdmin
Role assignment reviewQuarterlyAdmin + HR
Inactive account cleanupMonthlyAdmin
Privileged access reviewQuarterlyAdmin
API key rotationQuarterlyAdmin / DevOps

Immediate Deactivation

When an employee leaves the company or changes roles:

  1. Immediately set their account status to inactive (within 1 hour).
  2. Review all API access tokens issued to the user.
  3. Check for any data exports or unusual activity in the audit log.
  4. Reassign any pending requisitions or approvals to their successor.
  5. 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:

OperationInitiatorApprover
Expense paymentAccountantAdmin or senior accountant
RequisitionAny userSupervisor → Manager → Accounts → MD
Journal entry reversalAccountantDifferent accountant or Admin
Credit limit changeSalesAdmin

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