186 lines
5.6 KiB
JavaScript
186 lines
5.6 KiB
JavaScript
const jwt = require('jsonwebtoken');
|
|
const path = require('path');
|
|
const db = require('../database');
|
|
const authConfig = require('../config/auth');
|
|
|
|
// ── Rate limit de login (em memória) — proteção anti força bruta ──
|
|
const LOGIN_MAX = 10; // tentativas falhas permitidas
|
|
const LOGIN_JANELA_MS = 15 * 60 * 1000; // por janela de 15 minutos
|
|
const loginTentativas = new Map();
|
|
function chaveLogin(req, login) { return (req.ip || '') + '|' + (login || ''); }
|
|
function loginBloqueado(req, login) {
|
|
const e = loginTentativas.get(chaveLogin(req, login));
|
|
if (!e) return false;
|
|
if (Date.now() - e.inicio > LOGIN_JANELA_MS) { loginTentativas.delete(chaveLogin(req, login)); return false; }
|
|
return e.count >= LOGIN_MAX;
|
|
}
|
|
function registrarFalhaLogin(req, login) {
|
|
const k = chaveLogin(req, login);
|
|
const agora = Date.now();
|
|
const e = loginTentativas.get(k);
|
|
if (!e || agora - e.inicio > LOGIN_JANELA_MS) loginTentativas.set(k, { count: 1, inicio: agora });
|
|
else e.count++;
|
|
}
|
|
function limparTentativasLogin(req, login) { loginTentativas.delete(chaveLogin(req, login)); }
|
|
|
|
class AuthController {
|
|
/**
|
|
* Exibe a página de login
|
|
* Endpoint: GET /app/:alias/login
|
|
*/
|
|
static async loginPage(req, res) {
|
|
const loginPath = path.resolve(__dirname, '../public/login.html');
|
|
res.sendFile(loginPath);
|
|
}
|
|
|
|
/**
|
|
* Login do usuário no aplicativo
|
|
* Endpoint: POST /app/:alias/login
|
|
* Body: { USU_LOGIN, USU_SENHA }
|
|
*/
|
|
static async login(req, res) {
|
|
try {
|
|
const { alias } = req.params;
|
|
const { USU_LOGIN, USU_SENHA } = req.body;
|
|
|
|
// Validação dos campos obrigatórios
|
|
if (!USU_LOGIN || !USU_SENHA) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Os campos USU_LOGIN e USU_SENHA são obrigatórios.',
|
|
});
|
|
}
|
|
|
|
if (loginBloqueado(req, USU_LOGIN)) {
|
|
return res.status(429).json({
|
|
success: false,
|
|
error: 'Muitas tentativas de login. Aguarde alguns minutos e tente novamente.',
|
|
});
|
|
}
|
|
|
|
// Busca o usuário pelo login
|
|
const sql = `
|
|
SELECT
|
|
USU_CODIGO_ID,
|
|
USU_NIVEL_ID,
|
|
USU_NOME,
|
|
USU_LOGIN,
|
|
USU_EMAIL,
|
|
USU_STATUS,
|
|
USU_ACESSO_WEB,
|
|
USU_CAMINHO_FOTO,
|
|
USU_DT_ULTIMO_ACESSO,
|
|
USU_TIPO,
|
|
USU_TIPO_CHAT
|
|
FROM USUARIOS
|
|
WHERE USU_LOGIN = ?
|
|
AND (USU_SENHA = ? OR USU_SENHA_WEB = ?)
|
|
AND COALESCE(USU_ACESSO_WEB, '0') IN ('1', 'S')
|
|
AND USU_STATUS = 'A'
|
|
`;
|
|
|
|
const result = await db.query(alias, sql, [USU_LOGIN, USU_SENHA, USU_SENHA]);
|
|
|
|
if (result.length === 0) {
|
|
registrarFalhaLogin(req, USU_LOGIN);
|
|
return res.status(401).json({
|
|
success: false,
|
|
error: 'Credenciais inválidas ou usuário sem permissão de acesso.',
|
|
});
|
|
}
|
|
|
|
// Login bem-sucedido: zera o contador de tentativas
|
|
limparTentativasLogin(req, USU_LOGIN);
|
|
|
|
const user = result[0];
|
|
|
|
// Busca as empresas que o usuário tem acesso
|
|
const empresas = await db.query(alias,
|
|
'SELECT USE_EMPRESA_ID FROM USUARIOS_EMPRESA WHERE USE_USUARIO_ID = ?',
|
|
[user.USU_CODIGO_ID]
|
|
);
|
|
const empresasIds = empresas.map(e => Number(e.USE_EMPRESA_ID)).filter(n => !isNaN(n));
|
|
|
|
// Atualiza a data do último acesso
|
|
await db.execute(
|
|
alias,
|
|
`UPDATE USUARIOS SET USU_DT_ULTIMO_ACESSO = CURRENT_DATE WHERE USU_CODIGO_ID = ?`,
|
|
[user.USU_CODIGO_ID]
|
|
);
|
|
|
|
// Dados do usuário para o token
|
|
const payload = {
|
|
id: user.USU_CODIGO_ID,
|
|
nivelId: user.USU_NIVEL_ID,
|
|
nome: user.USU_NOME.trim(),
|
|
login: user.USU_LOGIN.trim(),
|
|
email: user.USU_EMAIL ? user.USU_EMAIL.trim() : null,
|
|
tipo: user.USU_TIPO ? user.USU_TIPO.trim() : null,
|
|
tipoChat: user.USU_TIPO_CHAT ? user.USU_TIPO_CHAT.trim() : null,
|
|
alias,
|
|
empresas: empresasIds, // empresas que o usuário pode acessar
|
|
};
|
|
|
|
// Gera o token JWT
|
|
const token = jwt.sign(payload, authConfig.secret, {
|
|
expiresIn: authConfig.expiresIn,
|
|
issuer: authConfig.issuer,
|
|
});
|
|
|
|
// Retorna o token e dados do usuário
|
|
return res.json({
|
|
success: true,
|
|
message: 'Login realizado com sucesso.',
|
|
alias,
|
|
token,
|
|
expiresIn: authConfig.expiresIn,
|
|
user: {
|
|
...payload,
|
|
foto: user.USU_CAMINHO_FOTO ? user.USU_CAMINHO_FOTO.trim() : null,
|
|
ultimoAcesso: user.USU_DT_ULTIMO_ACESSO,
|
|
},
|
|
});
|
|
} catch (err) {
|
|
console.error('Erro no login:', err);
|
|
return res.status(500).json({
|
|
success: false,
|
|
error: `Erro interno ao realizar login: ${err.message}`,
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Valida o token atual e retorna dados atualizados do usuário
|
|
* Endpoint: GET /app/:alias/me
|
|
*/
|
|
static async me(req, res) {
|
|
try {
|
|
return res.json({
|
|
success: true,
|
|
alias: req.params.alias,
|
|
user: {
|
|
...req.user,
|
|
authType: req.authType || 'jwt',
|
|
},
|
|
});
|
|
} catch (err) {
|
|
console.error('Erro ao buscar usuário:', err);
|
|
return res.status(500).json({
|
|
success: false,
|
|
error: `Erro ao buscar dados do usuário: ${err.message}`,
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Dashboard do app
|
|
* Endpoint: GET /app/:alias/dashboard
|
|
*/
|
|
static async dashboard(req, res) {
|
|
const dashboardPath = path.resolve(__dirname, '../public/dashboard.html');
|
|
res.sendFile(dashboardPath);
|
|
}
|
|
}
|
|
|
|
module.exports = AuthController;
|