Ajuste de cadastro/gerenciamento de usuarios e seguranca de endpoints
continuous-integration/webhook Falha no deploy (rx.scoreodonto.com)
continuous-integration/webhook Falha no deploy (rx.scoreodonto.com)
This commit is contained in:
@@ -68,6 +68,7 @@ function generateToken(user, expiresIn = JWT_EXPIRES_IN) {
|
||||
{
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
is_admin: user.is_admin
|
||||
},
|
||||
JWT_SECRET,
|
||||
|
||||
@@ -44,13 +44,20 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
try {
|
||||
const response = await fetch(API_URL, fetchOptions);
|
||||
|
||||
// Se recebeu 401 ou 403, token expirou — vai para login
|
||||
if (response.status === 401 || response.status === 403) {
|
||||
// Se recebeu 401, token expirou ou é inválido — vai para login
|
||||
if (response.status === 401) {
|
||||
localStorage.removeItem('auth_token');
|
||||
window.location.href = '/login';
|
||||
return;
|
||||
}
|
||||
|
||||
// Se recebeu 403, acesso proibido (não admin) — volta para a home
|
||||
if (response.status === 403) {
|
||||
alert('Acesso negado. Esta área é restrita a administradores.');
|
||||
window.location.href = '/';
|
||||
return;
|
||||
}
|
||||
|
||||
// Verificar se a resposta é realmente JSON (evita erro com HTML)
|
||||
const contentType = response.headers.get('content-type') || '';
|
||||
if (!contentType.includes('application/json')) {
|
||||
|
||||
@@ -55,6 +55,8 @@ async function verifyAuth() {
|
||||
if (data.valid) {
|
||||
const isAdmin = !!data.user.is_admin;
|
||||
localStorage.setItem('is_admin', isAdmin ? 'true' : 'false');
|
||||
localStorage.setItem('username', data.user.username || '');
|
||||
localStorage.setItem('email', data.user.email || '');
|
||||
|
||||
// Controle de visibilidade de itens de menu
|
||||
updateAdminMenuVisibility(isAdmin);
|
||||
@@ -1122,9 +1124,10 @@ function openSettingsModal() {
|
||||
const modal = document.getElementById('settingsModal');
|
||||
if (modal) modal.style.display = 'block';
|
||||
|
||||
// Limpar form
|
||||
// Limpar form e carregar valores atuais
|
||||
document.getElementById('currentPassword').value = '';
|
||||
document.getElementById('newUsername').value = '';
|
||||
document.getElementById('newUsername').value = localStorage.getItem('username') || '';
|
||||
document.getElementById('newEmail').value = localStorage.getItem('email') || '';
|
||||
document.getElementById('newPassword').value = '';
|
||||
}
|
||||
|
||||
@@ -1138,6 +1141,7 @@ async function updateCredentials(event) {
|
||||
|
||||
const currentPassword = document.getElementById('currentPassword').value;
|
||||
const newUsername = document.getElementById('newUsername').value.trim();
|
||||
const newEmail = document.getElementById('newEmail').value.trim();
|
||||
const newPassword = document.getElementById('newPassword').value;
|
||||
|
||||
if (!currentPassword) {
|
||||
@@ -1145,8 +1149,8 @@ async function updateCredentials(event) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!newUsername && !newPassword) {
|
||||
showToast('Preencha um novo usuário ou nova senha.', 'error');
|
||||
if (!newUsername && !newEmail && !newPassword) {
|
||||
showToast('Preencha um novo usuário, e-mail ou nova senha.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1163,7 +1167,7 @@ async function updateCredentials(event) {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${token}`
|
||||
},
|
||||
body: JSON.stringify({ currentPassword, newUsername, newPassword })
|
||||
body: JSON.stringify({ currentPassword, newUsername, newEmail, newPassword })
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
@@ -1178,6 +1182,11 @@ async function updateCredentials(event) {
|
||||
// Atualizar token no localStorage se tiver um novo
|
||||
if (data.token) {
|
||||
localStorage.setItem('auth_token', data.token);
|
||||
// Salvar novos dados locais decodificando token ou usando retorno
|
||||
if (data.user) {
|
||||
localStorage.setItem('username', data.user.username || '');
|
||||
localStorage.setItem('email', data.user.email || '');
|
||||
}
|
||||
}
|
||||
|
||||
closeSettingsModal();
|
||||
@@ -1208,6 +1217,8 @@ function openUserManagementModal() {
|
||||
document.getElementById('new_username').value = '';
|
||||
document.getElementById('new_email').value = '';
|
||||
document.getElementById('new_password').value = '';
|
||||
const isCheck = document.getElementById('new_is_admin');
|
||||
if (isCheck) isCheck.checked = false;
|
||||
|
||||
loadUsersList();
|
||||
}
|
||||
@@ -1275,6 +1286,8 @@ async function createUser(event) {
|
||||
const username = document.getElementById('new_username').value.trim();
|
||||
const email = document.getElementById('new_email').value.trim();
|
||||
const password = document.getElementById('new_password').value;
|
||||
const isCheck = document.getElementById('new_is_admin');
|
||||
const is_admin = isCheck ? isCheck.checked : false;
|
||||
|
||||
if (!username || !email || !password) {
|
||||
showToast('Todos os campos obrigatórios.', 'error');
|
||||
@@ -1294,7 +1307,7 @@ async function createUser(event) {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${token}`
|
||||
},
|
||||
body: JSON.stringify({ username, email, password })
|
||||
body: JSON.stringify({ username, email, password, is_admin })
|
||||
});
|
||||
const data = await res.json();
|
||||
|
||||
@@ -1306,6 +1319,7 @@ async function createUser(event) {
|
||||
document.getElementById('new_username').value = '';
|
||||
document.getElementById('new_email').value = '';
|
||||
document.getElementById('new_password').value = '';
|
||||
if (isCheck) isCheck.checked = false;
|
||||
|
||||
// Atualizar lista
|
||||
loadUsersList();
|
||||
|
||||
@@ -287,8 +287,25 @@
|
||||
}
|
||||
|
||||
async function loadClients() {
|
||||
const token = localStorage.getItem('auth_token') || '';
|
||||
try {
|
||||
const response = await fetch('/api/socket/status');
|
||||
const response = await fetch('/api/socket/status', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
|
||||
if (response.status === 401) {
|
||||
localStorage.removeItem('auth_token');
|
||||
window.location.href = '/login';
|
||||
return;
|
||||
}
|
||||
if (response.status === 403) {
|
||||
alert('Acesso negado. Esta área é restrita a administradores.');
|
||||
window.location.href = '/';
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
// Atualizar stats
|
||||
@@ -404,6 +421,8 @@
|
||||
testBtn.disabled = true;
|
||||
testBtn.innerHTML = '<span class="loading"></span> Testando...';
|
||||
|
||||
const token = localStorage.getItem('auth_token') || '';
|
||||
|
||||
try {
|
||||
// Limpar resultados anteriores
|
||||
testResults = {};
|
||||
@@ -413,10 +432,22 @@
|
||||
const response = await fetch('/api/socket/test-connection', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
|
||||
if (response.status === 401) {
|
||||
localStorage.removeItem('auth_token');
|
||||
window.location.href = '/login';
|
||||
return;
|
||||
}
|
||||
if (response.status === 403) {
|
||||
alert('Acesso negado. Esta área é restrita a administradores.');
|
||||
window.location.href = '/';
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
// Processar resultados
|
||||
|
||||
@@ -311,7 +311,12 @@
|
||||
<input type="text" id="newUsername" class="form-control" placeholder="Deixe em branco para não alterar">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="form-group" style="margin-top: 15px;">
|
||||
<label for="newEmail">Novo E-mail de Acesso</label>
|
||||
<input type="email" id="newEmail" class="form-control" placeholder="Deixe em branco para não alterar">
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="margin-top: 15px;">
|
||||
<label for="newPassword">Nova Senha</label>
|
||||
<input type="password" id="newPassword" class="form-control" placeholder="Deixe em branco para não alterar">
|
||||
</div>
|
||||
@@ -349,6 +354,10 @@
|
||||
<label style="display:block; margin-bottom:6px; font-weight:600; color:var(--text-primary);">Senha *</label>
|
||||
<input type="password" id="new_password" required placeholder="Senha de acesso" style="width: 100%; padding: 12px; border: 1px solid var(--border-color); border-radius: 8px; outline:none; background:var(--bg-color); color:var(--text-primary);">
|
||||
</div>
|
||||
<div class="form-group" style="margin-bottom: 16px; display: flex; align-items: center; gap: 8px;">
|
||||
<input type="checkbox" id="new_is_admin" style="width: 18px; height: 18px; cursor: pointer; margin: 0;">
|
||||
<label for="new_is_admin" style="font-weight:600; color:var(--text-primary); cursor: pointer; user-select: none; margin: 0;">Permissão de Administrador</label>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary" style="width: 100%; margin-top: 12px; padding: 12px; border-radius: 8px; font-weight: 600; background:var(--primary-color); color:white; border:none; cursor:pointer;">
|
||||
Cadastrar Usuário
|
||||
</button>
|
||||
|
||||
@@ -121,7 +121,7 @@ router.post('/create-user', authenticateToken, async (req, res) => {
|
||||
return res.status(403).json({ error: 'Apenas administradores podem criar usuários' });
|
||||
}
|
||||
|
||||
const { username, email, password, clinic_name, pc_name } = req.body;
|
||||
const { username, email, password, clinic_name, pc_name, is_admin } = req.body;
|
||||
|
||||
if (!username || !email || !password) {
|
||||
return res.status(400).json({
|
||||
@@ -146,9 +146,9 @@ router.post('/create-user', authenticateToken, async (req, res) => {
|
||||
|
||||
// Criar usuário
|
||||
const result = await db.run(
|
||||
`INSERT INTO users (username, email, password_hash, clinic_name, pc_name, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, NOW())`,
|
||||
[username, email, passwordHash, clinic_name || null, pc_name || null]
|
||||
`INSERT INTO users (username, email, password_hash, clinic_name, pc_name, is_admin, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, NOW())`,
|
||||
[username, email, passwordHash, clinic_name || null, pc_name || null, !!is_admin]
|
||||
);
|
||||
|
||||
res.json({
|
||||
@@ -217,14 +217,14 @@ router.post('/users/:id/token', authenticateToken, async (req, res) => {
|
||||
|
||||
router.put('/update-credentials', authenticateToken, async (req, res) => {
|
||||
try {
|
||||
const { currentPassword, newUsername, newPassword } = req.body;
|
||||
const { currentPassword, newUsername, newEmail, newPassword } = req.body;
|
||||
const userId = req.user.id;
|
||||
|
||||
if (!currentPassword) {
|
||||
return res.status(400).json({ error: 'A senha atual é obrigatória' });
|
||||
}
|
||||
|
||||
if (!newUsername && !newPassword) {
|
||||
if (!newUsername && !newEmail && !newPassword) {
|
||||
return res.status(400).json({ error: 'Nenhum dado novo foi fornecido para atualização' });
|
||||
}
|
||||
|
||||
@@ -241,6 +241,7 @@ router.put('/update-credentials', authenticateToken, async (req, res) => {
|
||||
}
|
||||
|
||||
let finalUsername = user.username;
|
||||
let finalEmail = user.email;
|
||||
let finalPasswordHash = user.password_hash;
|
||||
|
||||
// Verificar e aplicar novo username se fornecido
|
||||
@@ -252,6 +253,15 @@ router.put('/update-credentials', authenticateToken, async (req, res) => {
|
||||
finalUsername = newUsername;
|
||||
}
|
||||
|
||||
// Verificar e aplicar novo e-mail se fornecido
|
||||
if (newEmail && newEmail !== user.email) {
|
||||
const existing = await db.get('SELECT * FROM users WHERE email = ? AND id != ?', [newEmail, userId]);
|
||||
if (existing) {
|
||||
return res.status(409).json({ error: 'Este e-mail já está em uso' });
|
||||
}
|
||||
finalEmail = newEmail;
|
||||
}
|
||||
|
||||
// Verificar e aplicar nova senha se fornecido
|
||||
if (newPassword) {
|
||||
if (newPassword.length < 4) {
|
||||
@@ -262,12 +272,12 @@ router.put('/update-credentials', authenticateToken, async (req, res) => {
|
||||
|
||||
// Atualizar no banco de dados
|
||||
await db.run(
|
||||
'UPDATE users SET username = ?, password_hash = ? WHERE id = ?',
|
||||
[finalUsername, finalPasswordHash, userId]
|
||||
'UPDATE users SET username = ?, email = ?, password_hash = ? WHERE id = ?',
|
||||
[finalUsername, finalEmail, finalPasswordHash, userId]
|
||||
);
|
||||
|
||||
// Gerar novo token pois o username pode ter mudado
|
||||
const updatedUser = { id: userId, username: finalUsername, email: user.email };
|
||||
// Gerar novo token pois o username ou e-mail podem ter mudado
|
||||
const updatedUser = { id: userId, username: finalUsername, email: finalEmail, is_admin: user.is_admin };
|
||||
const newToken = generateToken(updatedUser);
|
||||
|
||||
res.json({
|
||||
|
||||
+11
-3
@@ -132,8 +132,16 @@ app.post('/api/debug-log', (req, res) => {
|
||||
res.json({ success: true });
|
||||
});
|
||||
|
||||
// Middleware para verificar se o usuário autenticado é admin
|
||||
const requireAdmin = (req, res, next) => {
|
||||
if (req.user && req.user.is_admin) {
|
||||
return next();
|
||||
}
|
||||
return res.status(403).json({ error: 'Acesso negado. Apenas administradores.' });
|
||||
};
|
||||
|
||||
// Status de conexões Socket.IO
|
||||
app.get('/api/socket/status', (req, res) => {
|
||||
app.get('/api/socket/status', authenticateToken, requireAdmin, (req, res) => {
|
||||
const sockets = Array.from(io.sockets.sockets.values());
|
||||
const clients = sockets.map(socket => ({
|
||||
id: socket.id,
|
||||
@@ -158,7 +166,7 @@ app.get('/api/socket/status', (req, res) => {
|
||||
});
|
||||
|
||||
// Endpoint para forçar identificação de um cliente (debug)
|
||||
app.post('/api/socket/request-identify/:socketId', (req, res) => {
|
||||
app.post('/api/socket/request-identify/:socketId', authenticateToken, requireAdmin, (req, res) => {
|
||||
const { socketId } = req.params;
|
||||
const socket = io.sockets.sockets.get(socketId);
|
||||
|
||||
@@ -178,7 +186,7 @@ app.post('/api/socket/request-identify/:socketId', (req, res) => {
|
||||
});
|
||||
|
||||
// Endpoint para teste de conexão Socket.IO
|
||||
app.post('/api/socket/test-connection', async (req, res) => {
|
||||
app.post('/api/socket/test-connection', authenticateToken, requireAdmin, async (req, res) => {
|
||||
try {
|
||||
const sockets = Array.from(io.sockets.sockets.values());
|
||||
const totalConnections = sockets.length;
|
||||
|
||||
Reference in New Issue
Block a user