eletrotupi / tcc/ api/src/controllers/auth.tsc901c35af9c84b6a29e5185545ced1129f33b8f4
5.0 KB | 194 lines Raw
1
import { Request, Response, NextFunction } from "express";
2
import { AuthenticatedRequest } from "@app/middleware/auth";
3

4
import {
5
  login,
6
  generateToken,
7
  requestPasswordReset,
8
  resetPassword,
9
  activateUser,
10
  issueActivationCode,
11
} from "@app/services/auth.service";
12

13
import { verifyToken } from "@app/lib/jwt";
14

15
import {
16
  findUserByEmail,
17
  findUserById,
18
  findUserByActivationCode,
19
} from "@app/services/user.service";
20

21
import {
22
  LoginSchema,
23
  PasswordResetSchema,
24
  PasswordResetRequestSchema,
25
  ActivateUserSchema,
26
} from "@app/schemas";
27
import { formatValidationError } from "@app/lib/errors/validationError";
28
import { publicUser } from "@app/lib/serializers/user";
29

30
import { getQueue, MailJobName } from "@app/lib/queue";
31

32
export const AuthController = {
33
  login: async (req: Request, res: Response, next: NextFunction) => {
34
    try {
35
      const parsed = LoginSchema.safeParse(req.body);
36

37
      if (!parsed.success) {
38
        return res.status(400).json(formatValidationError(parsed.error!));
39
      }
40

41
      const { user, token } = await login(parsed.data);
42

43
      if (
44
        !user.active &&
45
        new Date() > new Date(user.activationCodeExpiresAt!)
46
      ) {
47
        const code = await issueActivationCode(user.id);
48

49
        const mailQueue = getQueue("mail");
50
        const job = await mailQueue.add(MailJobName.ActivateAccountEmail, {
51
          userId: user.id,
52
          code: code,
53
        });
54
      }
55

56
      return res.json({
57
        token,
58
        user: publicUser(user),
59
      });
60
    } catch (err: any) {
61
      if (err.message === "Invalid credentials") {
62
        return res.status(401).json({ error: "Invalid credentials" });
63
      }
64
      next(err);
65
    }
66
  },
67

68
  forgotPassword: async (req: Request, res: Response, next: NextFunction) => {
69
    try {
70
      const parsed = PasswordResetRequestSchema.safeParse(req.body);
71

72
      if (!parsed.success) {
73
        return res.status(400).json(formatValidationError(parsed.error!));
74
      }
75

76
      const result = await requestPasswordReset(parsed.data);
77
      return res.status(200).json(result);
78
    } catch (err) {
79
      next(err);
80
    }
81
  },
82

83
  resetPassword: async (req: Request, res: Response, next: NextFunction) => {
84
    try {
85
      const parsed = PasswordResetSchema.safeParse(req.body);
86

87
      if (!parsed.success) {
88
        return res.status(400).json(formatValidationError(parsed.error!));
89
      }
90

91
      const result = await resetPassword(parsed.data);
92
      return res.status(200).json(result);
93
    } catch (err: any) {
94
      if (err.message.includes("token")) {
95
        return res.status(400).json({ error: err.message });
96
      }
97
      next(err);
98
    }
99
  },
100

101
  verify: async (req: Request, res: Response, next: NextFunction) => {
102
    try {
103
      const authHeader = req.headers.authorization;
104

105
      if (!authHeader || !authHeader.startsWith("Bearer ")) {
106
        return res.status(401).json({ error: "No token provided" });
107
      }
108

109
      const token = authHeader.substring(7);
110
      const payload = verifyToken(token);
111
      const user = await findUserByEmail(payload.email);
112

113
      if (!user) {
114
        // XXX: Investigate which actual cases this would happen
115
        // But the possible reason is that a token is living inside
116
        // the user's phone, but somehow he was banned/deleted
117
        return res.status(404).json({
118
          errors: "User does not exist anymore",
119
        });
120
      }
121

122
      res.json({
123
        valid: true,
124
        userId: payload.userId,
125
        email: payload.email,
126
        user: publicUser(user),
127
      });
128
    } catch (err: any) {
129
      if (
130
        err.name === "JsonWebTokenError" ||
131
        err.name === "TokenExpiredError"
132
      ) {
133
        return res.status(401).json({ error: "Invalid or expired token" });
134
      }
135
      next(err);
136
    }
137
  },
138

139
  activate: async (req: Request, res: Response, next: NextFunction) => {
140
    try {
141
      const parsed = ActivateUserSchema.safeParse(req.body);
142

143
      if (!parsed.success) {
144
        return res.status(400).json(formatValidationError(parsed.error!));
145
      }
146

147
      const user = await findUserByActivationCode(parsed.data.code);
148

149
      if (!user) {
150
        return res.status(422).json({
151
          error: "Código incorreto. Verifique o código e tente novamente",
152
        });
153
      }
154

155
      const result = await activateUser(user);
156

157
      return res.status(200).json({
158
        user: publicUser(result),
159
      });
160
    } catch (err: any) {
161
      next(err);
162
    }
163
  },
164

165
  resendActivationCode: async (
166
    req: AuthenticatedRequest,
167
    res: Response,
168
    next: NextFunction,
169
  ) => {
170
    try {
171
      const user = await findUserById(req.userId!);
172

173
      if (!user) {
174
        return res.status(400).json({
175
          errors: "Usuário não encontrado",
176
        });
177
      }
178

179
      const code = await issueActivationCode(user.id);
180

181
      const mailQueue = getQueue("mail");
182
      const job = await mailQueue.add(MailJobName.ActivateAccountEmail, {
183
        userId: user.id,
184
        code: code,
185
      });
186

187
      res.json({
188
        user: publicUser(user),
189
      });
190
    } catch (err: any) {
191
      next(err);
192
    }
193
  },
194
};