gestion des erreurs au sign-up

This commit is contained in:
2026-03-30 22:37:36 +02:00
parent 915813d681
commit 928fb55dea
6 changed files with 77 additions and 36 deletions

View File

@@ -4,10 +4,12 @@ import prismaPlugin from './plugins/prisma.js'
import mailerPlugin from './plugins/mailer.js'
import authRoutes from './routes/auth.js'
import userRoutes from './routes/users.js'
import errorHandler from './plugins/errorHandler.js'
export default function buildApp() {
const app = Fastify({ logger: true })
app.register(errorHandler)
app.register(cookie)
app.register(prismaPlugin)
app.register(mailerPlugin)

17
src/errors/AppError.ts Normal file
View File

@@ -0,0 +1,17 @@
export class AppError extends Error {
constructor(
public readonly code: string,
public readonly statusCode: number,
message: string
) {
super(message)
this.name = 'AppError'
}
}
// Erreurs prédéfinies
export const 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.'),
VALIDATION_ERROR: (message: string) => new AppError('VALIDATION_ERROR', 400, message),
}

View File

@@ -0,0 +1,34 @@
import fp from 'fastify-plugin'
import { FastifyInstance } from 'fastify'
import { ZodError } from 'zod'
import { AppError } from '../errors/AppError.js'
export default fp(async (fastify: FastifyInstance) => {
fastify.setErrorHandler((error, request, reply) => {
// Erreur Zod
if (error instanceof ZodError) {
return reply.status(400).send({
error: 'VALIDATION_ERROR',
details: error.issues.map((e) => ({
field: e.path.join('.'),
message: e.message,
})),
})
}
// Erreur métier
if (error instanceof AppError) {
return reply.status(error.statusCode).send({
error: error.code,
message: error.message,
})
}
// Erreur inconnue
fastify.log.error(error)
return reply.status(500).send({
error: 'INTERNAL_ERROR',
message: 'Une erreur interne est survenue.',
})
})
})

View File

@@ -1,41 +1,23 @@
import { FastifyInstance } from 'fastify'
import { ZodError } from 'zod'
import { RegisterSchema } from '../schemas/auth.schema.js'
import { registerUser } from '../services/auth.service.js'
export default async function authRoutes(fastify: FastifyInstance) {
fastify.post('/auth/register', async (request, reply) => {
// Validation Zod
let body
try {
body = RegisterSchema.parse(request.body)
} catch (err) {
if (err instanceof ZodError) {
return reply.status(400).send({ error: 'VALIDATION_ERROR', details: err.errors })
}
throw err
}
const body = RegisterSchema.parse(request.body) // Zod throw → handler global
// Register
try {
const { user, authToken } = await registerUser(
fastify.prisma,
fastify.mailer,
body
)
const { user, authToken } = await registerUser(
fastify.prisma,
fastify.mailer,
body
)
reply.setCookie('authToken', authToken, {
httpOnly: true,
sameSite: 'strict',
maxAge: 60 * 60 * 24 * 7, // 7 jours en secondes
})
reply.setCookie('authToken', authToken, {
httpOnly: true,
sameSite: 'strict',
maxAge: 60 * 60 * 24 * 7,
})
return reply.status(201).send({ user })
} catch (err: any) {
if (err.message === 'EMAIL_TAKEN') {
return reply.status(409).send({ error: 'EMAIL_TAKEN' })
}
throw err
}
return reply.status(201).send({ user })
})
}

View File

@@ -1,9 +1,14 @@
import { z } from 'zod'
export const RegisterSchema = z.object({
email: z.string().email(),
password: z.string().min(8),
displayName: z.string().min(2).max(50),
email: z.email({ error: 'Adresse email invalide.' }),
password: z
.string()
.regex(/^.{8,22}$/, { error: 'Le mot de passe doit contenir entre 8 et 22 caractères.' })
.regex(/[^A-Za-z0-9]/, { error: 'Le mot de passe doit contenir au moins un caractère spécial.' })
.regex(/[A-Z]/, { error: 'Le mot de passe doit contenir au moins une majuscule.' })
.regex(/[a-z]/, { error: 'Le mot de passe doit contenir au moins une minuscule.' })
.regex(/\d/, { error: 'Le mot de passe doit contenir au moins un chiffre.' }),
})
export type RegisterInput = z.infer<typeof RegisterSchema>

View File

@@ -5,6 +5,7 @@ import { Transporter } from 'nodemailer'
import { RegisterInput } from '../schemas/auth.schema.js'
import { generateToken, generateAuthTokenExpiry, generateActionTokenExpiry } from './token.service.js'
import { sendConfirmationMail } from './mail.service.js'
import { Errors } from '../errors/AppError.js'
export async function registerUser(
prisma: PrismaClient,
@@ -16,19 +17,19 @@ export async function registerUser(
where: { email: input.email },
})
if (existing) {
throw new Error('EMAIL_TAKEN')
}
throw Errors.EMAIL_TAKEN }
// 2. Hash du mot de passe
const passwordHash = await argon2.hash(input.password)
// 3. Création de l'user
const displayName = input.email.split('@')[0]
const user = await prisma.user.create({
data: {
id: crypto.randomUUID(),
email: input.email,
passwordHash,
displayName: input.displayName,
displayName,
avatar: '',
},
select: {