From a556760872db13190463c1c276867113b85aec27 Mon Sep 17 00:00:00 2001 From: Raffi Date: Tue, 31 Mar 2026 22:21:23 +0200 Subject: [PATCH] =?UTF-8?q?mise=20en=20place=20de=20la=20v=C3=A9rification?= =?UTF-8?q?=20d'email?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/errors/AppError.ts | 7 +++++++ src/routes/users.ts | 13 +++++++++++++ src/services/user.service.ts | 37 ++++++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+) create mode 100644 src/services/user.service.ts diff --git a/src/errors/AppError.ts b/src/errors/AppError.ts index 90e4f43..a5aec50 100644 --- a/src/errors/AppError.ts +++ b/src/errors/AppError.ts @@ -11,8 +11,15 @@ export class AppError extends Error { // Erreurs prédéfinies export const Errors = { + + // registration errors EMAIL_TAKEN: new AppError('EMAIL_TAKEN', 409, 'Cette adresse email est déjà utilisée.'), PASSWORD_TOO_WEAK: new AppError('PASSWORD_TOO_WEAK', 400, 'Le mot de passe doit contenir au moins 8 caractères.'), INVALID_CREDENTIALS: new AppError('INVALID_CREDENTIALS', 401, 'Email ou mot de passe incorrect.'), VALIDATION_ERROR: (message: string) => new AppError('VALIDATION_ERROR', 400, message), + + //Action token errors + INVALID_TOKEN: new AppError('INVALID_TOKEN', 400, 'Invalid or already used token'), + TOKEN_EXPIRED: new AppError('TOKEN_EXPIRED', 400, 'Token has expired'), + ALREADY_CONFIRMED: new AppError('ALREADY_CONFIRMED', 400, 'User is already confirmed'), } \ No newline at end of file diff --git a/src/routes/users.ts b/src/routes/users.ts index 9ccdd5a..7aa9bc5 100644 --- a/src/routes/users.ts +++ b/src/routes/users.ts @@ -1,4 +1,6 @@ import { FastifyInstance } from 'fastify' +import { Errors } from '../errors/AppError' +import { confirmEmail } from '../services/user.service' export default async function userRoutes(fastify: FastifyInstance) { fastify.get('/users', async (request, reply) => { @@ -16,4 +18,15 @@ export default async function userRoutes(fastify: FastifyInstance) { return users }) + + fastify.get('/user/confirm', async (request, reply) => { + const { token } = request.query as { token?: string } + + if (!token) { + throw Errors.INVALID_TOKEN + } + + const result = await confirmEmail(fastify.prisma, token) + return reply.status(200).send(result) +}) } \ No newline at end of file diff --git a/src/services/user.service.ts b/src/services/user.service.ts new file mode 100644 index 0000000..f371b3e --- /dev/null +++ b/src/services/user.service.ts @@ -0,0 +1,37 @@ +import { PrismaClient } from '../generated/prisma/client.js' +import { Errors } from '../errors/AppError.js' + +export async function confirmEmail(prisma: PrismaClient, token: string) { + const actionToken = await prisma.actionToken.findUnique({ + where: { token }, + }) + + if (!actionToken || actionToken.type !== 'email-confirm' || actionToken.used) { + throw Errors.INVALID_TOKEN + } + + if (actionToken.expiresAt < new Date()) { + throw Errors.TOKEN_EXPIRED + } + + const user = await prisma.user.findUnique({ + where: { id: actionToken.userId }, + select: { isConfirmed: true }, + }) + + if (user?.isConfirmed) { + throw Errors.ALREADY_CONFIRMED + } + + await prisma.$transaction([ + prisma.user.update({ + where: { id: actionToken.userId }, + data: { isConfirmed: true }, + }), + prisma.actionToken.delete({ + where: { id: actionToken.id }, + }), + ]) + + return { success: true } +} \ No newline at end of file