gestion des erreurs au sign-up
This commit is contained in:
@@ -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
17
src/errors/AppError.ts
Normal 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),
|
||||
}
|
||||
34
src/plugins/errorHandler.ts
Normal file
34
src/plugins/errorHandler.ts
Normal 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.',
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -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 })
|
||||
})
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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: {
|
||||
|
||||
Reference in New Issue
Block a user