diff options
| author | zwlucas <lucas.fariamo08@gmail.com> | 2026-07-25 21:23:29 +0000 |
|---|---|---|
| committer | zwlucas <lucas.fariamo08@gmail.com> | 2026-07-25 21:23:29 +0000 |
| commit | ef55d9bb24430610b60c1b77821307cd72c08c55 (patch) | |
| tree | e6935a810b34370b0a4c821e55f6397aac50c832 /api/src/modules/auth/auth.controller.ts | |
| download | helpdesk-master.tar.gz helpdesk-master.zip | |
Signed-off-by: zwlucas <lucas.fariamo08@gmail.com>
Diffstat (limited to 'api/src/modules/auth/auth.controller.ts')
| -rw-r--r-- | api/src/modules/auth/auth.controller.ts | 114 |
1 files changed, 114 insertions, 0 deletions
diff --git a/api/src/modules/auth/auth.controller.ts b/api/src/modules/auth/auth.controller.ts new file mode 100644 index 0000000..8aec643 --- /dev/null +++ b/api/src/modules/auth/auth.controller.ts @@ -0,0 +1,114 @@ +/** + * auth.controller.ts + * Lógica de negócio de autenticação. + */ + +import bcrypt from 'bcryptjs'; +import type { FastifyReply, FastifyRequest } from 'fastify'; +import { + findUserByEmail, + findUserById, + createUser, +} from './auth.repository.js'; +import type { RegisterBody, LoginBody } from './auth.schema.js'; + +const SALT_ROUNDS = 12; + +// ─── POST /auth/register ────────────────────────────────────────────────────── + +export async function registerHandler( + request: FastifyRequest<{ Body: RegisterBody }>, + reply: FastifyReply, +) { + const { name, email, password, role } = request.body; + const db = request.server.db; + + // Verifica duplicidade de e-mail + const existing = await findUserByEmail(db, email); + if (existing) { + return reply.conflict('Este e-mail já está cadastrado.'); + } + + // Apenas admins podem criar agentes ou outros admins + if ((role === 'agent' || role === 'admin')) { + const callerUser = request.user as { role?: string } | undefined; + if (!callerUser || callerUser.role !== 'admin') { + return reply.forbidden('Apenas administradores podem criar agentes ou admins.'); + } + } + + // Hash da senha (bcrypt com salt 12 ≈ ~250ms — seguro e aceitável) + const hashedPassword = await bcrypt.hash(password, SALT_ROUNDS); + + const user = await createUser(db, { + name, + email, + password: hashedPassword, + role, + }); + + // Assina o JWT com o payload mínimo necessário + const token = await reply.jwtSign({ + id: user.id, + email: user.email, + role: user.role, + }); + + return reply.code(201).send({ token, user }); +} + +// ─── POST /auth/login ───────────────────────────────────────────────────────── + +export async function loginHandler( + request: FastifyRequest<{ Body: LoginBody }>, + reply: FastifyReply, +) { + const { email, password } = request.body; + const db = request.server.db; + + const user = await findUserByEmail(db, email); + + // Mensagem genérica: não revela se o e-mail existe ou não (segurança) + const INVALID_MSG = 'E-mail ou senha incorretos.'; + + if (!user) { + // Executa bcrypt mesmo sem usuário para evitar timing attacks + await bcrypt.compare(password, '$2b$12$invalidhashplaceholderthatnevermatchesanypassword'); + return reply.unauthorized(INVALID_MSG); + } + + if (!user.isActive) { + return reply.forbidden('Conta desativada. Entre em contato com o administrador.'); + } + + const isPasswordValid = await bcrypt.compare(password, user.password); + if (!isPasswordValid) { + return reply.unauthorized(INVALID_MSG); + } + + const token = await reply.jwtSign({ + id: user.id, + email: user.email, + role: user.role, + }); + + // Retorna o user sem o campo password + const { password: _pwd, ...safeUser } = user; + return reply.send({ token, user: safeUser }); +} + +// ─── GET /auth/me ───────────────────────────────────────────────────────────── + +export async function meHandler( + request: FastifyRequest, + reply: FastifyReply, +) { + // request.user foi injetado pelo decorator authenticate + const user = await findUserById(request.server.db, request.user.id); + + if (!user) { + return reply.unauthorized('Usuário não encontrado.'); + } + + return reply.send(user); +} |