|
@@ -1,297 +1,119 @@
|
|
|
import bcrypt from "bcryptjs";
|
|
import bcrypt from "bcryptjs";
|
|
|
-import { z } from "zod";
|
|
|
|
|
import { db } from "../db/knex.js";
|
|
import { db } from "../db/knex.js";
|
|
|
-import { signAccessToken, generateRefreshToken, hashRefreshToken } from "../services/authTokens.js";
|
|
|
|
|
-import { config } from "../config/index.js";
|
|
|
|
|
|
|
+import { formatarUsuario } from "../utils/formatarUsuario.js";
|
|
|
|
|
|
|
|
-const criarSchema = z.object({
|
|
|
|
|
- Nome: z.string().min(1).max(100),
|
|
|
|
|
- Login: z.string().min(3).max(50),
|
|
|
|
|
- Email: z.string().email().max(150),
|
|
|
|
|
- Senha: z.string().min(6).max(128),
|
|
|
|
|
- Nivel: z.enum(["1", "2", "3"]),
|
|
|
|
|
- Setor: z.string().min(1).max(100)
|
|
|
|
|
-});
|
|
|
|
|
-
|
|
|
|
|
-const atualizarSchema = z.object({
|
|
|
|
|
- Nome: z.string().min(1).max(100).optional(),
|
|
|
|
|
- Email: z.string().email().max(150).optional(),
|
|
|
|
|
- Nivel: z.enum(["1", "2", "3"]).optional(),
|
|
|
|
|
- Setor: z.string().min(1).max(100).optional()
|
|
|
|
|
-}).refine(obj => Object.values(obj).some(v => v !== undefined), {
|
|
|
|
|
- message: "Nenhum campo para atualizar"
|
|
|
|
|
-});
|
|
|
|
|
-
|
|
|
|
|
-const senhaSchema = z.object({
|
|
|
|
|
- senhaAtual: z.string().min(1),
|
|
|
|
|
- senhaNova: z.string().min(6).max(128)
|
|
|
|
|
-});
|
|
|
|
|
-
|
|
|
|
|
-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;
|
|
|
|
|
|
|
+export const UsuarioController = {
|
|
|
|
|
+ Listar: async function (req, res, next) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const usuarios = await db("usuarios")
|
|
|
|
|
+ .select("Id", "Nome", "Login", "Email", "Status", "Nivel", "Setor")
|
|
|
|
|
+ .orderBy("Nome", "asc");
|
|
|
|
|
+
|
|
|
|
|
+ return res.status(200).send({
|
|
|
|
|
+ status: true,
|
|
|
|
|
+ items: usuarios.map(formatarUsuario)
|
|
|
|
|
+ });
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ return next(error);
|
|
|
}
|
|
}
|
|
|
- _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;
|
|
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ Criar: async function (req, res, next) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const { Nome, Login, Email, Senha, Nivel, Setor } = req.body;
|
|
|
|
|
+
|
|
|
|
|
+ const loginTrimado = Login.trim();
|
|
|
|
|
+ const existente = await db("usuarios").where({ Login: loginTrimado }).first();
|
|
|
|
|
+ if (existente) {
|
|
|
|
|
+ return res.status(409).send({ status: false, msg: "Login já cadastrado!" });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const senhaHash = await bcrypt.hash(Senha, 10);
|
|
|
|
|
+ const [id] = await db("usuarios").insert({
|
|
|
|
|
+ Nome: Nome.trim(),
|
|
|
|
|
+ Login: loginTrimado,
|
|
|
|
|
+ Email: Email.trim(),
|
|
|
|
|
+ Senha: senhaHash,
|
|
|
|
|
+ Status: "1",
|
|
|
|
|
+ Nivel,
|
|
|
|
|
+ Setor: Setor.trim()
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ const usuario = await db("usuarios").where({ Id: id }).first();
|
|
|
|
|
+ return res.status(201).send({ status: true, usuario: formatarUsuario(usuario) });
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ return next(error);
|
|
|
}
|
|
}
|
|
|
- return true;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-function formatarUsuario(usuario) {
|
|
|
|
|
- return {
|
|
|
|
|
- Id: usuario.Id,
|
|
|
|
|
- Nome: usuario.Nome,
|
|
|
|
|
- Login: usuario.Login,
|
|
|
|
|
- Email: usuario.Email,
|
|
|
|
|
- Status: usuario.Status,
|
|
|
|
|
- Nivel: usuario.Nivel,
|
|
|
|
|
- Setor: usuario.Setor
|
|
|
|
|
- };
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-export const UsuarioController = {
|
|
|
|
|
- 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")
|
|
|
|
|
- .orderBy("Nome", "asc");
|
|
|
|
|
-
|
|
|
|
|
- return res.status(200).send({
|
|
|
|
|
- status: true,
|
|
|
|
|
- items: usuarios.map(formatarUsuario)
|
|
|
|
|
- });
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- return next(error);
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
-
|
|
|
|
|
- Login: async function (req, res, next) {
|
|
|
|
|
- try {
|
|
|
|
|
- const { Login, login, Senha, senha } = req.body ?? {};
|
|
|
|
|
-
|
|
|
|
|
- const loginBody = Login ?? login;
|
|
|
|
|
- const senhaBody = Senha ?? senha;
|
|
|
|
|
-
|
|
|
|
|
- if (!loginBody || !senhaBody) {
|
|
|
|
|
- return res.status(401).send({ status: false, msg: "Informacoes faltando para Login!" });
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- 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) {
|
|
|
|
|
- recordFailedAttempt(loginInformado);
|
|
|
|
|
- return res.status(401).send({ status: false, msg: "Combinação de usuário e senha inválida!" });
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (String(usuario.Status) === "0") {
|
|
|
|
|
- return res.status(401).send({ status: false, msg: "Usuario inativo!" });
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const passwordIsValid = bcrypt.compareSync(senhaInformada, String(usuario.Senha ?? ""));
|
|
|
|
|
- if (!passwordIsValid) {
|
|
|
|
|
- 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;
|
|
|
|
|
-
|
|
|
|
|
- if (config.jwt.secret) {
|
|
|
|
|
- accessToken = signAccessToken({ sub: usuario.Id, login: usuario.Login, nivel: usuario.Nivel });
|
|
|
|
|
- refreshToken = generateRefreshToken();
|
|
|
|
|
- const tokenHash = hashRefreshToken(refreshToken);
|
|
|
|
|
- const expiresAt = new Date(Date.now() + config.jwt.refreshTtlSeconds * 1000);
|
|
|
|
|
- await db("refresh_tokens").insert({
|
|
|
|
|
- UsuarioId: usuario.Id,
|
|
|
|
|
- TokenHash: tokenHash,
|
|
|
|
|
- ExpiresAt: expiresAt
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- return res.status(200).send({
|
|
|
|
|
- status: true,
|
|
|
|
|
- msg: "Login realizado com sucesso!",
|
|
|
|
|
- usuario: formatarUsuario(usuario),
|
|
|
|
|
- ...(accessToken ? { accessToken, refreshToken } : {})
|
|
|
|
|
- });
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- return next(error);
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
-
|
|
|
|
|
- Refresh: async function (req, res, next) {
|
|
|
|
|
- try {
|
|
|
|
|
- const { refreshToken } = req.body ?? {};
|
|
|
|
|
- if (!refreshToken) {
|
|
|
|
|
- return res.status(401).send({ status: false, msg: "Refresh token ausente!" });
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const tokenHash = hashRefreshToken(refreshToken);
|
|
|
|
|
- const stored = await db("refresh_tokens")
|
|
|
|
|
- .where({ TokenHash: tokenHash })
|
|
|
|
|
- .whereNull("RevokedAt")
|
|
|
|
|
- .where("ExpiresAt", ">", new Date())
|
|
|
|
|
- .first();
|
|
|
|
|
-
|
|
|
|
|
- if (!stored) {
|
|
|
|
|
- return res.status(401).send({ status: false, msg: "Refresh token inválido ou expirado!" });
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const usuario = await db("usuarios").where({ Id: stored.UsuarioId }).first();
|
|
|
|
|
- if (!usuario || String(usuario.Status) === "0") {
|
|
|
|
|
- return res.status(401).send({ status: false, msg: "Usuario inativo!" });
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const accessToken = signAccessToken({ sub: usuario.Id, login: usuario.Login, nivel: usuario.Nivel });
|
|
|
|
|
-
|
|
|
|
|
- return res.status(200).send({ status: true, accessToken });
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- return next(error);
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
-
|
|
|
|
|
- Logout: async function (req, res, next) {
|
|
|
|
|
- try {
|
|
|
|
|
- const { refreshToken } = req.body ?? {};
|
|
|
|
|
- if (refreshToken) {
|
|
|
|
|
- const tokenHash = hashRefreshToken(refreshToken);
|
|
|
|
|
- await db("refresh_tokens")
|
|
|
|
|
- .where({ TokenHash: tokenHash })
|
|
|
|
|
- .whereNull("RevokedAt")
|
|
|
|
|
- .update({ RevokedAt: new Date() });
|
|
|
|
|
- }
|
|
|
|
|
- return res.status(200).send({ status: true, msg: "Logout realizado com sucesso!" });
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- return next(error);
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
-
|
|
|
|
|
- Criar: async function (req, res, next) {
|
|
|
|
|
- if (!requireAdmin(req, res)) return;
|
|
|
|
|
- try {
|
|
|
|
|
- const { Nome, Login, Email, Senha, Nivel, Setor } = criarSchema.parse(req.body ?? {});
|
|
|
|
|
-
|
|
|
|
|
- const loginTrimado = Login.trim();
|
|
|
|
|
- const existente = await db("usuarios").where({ Login: loginTrimado }).first();
|
|
|
|
|
- if (existente) {
|
|
|
|
|
- return res.status(409).send({ status: false, msg: "Login já cadastrado!" });
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const senhaHash = await bcrypt.hash(Senha, 10);
|
|
|
|
|
- const [id] = await db("usuarios").insert({
|
|
|
|
|
- Nome: Nome.trim(),
|
|
|
|
|
- Login: loginTrimado,
|
|
|
|
|
- Email: Email.trim(),
|
|
|
|
|
- Senha: senhaHash,
|
|
|
|
|
- Status: "1",
|
|
|
|
|
- Nivel,
|
|
|
|
|
- Setor: Setor.trim()
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- const usuario = await db("usuarios").where({ Id: id }).first();
|
|
|
|
|
- return res.status(201).send({ status: true, usuario: formatarUsuario(usuario) });
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- return next(error);
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
-
|
|
|
|
|
- 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!" });
|
|
|
|
|
-
|
|
|
|
|
- const { Nome, Email, Nivel, Setor } = atualizarSchema.parse(req.body ?? {});
|
|
|
|
|
- const updates = {};
|
|
|
|
|
- if (Nome !== undefined) updates.Nome = Nome.trim();
|
|
|
|
|
- if (Email !== undefined) updates.Email = Email.trim();
|
|
|
|
|
- if (Nivel !== undefined) updates.Nivel = Nivel;
|
|
|
|
|
- if (Setor !== undefined) updates.Setor = Setor.trim();
|
|
|
|
|
-
|
|
|
|
|
- await db("usuarios").where({ Id: id }).update(updates);
|
|
|
|
|
- const usuario = await db("usuarios").where({ Id: id }).first();
|
|
|
|
|
- if (!usuario) return res.status(404).send({ status: false, msg: "Usuário não encontrado!" });
|
|
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ Atualizar: async function (req, res, next) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const id = Number(req.params.id);
|
|
|
|
|
+ if (!id) return res.status(400).send({ status: false, msg: "ID inválido!" });
|
|
|
|
|
+
|
|
|
|
|
+ const { Nome, Email, Nivel, Setor } = req.body;
|
|
|
|
|
+ const updates = {};
|
|
|
|
|
+ if (Nome !== undefined) updates.Nome = Nome.trim();
|
|
|
|
|
+ if (Email !== undefined) updates.Email = Email.trim();
|
|
|
|
|
+ if (Nivel !== undefined) updates.Nivel = Nivel;
|
|
|
|
|
+ if (Setor !== undefined) updates.Setor = Setor.trim();
|
|
|
|
|
+
|
|
|
|
|
+ await db("usuarios").where({ Id: id }).update(updates);
|
|
|
|
|
+ const usuario = await db("usuarios").where({ Id: id }).first();
|
|
|
|
|
+ if (!usuario) return res.status(404).send({ status: false, msg: "Usuário não encontrado!" });
|
|
|
|
|
+
|
|
|
|
|
+ return res.status(200).send({ status: true, usuario: formatarUsuario(usuario) });
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ return next(error);
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
|
|
|
- return res.status(200).send({ status: true, usuario: formatarUsuario(usuario) });
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- return next(error);
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
|
|
+ ToggleStatus: async function (req, res, next) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const id = Number(req.params.id);
|
|
|
|
|
+ if (!id) return res.status(400).send({ status: false, msg: "ID inválido!" });
|
|
|
|
|
|
|
|
- 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!" });
|
|
|
|
|
|
|
+ const usuario = await db("usuarios").where({ Id: id }).first();
|
|
|
|
|
+ if (!usuario) return res.status(404).send({ status: false, msg: "Usuário não encontrado!" });
|
|
|
|
|
|
|
|
- const usuario = await db("usuarios").where({ Id: id }).first();
|
|
|
|
|
- if (!usuario) return res.status(404).send({ status: false, msg: "Usuário não encontrado!" });
|
|
|
|
|
|
|
+ const novoStatus = String(usuario.Status) === "1" ? "0" : "1";
|
|
|
|
|
+ await db("usuarios").where({ Id: id }).update({ Status: novoStatus });
|
|
|
|
|
|
|
|
- const novoStatus = String(usuario.Status) === "1" ? "0" : "1";
|
|
|
|
|
- await db("usuarios").where({ Id: id }).update({ Status: novoStatus });
|
|
|
|
|
|
|
+ return res.status(200).send({ status: true, novoStatus });
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ return next(error);
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
|
|
|
- return res.status(200).send({ status: true, novoStatus });
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- return next(error);
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
|
|
+ AlterarSenha: async function (req, res, next) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const id = Number(req.params.id);
|
|
|
|
|
+ if (!id) return res.status(400).send({ status: false, msg: "ID inválido!" });
|
|
|
|
|
|
|
|
- AlterarSenha: async function (req, res, next) {
|
|
|
|
|
- try {
|
|
|
|
|
- const id = Number(req.params.id);
|
|
|
|
|
- if (!id) return res.status(400).send({ status: false, msg: "ID inválido!" });
|
|
|
|
|
|
|
+ const callerIsOwner = String(req.user?.sub) === String(id);
|
|
|
|
|
+ const callerIsAdmin = String(req.user?.nivel) === "3";
|
|
|
|
|
+ if (!callerIsOwner && !callerIsAdmin) {
|
|
|
|
|
+ return res.status(403).send({ status: false, msg: "Sem permissão para alterar a senha deste usuário." });
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- const { senhaAtual, senhaNova } = senhaSchema.parse(req.body ?? {});
|
|
|
|
|
|
|
+ const { senhaAtual, senhaNova } = req.body;
|
|
|
|
|
|
|
|
- const usuario = await db("usuarios").where({ Id: id }).first();
|
|
|
|
|
- if (!usuario) return res.status(404).send({ status: false, msg: "Usuário não encontrado!" });
|
|
|
|
|
|
|
+ const usuario = await db("usuarios").where({ Id: id }).first();
|
|
|
|
|
+ if (!usuario) return res.status(404).send({ status: false, msg: "Usuário não encontrado!" });
|
|
|
|
|
|
|
|
- const senhaValida = bcrypt.compareSync(senhaAtual, String(usuario.Senha ?? ""));
|
|
|
|
|
- if (!senhaValida) {
|
|
|
|
|
- return res.status(401).send({ status: false, msg: "Senha atual incorreta!" });
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ const senhaValida = bcrypt.compareSync(senhaAtual, String(usuario.Senha ?? ""));
|
|
|
|
|
+ if (!senhaValida) {
|
|
|
|
|
+ return res.status(401).send({ status: false, msg: "Senha atual incorreta!" });
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- const novaHash = bcrypt.hashSync(senhaNova, 10);
|
|
|
|
|
- await db("usuarios").where({ Id: id }).update({ Senha: novaHash });
|
|
|
|
|
|
|
+ const novaHash = bcrypt.hashSync(senhaNova, 10);
|
|
|
|
|
+ await db("usuarios").where({ Id: id }).update({ Senha: novaHash });
|
|
|
|
|
|
|
|
- return res.status(200).send({ status: true, msg: "Senha alterada com sucesso!" });
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- return next(error);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ return res.status(200).send({ status: true, msg: "Senha alterada com sucesso!" });
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ return next(error);
|
|
|
}
|
|
}
|
|
|
|
|
+ }
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
export default UsuarioController;
|
|
export default UsuarioController;
|