feat(auth): enhance validation rules for username and password

- Updated username validation to allow only lowercase letters, numbers, and underscores.
- Strengthened password requirements to include at least 8 characters, one uppercase letter, one lowercase letter, one number, and one special character.
- Adjusted frontend forms and backend DTOs to reflect new validation rules.
This commit is contained in:
2026-01-28 21:48:23 +01:00
parent ba0234fd13
commit a4d0c6aa8c
4 changed files with 46 additions and 19 deletions

View File

@@ -148,7 +148,7 @@ describe("AuthService", () => {
const dto = { const dto = {
username: "test", username: "test",
email: "test@example.com", email: "test@example.com",
password: "password", password: "Password1!",
}; };
mockHashingService.hashPassword.mockResolvedValue("hashed-password"); mockHashingService.hashPassword.mockResolvedValue("hashed-password");
mockHashingService.hashEmail.mockResolvedValue("hashed-email"); mockHashingService.hashEmail.mockResolvedValue("hashed-email");
@@ -165,7 +165,7 @@ describe("AuthService", () => {
describe("login", () => { describe("login", () => {
it("should login a user", async () => { it("should login a user", async () => {
const dto = { email: "test@example.com", password: "password" }; const dto = { email: "test@example.com", password: "Password1!" };
const user = { const user = {
uuid: "user-id", uuid: "user-id",
username: "test", username: "test",

View File

@@ -2,6 +2,7 @@ import {
IsEmail, IsEmail,
IsNotEmpty, IsNotEmpty,
IsString, IsString,
Matches,
MaxLength, MaxLength,
MinLength, MinLength,
} from "class-validator"; } from "class-validator";
@@ -10,6 +11,9 @@ export class RegisterDto {
@IsString() @IsString()
@IsNotEmpty() @IsNotEmpty()
@MaxLength(32) @MaxLength(32)
@Matches(/^[a-z0-9_]+$/, {
message: "username must contain only lowercase letters, numbers, and underscores",
})
username!: string; username!: string;
@IsString() @IsString()
@@ -21,5 +25,15 @@ export class RegisterDto {
@IsString() @IsString()
@MinLength(8) @MinLength(8)
@Matches(/[A-Z]/, {
message: "password must contain at least one uppercase letter",
})
@Matches(/[a-z]/, {
message: "password must contain at least one lowercase letter",
})
@Matches(/[0-9]/, { message: "password must contain at least one number" })
@Matches(/[^A-Za-z0-9]/, {
message: "password must contain at least one special character",
})
password!: string; password!: string;
} }

View File

@@ -30,7 +30,7 @@ const loginSchema = z.object({
email: z.string().email({ message: "Email invalide" }), email: z.string().email({ message: "Email invalide" }),
password: z password: z
.string() .string()
.min(6, { message: "Le mot de passe doit faire au moins 6 caractères" }), .min(8, { message: "Le mot de passe doit faire au moins 8 caractères" }),
}); });
type LoginFormValues = z.infer<typeof loginSchema>; type LoginFormValues = z.infer<typeof loginSchema>;

View File

@@ -29,11 +29,24 @@ import { useAuth } from "@/providers/auth-provider";
const registerSchema = z.object({ const registerSchema = z.object({
username: z username: z
.string() .string()
.min(3, { message: "Le pseudo doit faire au moins 3 caractères" }), .min(3, { message: "Le pseudo doit faire au moins 3 caractères" })
.regex(/^[a-z0-9_]+$/, {
message: "Le pseudo ne doit contenir que des minuscules, chiffres et underscores",
}),
email: z.string().email({ message: "Email invalide" }), email: z.string().email({ message: "Email invalide" }),
password: z password: z
.string() .string()
.min(6, { message: "Le mot de passe doit faire au moins 6 caractères" }), .min(8, { message: "Le mot de passe doit faire au moins 8 caractères" })
.regex(/[A-Z]/, {
message: "Le mot de passe doit contenir au moins une majuscule",
})
.regex(/[a-z]/, {
message: "Le mot de passe doit contenir au moins une minuscule",
})
.regex(/[0-9]/, { message: "Le mot de passe doit contenir au moins un chiffre" })
.regex(/[^A-Za-z0-9]/, {
message: "Le mot de passe doit contenir au moins un caractère spécial",
}),
displayName: z.string().optional(), displayName: z.string().optional(),
}); });
@@ -84,12 +97,25 @@ export default function RegisterPage() {
<CardContent> <CardContent>
<Form {...form}> <Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4"> <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<FormField
control={form.control}
name="displayName"
render={({ field }) => (
<FormItem>
<FormLabel>Nom d'affichage (Optionnel)</FormLabel>
<FormControl>
<Input placeholder="Le Roi des Chèvres" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField <FormField
control={form.control} control={form.control}
name="username" name="username"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Pseudo</FormLabel> <FormLabel>Pseudo (minuscule)</FormLabel>
<FormControl> <FormControl>
<Input placeholder="supergoat" {...field} /> <Input placeholder="supergoat" {...field} />
</FormControl> </FormControl>
@@ -110,19 +136,6 @@ export default function RegisterPage() {
</FormItem> </FormItem>
)} )}
/> />
<FormField
control={form.control}
name="displayName"
render={({ field }) => (
<FormItem>
<FormLabel>Nom d'affichage (Optionnel)</FormLabel>
<FormControl>
<Input placeholder="Le Roi des Chèvres" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField <FormField
control={form.control} control={form.control}
name="password" name="password"