106 lines
4.7 KiB
JavaScript
106 lines
4.7 KiB
JavaScript
// 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,
|
|
};
|