|
|
@@ -3,7 +3,36 @@ import { db } from "../db/knex.js";
|
|
|
import { signAccessToken, generateRefreshToken, hashRefreshToken } from "../services/authTokens.js";
|
|
|
import { config } from "../config/index.js";
|
|
|
|
|
|
-async function recordFailedAttempt(_login) {}
|
|
|
+const _failedAttempts = new Map();
|
|
|
+const MAX_ATTEMPTS = 5;
|
|
|
+const BLOCK_MS = 15 * 60 * 1000;
|
|
|
+
|
|
|
+function recordFailedAttempt(login) {
|
|
|
+ const now = Date.now();
|
|
|
+ const entry = _failedAttempts.get(login) ?? { count: 0, blockedUntil: 0 };
|
|
|
+ entry.count += 1;
|
|
|
+ if (entry.count >= MAX_ATTEMPTS) {
|
|
|
+ entry.blockedUntil = now + BLOCK_MS;
|
|
|
+ entry.count = 0;
|
|
|
+ }
|
|
|
+ _failedAttempts.set(login, entry);
|
|
|
+}
|
|
|
+
|
|
|
+function isLoginBlocked(login) {
|
|
|
+ const entry = _failedAttempts.get(login);
|
|
|
+ if (!entry) return false;
|
|
|
+ if (entry.blockedUntil > Date.now()) return true;
|
|
|
+ if (entry.blockedUntil > 0) _failedAttempts.delete(login);
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
+function requireAdmin(req, res) {
|
|
|
+ if (String(req.user?.nivel) !== "3") {
|
|
|
+ res.status(403).send({ status: false, msg: "Acesso negado. Apenas administradores podem realizar esta ação." });
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+}
|
|
|
|
|
|
function formatarUsuario(usuario) {
|
|
|
return {
|
|
|
@@ -18,7 +47,8 @@ function formatarUsuario(usuario) {
|
|
|
}
|
|
|
|
|
|
export const UsuarioController = {
|
|
|
- Listar: async function (_req, res, next) {
|
|
|
+ Listar: async function (req, res, next) {
|
|
|
+ if (!requireAdmin(req, res)) return;
|
|
|
try {
|
|
|
const usuarios = await db("usuarios")
|
|
|
.select("Id", "Nome", "Login", "Email", "Status", "Nivel", "Setor")
|
|
|
@@ -47,13 +77,17 @@ export const UsuarioController = {
|
|
|
const loginInformado = String(loginBody).trim();
|
|
|
const senhaInformada = String(senhaBody);
|
|
|
|
|
|
+ if (isLoginBlocked(loginInformado)) {
|
|
|
+ return res.status(429).send({ status: false, msg: "Muitas tentativas incorretas. Tente novamente em 15 minutos." });
|
|
|
+ }
|
|
|
+
|
|
|
const usuario = await db("usuarios")
|
|
|
.where(loginInformado.includes("@") ? { Email: loginInformado } : { Login: loginInformado })
|
|
|
.first();
|
|
|
|
|
|
if (!usuario) {
|
|
|
- await recordFailedAttempt(loginInformado);
|
|
|
- return res.status(401).send({ status: false, msg: "Usuario não localizado!" });
|
|
|
+ recordFailedAttempt(loginInformado);
|
|
|
+ return res.status(401).send({ status: false, msg: "Combinação de usuário e senha inválida!" });
|
|
|
}
|
|
|
|
|
|
if (String(usuario.Status) === "0") {
|
|
|
@@ -62,10 +96,12 @@ export const UsuarioController = {
|
|
|
|
|
|
const passwordIsValid = bcrypt.compareSync(senhaInformada, String(usuario.Senha ?? ""));
|
|
|
if (!passwordIsValid) {
|
|
|
- await recordFailedAttempt(loginInformado);
|
|
|
- return res.status(401).send({ status: false, msg: "Combinacao de usuario e senha invalida!" });
|
|
|
+ recordFailedAttempt(loginInformado);
|
|
|
+ return res.status(401).send({ status: false, msg: "Combinação de usuário e senha inválida!" });
|
|
|
}
|
|
|
|
|
|
+ _failedAttempts.delete(loginInformado);
|
|
|
+
|
|
|
let accessToken = null;
|
|
|
let refreshToken = null;
|
|
|
|
|
|
@@ -140,6 +176,7 @@ export const UsuarioController = {
|
|
|
},
|
|
|
|
|
|
Criar: async function (req, res, next) {
|
|
|
+ if (!requireAdmin(req, res)) return;
|
|
|
try {
|
|
|
const { Nome, Login, Email, Senha, Nivel, Setor } = req.body ?? {};
|
|
|
|
|
|
@@ -147,6 +184,10 @@ export const UsuarioController = {
|
|
|
return res.status(400).send({ status: false, msg: "Todos os campos são obrigatórios!" });
|
|
|
}
|
|
|
|
|
|
+ if (String(Senha).length < 6) {
|
|
|
+ return res.status(400).send({ status: false, msg: "Senha deve ter pelo menos 6 caracteres!" });
|
|
|
+ }
|
|
|
+
|
|
|
const loginTrimado = String(Login).trim();
|
|
|
const existente = await db("usuarios").where({ Login: loginTrimado }).first();
|
|
|
if (existente) {
|
|
|
@@ -172,6 +213,7 @@ export const UsuarioController = {
|
|
|
},
|
|
|
|
|
|
Atualizar: async function (req, res, next) {
|
|
|
+ if (!requireAdmin(req, res)) return;
|
|
|
try {
|
|
|
const id = Number(req.params.id);
|
|
|
if (!id) return res.status(400).send({ status: false, msg: "ID inválido!" });
|
|
|
@@ -198,6 +240,7 @@ export const UsuarioController = {
|
|
|
},
|
|
|
|
|
|
ToggleStatus: async function (req, res, next) {
|
|
|
+ if (!requireAdmin(req, res)) return;
|
|
|
try {
|
|
|
const id = Number(req.params.id);
|
|
|
if (!id) return res.status(400).send({ status: false, msg: "ID inválido!" });
|