eletrotupi / tcc/ api/src/controllers/auth.tsafa605370e2af884a10edffbb025e6ee263ed462
5.5 KB | 210 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
  storeActivationCode,
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

29
import { addMinutes } from "date-fns";
30
import { getQueue, MailJobName } from "@app/lib/queue";
31
import crypto from "crypto";
32

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

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

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

44
      if (
45
        !user.active &&
46
        new Date() > new Date(user.activationCodeExpiresAt!)
47
      ) {
48
        const code = [...Array(6)].map(() => crypto.randomInt(9)).join("");
49

50
        const expiresAt = addMinutes(new Date(), 5);
51
        await storeActivationCode(user.id, code, expiresAt);
52

53
        const mailQueue = getQueue("mail");
54
        const job = await mailQueue.add(MailJobName.ActivateAccountEmail, {
55
          userId: user.id,
56
          code: code,
57
        });
58
      }
59

60
      return res.json({
61
        token,
62
        user,
63
      });
64
    } catch (err: any) {
65
      if (err.message === "Invalid credentials") {
66
        return res.status(401).json({ error: "Invalid credentials" });
67
      }
68
      next(err);
69
    }
70
  },
71

72
  forgotPassword: async (req: Request, res: Response, next: NextFunction) => {
73
    try {
74
      const parsed = PasswordResetRequestSchema.safeParse(req.body);
75

76
      if (!parsed.success) {
77
        return res.status(400).json(formatValidationError(parsed.error!));
78
      }
79

80
      const result = await requestPasswordReset(parsed.data);
81
      return res.status(200).json(result);
82
    } catch (err) {
83
      next(err);
84
    }
85
  },
86

87
  resetPassword: async (req: Request, res: Response, next: NextFunction) => {
88
    try {
89
      const parsed = PasswordResetSchema.safeParse(req.body);
90

91
      if (!parsed.success) {
92
        return res.status(400).json(formatValidationError(parsed.error!));
93
      }
94

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

105
  verify: async (req: Request, res: Response, next: NextFunction) => {
106
    try {
107
      const authHeader = req.headers.authorization;
108

109
      if (!authHeader || !authHeader.startsWith("Bearer ")) {
110
        return res.status(401).json({ error: "No token provided" });
111
      }
112

113
      const token = authHeader.substring(7);
114
      const payload = verifyToken(token);
115
      const user = await findUserByEmail(payload.email);
116

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

126
      res.json({
127
        valid: true,
128
        userId: payload.userId,
129
        email: payload.email,
130
        user: {
131
          firstName: user.firstName,
132
          email: user.email,
133
          lastName: user.lastName,
134
          updatedAt: user.updatedAt,
135
          active: user.active,
136
          avatarKey: user.avatarKey,
137
          avatarURL: user.avatarURL,
138
          pushToken: user.pushToken,
139
        },
140
      });
141
    } catch (err: any) {
142
      if (
143
        err.name === "JsonWebTokenError" ||
144
        err.name === "TokenExpiredError"
145
      ) {
146
        return res.status(401).json({ error: "Invalid or expired token" });
147
      }
148
      next(err);
149
    }
150
  },
151

152
  activate: async (req: Request, res: Response, next: NextFunction) => {
153
    try {
154
      const parsed = ActivateUserSchema.safeParse(req.body);
155

156
      if (!parsed.success) {
157
        return res.status(400).json(formatValidationError(parsed.error!));
158
      }
159

160
      const user = await findUserByActivationCode(String(parsed.data.code));
161

162
      if (!user) {
163
        return res.status(422).json({
164
          error: "Código incorreto. Verifique o código e tente novamente",
165
        });
166
      }
167

168
      const result = await activateUser(user);
169

170
      return res.status(200).json({
171
        user: result,
172
      });
173
    } catch (err: any) {
174
      next(err);
175
    }
176
  },
177

178
  resendActivationCode: async (
179
    req: AuthenticatedRequest,
180
    res: Response,
181
    next: NextFunction,
182
  ) => {
183
    try {
184
      const user = await findUserById(req.userId!);
185

186
      if (!user) {
187
        return res.status(400).json({
188
          errors: "Usuário não encontrado",
189
        });
190
      }
191

192
      const code = [...Array(6)].map(() => crypto.randomInt(9)).join("");
193

194
      const expiresAt = addMinutes(new Date(), 5);
195
      await storeActivationCode(user.id, code, expiresAt);
196

197
      const mailQueue = getQueue("mail");
198
      const job = await mailQueue.add(MailJobName.ActivateAccountEmail, {
199
        userId: user.id,
200
        code: code,
201
      });
202

203
      res.json({
204
        user,
205
      });
206
    } catch (err: any) {
207
      next(err);
208
    }
209
  },
210
};