eletrotupi / tcc/ api/src/controllers/auth.ts38004a30fcf3fc21f8954a0179705ede8a78a57e
5.1 KB | 202 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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

147
  activate: async (req: Request, res: Response, next: NextFunction) => {
148
    try {
149
      const parsed = ActivateUserSchema.safeParse(req.body);
150

151
      if (!parsed.success) {
152
        return res.status(400).json(formatValidationError(parsed.error!));
153
      }
154

155
      const user = await findUserByActivationCode(parsed.data.code);
156

157
      if (!user) {
158
        return res.status(422).json({
159
          error: "Código incorreto. Verifique o código e tente novamente",
160
        });
161
      }
162

163
      const result = await activateUser(user);
164

165
      return res.status(200).json({
166
        user: result,
167
      });
168
    } catch (err: any) {
169
      next(err);
170
    }
171
  },
172

173
  resendActivationCode: async (
174
    req: AuthenticatedRequest,
175
    res: Response,
176
    next: NextFunction,
177
  ) => {
178
    try {
179
      const user = await findUserById(req.userId!);
180

181
      if (!user) {
182
        return res.status(400).json({
183
          errors: "Usuário não encontrado",
184
        });
185
      }
186

187
      const code = await issueActivationCode(user.id);
188

189
      const mailQueue = getQueue("mail");
190
      const job = await mailQueue.add(MailJobName.ActivateAccountEmail, {
191
        userId: user.id,
192
        code: code,
193
      });
194

195
      res.json({
196
        user,
197
      });
198
    } catch (err: any) {
199
      next(err);
200
    }
201
  },
202
};