Add backend policy tests and API client split
All checks were successful
Build and Deploy / build-and-push (push) Successful in 3m8s
All checks were successful
Build and Deploy / build-and-push (push) Successful in 3m8s
This commit is contained in:
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user