import { Request, Response, NextFunction } from "express";
import { AuthenticatedRequest } from "@app/middleware/auth";
import {
login,
generateToken,
requestPasswordReset,
resetPassword,
activateUser,
storeActivationCode,
} 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 { addMinutes } from "date-fns";
import { getQueue, MailJobName } from "@app/lib/queue";
import crypto from "crypto";
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({
errors: parsed.error!.issues,
});
}
const { user, token } = await login(parsed.data);
if (
!user.active &&
new Date() > new Date(user.activationCodeExpiresAt!)
) {
const code = [...Array(6)].map(() => crypto.randomInt(9)).join("");
const expiresAt = addMinutes(new Date(), 5);
await storeActivationCode(user.id, code, expiresAt);
const mailQueue = getQueue("mail");
const job = await mailQueue.add(MailJobName.ActivateAccountEmail, {
userId: user.id,
code: code,
});
}
return res.json({
token,
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({
errors: parsed.error!.issues,
});
}
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({
errors: parsed.error!.issues,
});
}
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: {
firstName: user.firstName,
email: user.email,
lastName: user.lastName,
updatedAt: user.updatedAt,
active: user.active,
avatarKey: user.avatarKey,
avatarURL: user.avatarURL,
pushToken: user.pushToken,
},
});
} 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({
errors: parsed.error!.issues,
});
}
const user = await findUserByActivationCode(String(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: 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 = [...Array(6)].map(() => crypto.randomInt(9)).join("");
const expiresAt = addMinutes(new Date(), 5);
await storeActivationCode(user.id, code, expiresAt);
const mailQueue = getQueue("mail");
const job = await mailQueue.add(MailJobName.ActivateAccountEmail, {
userId: user.id,
code: code,
});
res.json({
user,
});
} catch (err: any) {
next(err);
}
},
};