Add backend policy tests and API client split
All checks were successful
Build and Deploy / build-and-push (push) Successful in 3m8s

This commit is contained in:
Cauê Faleiros
2026-05-28 16:00:30 -03:00
parent 5648dc7986
commit aa59e642af
11 changed files with 298 additions and 171 deletions

View File

@@ -10,6 +10,15 @@ const multer = require('multer');
const { v4: uuidv4 } = require('uuid');
const fs = require('fs');
const pool = require('./db');
const { stripEnvQuotes, hashSecret, maskSecret } = require('./utils/security');
const {
canReadUser,
canUpdateUser,
canManageUserStatus,
canChangeUserEmail,
canManageUserRoleOrTeam,
canReadAttendance,
} = require('./policies/accessPolicy');
const app = express();
const PORT = process.env.PORT || 3001;
@@ -20,9 +29,6 @@ if (!JWT_SECRET) {
throw new Error('JWT_SECRET is required in production.');
}
const stripEnvQuotes = (value = '') => value.replace(/^"|"$/g, '');
const hashSecret = (value) => crypto.createHash('sha256').update(value).digest('hex');
const maskSecret = (id, value) => `masked:${id}:${value.slice(-6)}`;
const USER_PUBLIC_FIELDS = 'id, tenant_id, team_id, name, email, slug, role, status, bio, avatar_url, sound_enabled, created_at';
const transporter = nodemailer.createTransport({
@@ -489,18 +495,7 @@ apiRouter.get('/users/:idOrSlug', async (req, res) => {
if (!rows || rows.length === 0) return res.status(404).json({ error: 'Not found' });
if (!req.user || !req.user.role) return res.status(401).json({ error: 'Não autenticado' });
if (req.user.role !== 'super_admin' && rows[0].tenant_id !== req.user.tenant_id) {
return res.status(403).json({ error: 'Acesso negado.' });
}
if (req.user.role === 'agent' && rows[0].id !== req.user.id) {
return res.status(403).json({ error: 'Acesso negado.' });
}
if (req.user.role === 'manager') {
const canSeeUser = rows[0].id === req.user.id || (req.user.team_id && rows[0].team_id === req.user.team_id);
if (!canSeeUser) return res.status(403).json({ error: 'Acesso negado.' });
}
if (!canReadUser(req.user, rows[0])) return res.status(403).json({ error: 'Acesso negado.' });
res.json(rows[0]);
} catch (error) {
@@ -580,30 +575,14 @@ apiRouter.put('/users/:id', async (req, res) => {
const [existing] = await pool.query('SELECT * FROM users WHERE id = ?', [req.params.id]);
if (existing.length === 0) return res.status(404).json({ error: 'Not found' });
const isSelf = req.user.id === req.params.id;
const isManagerOrAdmin = ['admin', 'manager', 'super_admin'].includes(req.user.role);
const isAdmin = ['admin', 'super_admin'].includes(req.user.role);
if (!isSelf && !isManagerOrAdmin) {
return res.status(403).json({ error: 'Acesso negado.' });
}
if (req.user.role !== 'super_admin' && existing[0].tenant_id !== req.user.tenant_id) {
return res.status(403).json({ error: 'Acesso negado.' });
}
if (!canUpdateUser(req.user, existing[0])) return res.status(403).json({ error: 'Acesso negado.' });
// Only Admins can change roles and teams. Managers can only edit basic info of their team members.
const finalRole = isAdmin && role !== undefined ? role : existing[0].role;
const finalTeamId = isAdmin && team_id !== undefined ? team_id : existing[0].team_id;
if (req.user.role === 'manager') {
const canEditUser = existing[0].id !== req.user.id && req.user.team_id && existing[0].team_id === req.user.team_id && existing[0].role === 'agent';
if (!isSelf && !canEditUser) return res.status(403).json({ error: 'Acesso negado.' });
}
const finalStatus = isAdmin && status !== undefined ? status : existing[0].status;
const canChangeEmail = isSelf || isAdmin;
const finalEmail = canChangeEmail && email !== undefined ? email : existing[0].email;
const finalSoundEnabled = isSelf && sound_enabled !== undefined ? sound_enabled : (existing[0].sound_enabled ?? true);
const finalRole = canManageUserRoleOrTeam(req.user) && role !== undefined ? role : existing[0].role;
const finalTeamId = canManageUserRoleOrTeam(req.user) && team_id !== undefined ? team_id : existing[0].team_id;
const finalStatus = canManageUserStatus(req.user) && status !== undefined ? status : existing[0].status;
const finalEmail = canChangeUserEmail(req.user, existing[0]) && email !== undefined ? email : existing[0].email;
const finalSoundEnabled = req.user.id === req.params.id && sound_enabled !== undefined ? sound_enabled : (existing[0].sound_enabled ?? true);
if (finalEmail !== existing[0].email) {
const [emailCheck] = await pool.query('SELECT id FROM users WHERE email = ? AND id != ?', [finalEmail, req.params.id]);
@@ -1126,17 +1105,7 @@ apiRouter.get('/attendances/:id', async (req, res) => {
);
if (rows.length === 0) return res.status(404).json({ error: 'Not found' });
if (req.user.role !== 'super_admin' && rows[0].tenant_id !== req.user.tenant_id) {
return res.status(403).json({ error: 'Acesso negado.' });
}
if (req.user.role === 'agent' && rows[0].user_id !== req.user.id) {
return res.status(403).json({ error: 'Acesso negado.' });
}
if (req.user.role === 'manager' && (!req.user.team_id || rows[0].team_id !== req.user.team_id)) {
return res.status(403).json({ error: 'Acesso negado.' });
}
if (!canReadAttendance(req.user, rows[0])) return res.status(403).json({ error: 'Acesso negado.' });
const r = rows[0];
res.json({