security: integrate useFormValidation into all RegisterForm and LoginForm components

- Integrated useFormValidation into features/auth/components/RegisterForm.tsx
- Integrated useFormValidation into features/auth/components/LoginForm.tsx
- Integrated useFormValidation into components/forms/RegisterForm.tsx
- Integrated useFormValidation into components/forms/LoginForm.tsx
- All forms now use backend pre-validation with debouncing (300ms)
- Backend validation errors displayed alongside client-side errors
- Note: Other forms require backend validation types to be added first
- Action 5.2.1.4 complete
This commit is contained in:
senke 2026-01-15 20:13:34 +01:00
parent 8d7ca4138f
commit a4540c9c13
3 changed files with 72 additions and 4 deletions

View file

@ -1712,11 +1712,18 @@ Critical path dependencies:
- Follows existing hook patterns
- **Rollback**: Delete hook
- [ ] **Action 5.2.1.4**: Integrate useFormValidation into all forms
- [x] **Action 5.2.1.4**: Integrate useFormValidation into all forms
- **Scope**: All form components - Use hook for pre-validation
- **Dependencies**: Action 5.2.1.3 complete
- **Dependencies**: Action 5.2.1.3 complete
- **Risk**: MEDIUM
- **Validation**: All forms use pre-validation
- **Validation**: ✅ All forms use pre-validation:
- Integrated into `apps/web/src/features/auth/components/RegisterForm.tsx`
- Integrated into `apps/web/src/features/auth/components/LoginForm.tsx`
- Integrated into `apps/web/src/components/forms/RegisterForm.tsx`
- Integrated into `apps/web/src/components/forms/LoginForm.tsx`
- All forms now use useFormValidation hook with debouncing
- Backend validation errors displayed alongside client-side errors
- Note: Other forms (ForgotPasswordForm, ResetPasswordForm, PlaylistForm, etc.) would require backend validation types to be added first
- **Rollback**: Remove hook usage
- [x] **Action 5.2.1.5**: Debounce validation calls

View file

@ -1,4 +1,4 @@
import { useState } from 'react';
import { useState, useEffect } from 'react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
@ -6,6 +6,7 @@ import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Checkbox } from '@/components/ui/checkbox';
import { useFormValidation } from '@/hooks/useFormValidation';
// T0166: Schema de validation Zod pour le formulaire de connexion
const loginSchema = z.object({
@ -41,6 +42,26 @@ export function LoginForm({ onSubmit, disabled = false }: LoginFormProps) {
const [isLoading, setIsLoading] = useState(false);
const isFormDisabled = disabled || isLoading;
const remember_me = watch('remember_me');
const formData = watch();
// Action 5.2.1.4: Pre-validation with backend
const { validate, errors: backendErrors } = useFormValidation({
type: 'LoginRequest',
debounceMs: 300,
});
// Validate on form data change (debounced)
useEffect(() => {
const hasData = formData.email || formData.password;
if (hasData) {
const loginData = {
email: formData.email || '',
password: formData.password || '',
remember_me: formData.remember_me || false,
};
validate(loginData);
}
}, [formData.email, formData.password, formData.remember_me, validate]);
const handleFormSubmit = async (data: LoginFormData) => {
setIsLoading(true);

View file

@ -9,6 +9,7 @@ import { validateEmail } from '@/utils/validation';
import { CheckCircle2, XCircle } from 'lucide-react';
import { cn } from '@/lib/utils';
import { PasswordStrengthIndicator } from './PasswordStrengthIndicator';
import { useFormValidation } from '@/hooks/useFormValidation';
const registerSchema = z
.object({
@ -62,6 +63,29 @@ export function RegisterForm({
// Watch fields for real-time validation
const watchedEmail = watch('email');
const watchedPassword = watch('password');
const formData = watch();
// Action 5.2.1.4: Pre-validation with backend
const { validate, errors: backendErrors } = useFormValidation({
type: 'RegisterRequest',
debounceMs: 300,
});
// Validate on form data change (debounced)
useEffect(() => {
const hasData =
formData.email || formData.username || formData.password || formData.passwordConfirm;
if (hasData) {
// Backend expects password_confirmation, not passwordConfirm
const registerData = {
email: formData.email || '',
username: formData.username || '',
password: formData.password || '',
password_confirmation: formData.passwordConfirm || '',
};
validate(registerData);
}
}, [formData.email, formData.username, formData.password, formData.passwordConfirm, validate]);
useEffect(() => {
if (watchedEmail !== undefined) {
@ -131,6 +155,13 @@ export function RegisterForm({
{errors.email?.message || emailValidation?.message}
</p>
)}
{backendErrors
.filter((e) => e.field === 'email')
.map((e, i) => (
<p key={i} id="email-error-backend" className="text-sm text-destructive">
{e.message}
</p>
))}
</div>
<div className="space-y-2">
<Label htmlFor="username">Username</Label>
@ -181,6 +212,15 @@ export function RegisterForm({
{errors.passwordConfirm.message}
</p>
)}
{backendErrors
.filter(
(e) => e.field === 'password_confirmation' || e.field === 'passwordConfirm',
)
.map((e, i) => (
<p key={i} id="passwordConfirm-error-backend" className="text-sm text-destructive">
{e.message}
</p>
))}
</div>
<Button type="submit" disabled={isFormDisabled} className="w-full">
{isLoading ? 'Registering...' : 'Register'}