import { Request, Response, NextFunction } from "express"; import { AuthenticatedRequest } from "@app/middleware/auth"; import { login, generateToken, requestPasswordReset, resetPassword, activateUser, issueActivationCode, } from "@app/services/auth.service"; import { verifyToken } from "@app/lib/jwt"; import { findUserByEmail, findUserById, findUserByActivationCode, } from "@app/services/user.service"; import { LoginSchema, PasswordResetSchema, PasswordResetRequestSchema, ActivateUserSchema, } from "@app/schemas"; import { formatValidationError } from "@app/lib/errors/validationError"; import { publicUser } from "@app/lib/serializers/user"; import { getQueue, MailJobName } from "@app/lib/queue"; export const AuthController = { login: async (req: Request, res: Response, next: NextFunction) => { try { const parsed = LoginSchema.safeParse(req.body); if (!parsed.success) { return res.status(400).json(formatValidationError(parsed.error!)); } const { user, token } = await login(parsed.data); if ( !user.active && new Date() > new Date(user.activationCodeExpiresAt!) ) { const code = await issueActivationCode(user.id); const mailQueue = getQueue("mail"); const job = await mailQueue.add(MailJobName.ActivateAccountEmail, { userId: user.id, code: code, }); } return res.json({ token, user: publicUser(user), }); } catch (err: any) { if (err.message === "Invalid credentials") { return res.status(401).json({ error: "Invalid credentials" }); } next(err); } }, forgotPassword: async (req: Request, res: Response, next: NextFunction) => { try { const parsed = PasswordResetRequestSchema.safeParse(req.body); if (!parsed.success) { return res.status(400).json(formatValidationError(parsed.error!)); } const result = await requestPasswordReset(parsed.data); return res.status(200).json(result); } catch (err) { next(err); } }, resetPassword: async (req: Request, res: Response, next: NextFunction) => { try { const parsed = PasswordResetSchema.safeParse(req.body); if (!parsed.success) { return res.status(400).json(formatValidationError(parsed.error!)); } const result = await resetPassword(parsed.data); return res.status(200).json(result); } catch (err: any) { if (err.message.includes("token")) { return res.status(400).json({ error: err.message }); } next(err); } }, verify: async (req: Request, res: Response, next: NextFunction) => { try { const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith("Bearer ")) { return res.status(401).json({ error: "No token provided" }); } const token = authHeader.substring(7); const payload = verifyToken(token); const user = await findUserByEmail(payload.email); if (!user) { // XXX: Investigate which actual cases this would happen // But the possible reason is that a token is living inside // the user's phone, but somehow he was banned/deleted return res.status(404).json({ errors: "User does not exist anymore", }); } res.json({ valid: true, userId: payload.userId, email: payload.email, user: publicUser(user), }); } catch (err: any) { if ( err.name === "JsonWebTokenError" || err.name === "TokenExpiredError" ) { return res.status(401).json({ error: "Invalid or expired token" }); } next(err); } }, activate: async (req: Request, res: Response, next: NextFunction) => { try { const parsed = ActivateUserSchema.safeParse(req.body); if (!parsed.success) { return res.status(400).json(formatValidationError(parsed.error!)); } const user = await findUserByActivationCode(parsed.data.code); if (!user) { return res.status(422).json({ error: "Código incorreto. Verifique o código e tente novamente", }); } const result = await activateUser(user); return res.status(200).json({ user: publicUser(result), }); } catch (err: any) { next(err); } }, resendActivationCode: async ( req: AuthenticatedRequest, res: Response, next: NextFunction, ) => { try { const user = await findUserById(req.userId!); if (!user) { return res.status(400).json({ errors: "Usuário não encontrado", }); } const code = await issueActivationCode(user.id); const mailQueue = getQueue("mail"); const job = await mailQueue.add(MailJobName.ActivateAccountEmail, { userId: user.id, code: code, }); res.json({ user: publicUser(user), }); } catch (err: any) { next(err); } }, };