// backend/middleware/auth.js const jwt = require('jsonwebtoken'); const SECRET = process.env.JWT_SECRET || 'sap-portal-secret'; function verifyToken(req, res, next) { const auth = req.headers.authorization || ''; const token = auth.startsWith('Bearer ') ? auth.slice(7) : auth; if (!token) return res.status(401).json({ success: false, message: 'No token provided' }); try { req.user = jwt.verify(token, SECRET); next(); } catch { res.status(401).json({ success: false, message: 'Invalid or expired token' }); } } // Only sap_adder can approve → push to SAP B1 function verifyAdmin(req, res, next) { if (req.user?.role === 'admin' || req.user?.role === 'sap_adder') return next(); return res.status(403).json({ success: false, message: 'SAP Adder or Admin role required' }); } // User-administration gate: full Admin, SAP Adder (historically also manages // users) and the System Admin role (user management ONLY — deliberately NOT part // of verifyAdmin, so sub-admins can't touch system settings / approvals). function verifyUserAdmin(req, res, next) { if (['admin', 'sap_adder', 'system_admin'].includes(req.user?.role)) return next(); return res.status(403).json({ success: false, message: 'Admin, SAP Adder or System Admin role required' }); } // Every step assignment grants a set of perms. Legacy entries are plain // strings ("work_order:prepared_qa") from before per-step perms existed — // normalize them to the full perm set so nobody's access silently shrinks. // 'approve' is distinct from 'edit': performing the sign-off action at a // stage (e.g. "did QC check it, yes/no") is NOT the same capability as // editing the record's own fields (product/batch/materials/etc.) — someone // can be trusted to check a stage off without being able to alter the data. const ALL_PERMS = ['view', 'add', 'edit', 'approve', 'delete']; function normalizeSteps(raw) { const arr = Array.isArray(raw) ? raw : []; return arr.map(s => { if (typeof s === 'string') return { step: s, perms: ALL_PERMS.slice() }; if (s && typeof s === 'object' && s.step) return { step: s.step, perms: Array.isArray(s.perms) ? s.perms : ALL_PERMS.slice() }; return null; }).filter(Boolean); } // Does this user hold `perm` on the exact step `fullKey` ("workflow:key")? function hasStepPerm(user, fullKey, perm) { if (user?.role === 'admin') return true; const steps = normalizeSteps(user?.approvalSteps); const entry = steps.find(s => s.step === fullKey); return !!entry && entry.perms.includes(perm); } // Does this user hold `perm` on ANY step within `workflow`? Used for // workflow-wide actions like viewing a list, where the specific stage // doesn't matter — holding the perm on one step is enough. function hasWorkflowPerm(user, workflow, perm) { if (user?.role === 'admin') return true; const steps = normalizeSteps(user?.approvalSteps); return steps.some(s => s.step.startsWith(`${workflow}:`) && s.perms.includes(perm)); } // Generic Approval Steps gate — 'workflow:key' (see services/approvalStepsStore.js). // Admin always passes. Everyone else needs `perm` (default 'view') on this // exact step in req.user.approvalSteps (baked into the JWT at login). function requireApprovalStep(fullKey, perm = 'view') { return (req, res, next) => { if (hasStepPerm(req.user, fullKey, perm)) return next(); res.status(403).json({ success: false, message: `You are not assigned "${perm}" on approval step: ${fullKey}` }); }; } // Same as above but workflow-wide (any step in the workflow grants access) — // for actions like listing/viewing that aren't tied to one specific stage. function requireWorkflowPerm(workflow, perm = 'view') { return (req, res, next) => { if (hasWorkflowPerm(req.user, workflow, perm)) return next(); res.status(403).json({ success: false, message: `You are not assigned "${perm}" on workflow: ${workflow}` }); }; } // "Is this step assigned to the user at all?" — true if the user holds the // step with ANY permission (view/add/edit/approve/delete). Used for actions // (Issuance, Receipt, Close…) where being assigned the step means the user // may perform it, regardless of which specific perm boxes were ticked. function hasStepAssigned(user, fullKey) { if (user && user.role === 'admin') return true; return ALL_PERMS.some(p => hasStepPerm(user, fullKey, p)); } function requireStepAssigned(fullKey) { return (req, res, next) => { if (hasStepAssigned(req.user, fullKey)) return next(); res.status(403).json({ success: false, message: `You are not assigned to approval step: ${fullKey}` }); }; } module.exports = { verifyToken, verifyAdmin, verifyUserAdmin, requireApprovalStep, requireWorkflowPerm, requireStepAssigned, hasStepAssigned, normalizeSteps, hasStepPerm, hasWorkflowPerm, ALL_PERMS, };