eletrotupi / tcc/ api/src/controllers/auth.tsf1583dfe90160d2a63fcf9773f9ef6ba70337d4e
5.5 KB | 217 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

28
import { addMinutes } from "date-fns";
29
import { getQueue, MailJobName } from "@app/lib/queue";
30
import crypto from "crypto";
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({
39
          errors: parsed.error!.issues,
40
        });
41
      }
42

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

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

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

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

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

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

77
      if (!parsed.success) {
78
        return res.status(400).json({
79
          errors: parsed.error!.issues,
80
        });
81
      }
82

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

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

94
      if (!parsed.success) {
95
        return res.status(400).json({
96
          errors: parsed.error!.issues,
97
        });
98
      }
99

100
      const result = await resetPassword(parsed.data);
101
      return res.status(200).json(result);
102
    } catch (err: any) {
103
      if (err.message.includes("token")) {
104
        return res.status(400).json({ error: err.message });
105
      }
106
      next(err);
107
    }
108
  },
109

110
  verify: async (req: Request, res: Response, next: NextFunction) => {
111
    try {
112
      const authHeader = req.headers.authorization;
113

114
      if (!authHeader || !authHeader.startsWith("Bearer ")) {
115
        return res.status(401).json({ error: "No token provided" });
116
      }
117

118
      const token = authHeader.substring(7);
119
      const payload = verifyToken(token);
120
      const user = await findUserByEmail(payload.email);
121

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

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

157
  activate: async (req: Request, res: Response, next: NextFunction) => {
158
    try {
159
      const parsed = ActivateUserSchema.safeParse(req.body);
160

161
      if (!parsed.success) {
162
        return res.status(400).json({
163
          errors: parsed.error!.issues,
164
        });
165
      }
166

167
      const user = await findUserByActivationCode(String(parsed.data.code));
168

169
      if (!user) {
170
        return res.status(422).json({
171
          error: "Código incorreto. Verifique o código e tente novamente",
172
        });
173
      }
174

175
      const result = await activateUser(user);
176

177
      return res.status(200).json({
178
        user: result,
179
      });
180
    } catch (err: any) {
181
      next(err);
182
    }
183
  },
184

185
  resendActivationCode: async (
186
    req: AuthenticatedRequest,
187
    res: Response,
188
    next: NextFunction,
189
  ) => {
190
    try {
191
      const user = await findUserById(req.userId!);
192

193
      if (!user) {
194
        return res.status(400).json({
195
          errors: "Usuário não encontrado",
196
        });
197
      }
198

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

201
      const expiresAt = addMinutes(new Date(), 5);
202
      await storeActivationCode(user.id, code, expiresAt);
203

204
      const mailQueue = getQueue("mail");
205
      const job = await mailQueue.add(MailJobName.ActivateAccountEmail, {
206
        userId: user.id,
207
        code: code,
208
      });
209

210
      res.json({
211
        user,
212
      });
213
    } catch (err: any) {
214
      next(err);
215
    }
216
  },
217
};