api: split activation code from welcome email
Parents:
e6b64936 file(s) changed
- api/src/controllers/auth.ts +54 -6
- api/src/lib/queue/processors/mail.ts +12 -1
- api/src/lib/queue/types.ts +6 -3
- api/src/routes/auth.ts +2 -0
- api/src/services/mail/index.ts +1 -0
- api/src/services/mail/sendActivateAccountEmail.ts +33 -0
api/src/controllers/auth.ts
@@ -1,10 +1,13 @@
1 1 import { Request, Response, NextFunction } from 'express';
2 + import { AuthenticatedRequest } from '@app/middleware/auth';
3 +
2 4 import {
3 5 login,
4 6 generateToken,
5 7 requestPasswordReset,
6 8 resetPassword,
7 - activateUser
9 + activateUser,
10 + storeActivationCode
8 11 } from '@app/services/auth.service';
9 12
10 13 import {
@@ -14,7 +17,7 @@
14 17 import {
15 18 findUserByEmail,
16 19 findUserById,
17 - findUserByActivationCode
20 + findUserByActivationCode,
18 21 } from '@app/services/user.service'
19 22
20 23 import {
@@ -24,6 +27,10 @@ PasswordResetRequestSchema,
24 27 ActivateUserSchema
25 28 } from '@app/schemas';
26 29
30 + import { addMinutes } from 'date-fns';
31 + import { getQueue, MailJobName } from '@app/lib/queue';
32 + import crypto from 'crypto';
33 +
27 34 export const AuthController = {
28 35 login: async (req: Request, res: Response, next: NextFunction) => {
29 36 try {
@@ -37,9 +44,18 @@ }
37 44
38 45 const { user, token } = await login(parsed.data);
39 46
40 - if (!user.active) {
41 - // TODO: generate a new activation code and send an email
42 - // if the code has expired
47 + if (!user.active && (new Date() > new Date(user.activationCodeExpiresAt!))) {
48 + const code = [...Array(6)].map(() => crypto.randomInt(9))
49 + .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 + });
43 59 }
44 60
45 61 return res.json({
@@ -145,7 +161,9 @@
145 161 const user = await findUserByActivationCode(String(parsed.data.code));
146 162
147 163 if (!user) {
148 - return res.status(403).json({ error: "Usuario não encontrado" })
164 + return res.status(422).json({
165 + error: "Código incorreto. Verifique o código e tente novamente"
166 + })
149 167 }
150 168
151 169 const result = await activateUser(user);
@@ -153,6 +171,36 @@
153 171 return res.status(200).json({
154 172 user: result
155 173 });
174 + } catch (err: any) {
175 + next(err);
176 + }
177 + },
178 +
179 + resendActivationCode: async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
180 + try {
181 + const user = await findUserById(req.userId!);
182 +
183 + if (!user) {
184 + return res.status(400).json({
185 + errors: "Usuário não encontrado"
186 + })
187 + }
188 +
189 + const code = [...Array(6)].map(() => crypto.randomInt(9))
190 + .join("");
191 +
192 + const expiresAt = addMinutes(new Date(), 5);
193 + await storeActivationCode(user.id, code, expiresAt);
194 +
195 + //const mailQueue = getQueue('mail');
196 + //const job = await mailQueue.add(MailJobName.ActivateAccountEmail, {
197 + // userId: user.id,
198 + // code: code
199 + //});
200 +
201 + res.json({
202 + user
203 + })
156 204 } catch (err: any) {
157 205 next(err);
158 206 }
api/src/lib/queue/processors/mail.ts
@@ -3,11 +3,13 @@ import {
3 3 MailJobName, MailJobData,
4 4 WelcomeEmailPayload,
5 5 PasswordResetPayload,
6 + ActivateAccountEmailPayload
6 7 } from '@app/lib/queue/types';
7 8
8 9 import {
9 10 sendWelcomeEmail,
10 - sendResetPasswordEmail
11 + sendResetPasswordEmail,
12 + sendActivateAccountEmail
11 13 } from '@app/services/mail'
12 14
13 15 export async function mailProcessor(job: Job<MailJobData['data']>): Promise<void> {
@@ -18,6 +20,15 @@ const data = job.data as WelcomeEmailPayload;
18 20 console.log("Dispatching an welcome email", data);
19 21
20 22 sendWelcomeEmail(Number(data.userId), data.code);
23 +
24 + break;
25 + }
26 +
27 + case MailJobName.ActivateAccountEmail: {
28 + const data = job.data as ActivateAccountEmailPayload;
29 + console.log("Dispatching an activate account email", data);
30 +
31 + sendActivateAccountEmail(Number(data.userId), data.code);
21 32
22 33 break;
23 34 }
api/src/lib/queue/types.ts
@@ -1,12 +1,15 @@
1 1 // Mail queue
2 2 export enum MailJobName {
3 - WelcomeEmail = 'mail:welcome',
4 - PasswordReset = 'mail:password-reset',
3 + WelcomeEmail = 'mail:welcome',
4 + PasswordReset = 'mail:password-reset',
5 + ActivateAccountEmail = 'mail:activate-account'
5 6 }
6 7
7 8 export type WelcomeEmailPayload = { userId: number; code: string };
8 9 export type PasswordResetPayload = { userId: number; token: string };
10 + export type ActivateAccountEmailPayload = { userId: number; code: string };
9 11
10 12 export type MailJobData =
11 13 | { name: MailJobName.WelcomeEmail; data: WelcomeEmailPayload }
12 - | { name: MailJobName.PasswordReset; data: PasswordResetPayload };
14 + | { name: MailJobName.PasswordReset; data: PasswordResetPayload }
15 + | { name: MailJobName.ActivateAccountEmail; data: ActivateAccountEmailPayload };
api/src/routes/auth.ts
@@ -1,5 +1,6 @@
1 1 import { Router } from 'express';
2 2 import { AuthController } from '@app/controllers/auth';
3 + import { requireAuth } from '@app/middleware/auth';
3 4
4 5 const router = Router();
5 6
@@ -8,5 +9,6 @@ router.post('/forgot-password', AuthController.forgotPassword);
8 9 router.post('/reset-password', AuthController.resetPassword);
9 10 router.get('/verify', AuthController.verify);
10 11 router.post('/activate', AuthController.activate);
12 + router.post('/resend_code', requireAuth, AuthController.resendActivationCode);
11 13
12 14 export default router;
api/src/services/mail/index.ts
@@ -1,2 +1,3 @@
1 1 export { sendWelcomeEmail } from '@app/services/mail/welcome';
2 2 export { sendResetPasswordEmail } from '@app/services/mail/sendPasswordReset'
3 + export { sendActivateAccountEmail } from '@app/services/mail/sendActivateAccountEmail';
api/src/services/mail/sendActivateAccountEmail.ts
@@ -0,0 +1,33 @@
1 + import { findUserById } from '@app/services/user.service';
2 + import { sendEmail } from '@app/lib/mail';
3 +
4 + const email = (firstName: string, code: string) => {
5 + return (`
6 + Oi, ${firstName}.
7 +
8 + Você criou sua conta no nexo. Porém, precisa ativar sua conta.
9 +
10 + Aqui seu código de ativação: ${code}.
11 +
12 + Qualquer coisa prende o grito!
13 + `)
14 + }
15 +
16 + export async function sendActivateAccountEmail(userId: number, code: string): Promise<void> {
17 + const user = await findUserById(userId)!;
18 +
19 + console.log("Email/activateAccount: ", user, code);
20 +
21 + const { data, error } = await sendEmail({
22 + to: user!.email,
23 + subject: "Ative sua conta no nexo!",
24 + text: email(user!.firstName, code)
25 + });
26 +
27 + if (error) {
28 + console.error("Error sending email: ", error);
29 + }
30 +
31 + console.log("Email sent successfully!");
32 + console.log("Email ID:", data?.id);
33 + }