api: split activation code from welcome email
Pedro Lucas Porcellis porcellis@eletrotupi.com · 4 months ago
10dda3b1236e6affa6cb2722043f04c66876a354 Browse filesParents
e6b6493Changed files
6
Additions
+108
Deletions
-10
api/src/controllers/auth.ts
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 {
11 14
verifyToken
12 15
} from '@app/lib/jwt';
13 16
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 {
21 24
LoginSchema,
22 25
PasswordResetSchema,
23 26
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 {
30 37
const parsed = LoginSchema.safeParse(req.body);
31 38
35 42
});
36 43
}
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({
46 62
token,
47 63
user
143 159
}
144 160
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);
152 170
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
}
159 207
}
160 208
};
api/src/lib/queue/processors/mail.ts
1 1
import { Job } from 'bullmq';
2 2
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> {
14 16
// job.name is the discriminant
15 17
switch (job.name as MailJobName) {
16 18
case MailJobName.WelcomeEmail: {
17 19
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
}
24 35
25 36
case MailJobName.PasswordReset: {
33 44
34 45
default:
35 46
throw new Error(`Unknown mail job: ${job.name}`);
36 47
}
37 48
}
api/src/lib/queue/types.ts
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 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
6 7
router.post('/login', AuthController.login);
7 8
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 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';
/dev/null → api/src/services/mail/sendActivateAccountEmail.ts
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 +
}