Refactor: Analyze and fix security issues
This commit is contained in:
202
SECURITY.md
Normal file
202
SECURITY.md
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
# 🔒 Reporte de Seguridad del Proyecto Karibeo
|
||||||
|
|
||||||
|
## ⚠️ ADVERTENCIAS CRÍTICAS DE SEGURIDAD
|
||||||
|
|
||||||
|
### 1. CRÍTICO: Verificación de Roles del Cliente (NO SEGURO PARA PRODUCCIÓN)
|
||||||
|
|
||||||
|
**PROBLEMA**: El sistema actual verifica roles de administrador usando el objeto `user` almacenado en localStorage del cliente.
|
||||||
|
|
||||||
|
**Archivos afectados**:
|
||||||
|
- `src/hooks/useAdminData.ts` (línea 18)
|
||||||
|
- `src/hooks/useEmergencyData.ts` (línea 16)
|
||||||
|
- `src/App.tsx` (línea 131)
|
||||||
|
- `src/pages/SignIn.tsx` (línea 33)
|
||||||
|
|
||||||
|
**Por qué es inseguro**:
|
||||||
|
```typescript
|
||||||
|
// ❌ INSEGURO - Un atacante puede modificar localStorage
|
||||||
|
const isAdmin = user?.role === 'admin';
|
||||||
|
```
|
||||||
|
|
||||||
|
Un atacante puede:
|
||||||
|
1. Abrir DevTools en el navegador
|
||||||
|
2. Modificar `localStorage.setItem('karibeo-user', JSON.stringify({...user, role: 'admin'}))`
|
||||||
|
3. Obtener acceso completo al panel de administración
|
||||||
|
|
||||||
|
**SOLUCIÓN REQUERIDA PARA PRODUCCIÓN**:
|
||||||
|
|
||||||
|
#### Opción A: Backend con verificación de roles (RECOMENDADO)
|
||||||
|
1. Crear tabla `user_roles` en la base de datos (separada de `users`/`profiles`)
|
||||||
|
2. Implementar verificación de roles en el backend
|
||||||
|
3. Cada endpoint de API debe verificar permisos en el servidor
|
||||||
|
4. Usar Row Level Security (RLS) en Supabase
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Ejemplo de estructura segura
|
||||||
|
CREATE TABLE user_roles (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
|
||||||
|
role TEXT NOT NULL CHECK (role IN ('admin', 'super_admin', 'user', 'hotel', 'restaurant')),
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
UNIQUE(user_id, role)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Función de seguridad definer (evita recursión RLS)
|
||||||
|
CREATE OR REPLACE FUNCTION has_role(_user_id UUID, _role TEXT)
|
||||||
|
RETURNS BOOLEAN
|
||||||
|
LANGUAGE SQL
|
||||||
|
STABLE
|
||||||
|
SECURITY DEFINER
|
||||||
|
SET search_path = public
|
||||||
|
AS $$
|
||||||
|
SELECT EXISTS (
|
||||||
|
SELECT 1 FROM user_roles
|
||||||
|
WHERE user_id = _user_id AND role = _role
|
||||||
|
)
|
||||||
|
$$;
|
||||||
|
|
||||||
|
-- Política RLS usando la función
|
||||||
|
CREATE POLICY "Only admins can view all data"
|
||||||
|
ON some_table
|
||||||
|
FOR SELECT
|
||||||
|
TO authenticated
|
||||||
|
USING (has_role(auth.uid(), 'admin'));
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Opción B: Sin Backend
|
||||||
|
Si NO puedes implementar backend:
|
||||||
|
1. **Aceptar el riesgo** - La verificación del cliente es solo para UX
|
||||||
|
2. **No almacenar datos sensibles** - Nada crítico debe depender de roles
|
||||||
|
3. **Implementar ofuscación** - Dificultar (no prevenir) manipulación
|
||||||
|
4. **Rate limiting** - Limitar llamadas a API
|
||||||
|
|
||||||
|
### 2. Validación de Inputs Insuficiente
|
||||||
|
|
||||||
|
**PROBLEMA**: La mayoría de formularios no tienen validación robusta.
|
||||||
|
|
||||||
|
**Archivos sin validación**:
|
||||||
|
- ✅ `src/pages/SignIn.tsx` - **CORREGIDO**
|
||||||
|
- ✅ `src/pages/SignUp.tsx` - **CORREGIDO**
|
||||||
|
- ❌ `src/pages/dashboard/AddListing.tsx`
|
||||||
|
- ❌ `src/pages/dashboard/Settings.tsx`
|
||||||
|
- ❌ `src/pages/dashboard/Profile.tsx`
|
||||||
|
|
||||||
|
**Riesgos**:
|
||||||
|
- Inyección SQL (si el backend no valida)
|
||||||
|
- XSS (Cross-Site Scripting)
|
||||||
|
- Datos corruptos en la base de datos
|
||||||
|
- Buffer overflow en campos de texto
|
||||||
|
|
||||||
|
**SOLUCIÓN IMPLEMENTADA**:
|
||||||
|
- Creado `src/lib/validation.ts` con schemas de validación usando zod
|
||||||
|
- Actualizado SignIn.tsx con validación completa
|
||||||
|
- Actualizado SignUp.tsx con validación robusta
|
||||||
|
|
||||||
|
### 3. Credenciales Mock Hardcoded
|
||||||
|
|
||||||
|
**PROBLEMA**: Usuarios de prueba con contraseñas conocidas en `src/contexts/AuthContext.tsx`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ⚠️ DESARROLLO SOLAMENTE
|
||||||
|
const mockUsers = {
|
||||||
|
'superadmin@karibeo.com': { password: '123456', role: 'super_admin' },
|
||||||
|
'admin@karibeo.com': { password: '123456', role: 'admin' },
|
||||||
|
'user@karibeo.com': { password: '123456', role: 'tourist' }
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
**SOLUCIÓN**:
|
||||||
|
- ✅ Agregada documentación clara de que es solo para desarrollo
|
||||||
|
- ⚠️ **CRÍTICO**: Eliminar estos usuarios antes de producción
|
||||||
|
- ⚠️ **CRÍTICO**: Usar variables de entorno para credenciales de prueba
|
||||||
|
|
||||||
|
### 4. Tokens en localStorage
|
||||||
|
|
||||||
|
**PROBLEMA**: Tokens JWT almacenados en localStorage son vulnerables a XSS.
|
||||||
|
|
||||||
|
**Estado actual**:
|
||||||
|
```typescript
|
||||||
|
localStorage.setItem('karibeo-token', token);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Riesgo**: Si hay una vulnerabilidad XSS, un atacante puede robar tokens.
|
||||||
|
|
||||||
|
**SOLUCIÓN (para implementar)**:
|
||||||
|
- Usar HttpOnly cookies (requiere backend)
|
||||||
|
- Implementar refresh tokens con rotación
|
||||||
|
- Tokens de corta duración (15 minutos)
|
||||||
|
- Refresh tokens en HttpOnly cookies
|
||||||
|
|
||||||
|
### 5. Sin Rate Limiting
|
||||||
|
|
||||||
|
**PROBLEMA**: No hay protección contra ataques de fuerza bruta en login.
|
||||||
|
|
||||||
|
**SOLUCIÓN REQUERIDA**:
|
||||||
|
- Implementar rate limiting en el backend
|
||||||
|
- Bloquear IPs después de N intentos fallidos
|
||||||
|
- Captcha después de 3 intentos
|
||||||
|
- Retrasos progresivos entre intentos
|
||||||
|
|
||||||
|
## ✅ Mejoras Implementadas
|
||||||
|
|
||||||
|
1. **Validación de Inputs con Zod**
|
||||||
|
- Schemas de validación centralizados en `src/lib/validation.ts`
|
||||||
|
- Validación de email, password, nombres, teléfonos
|
||||||
|
- Mensajes de error descriptivos
|
||||||
|
- Protección contra caracteres maliciosos
|
||||||
|
|
||||||
|
2. **Documentación de Seguridad**
|
||||||
|
- Este documento SECURITY.md
|
||||||
|
- Comentarios de advertencia en código crítico
|
||||||
|
- Guías de implementación segura
|
||||||
|
|
||||||
|
3. **Mejora de Formularios de Autenticación**
|
||||||
|
- SignIn con validación completa
|
||||||
|
- SignUp con validación de password seguro
|
||||||
|
- Manejo de errores mejorado
|
||||||
|
|
||||||
|
## 📋 Checklist de Seguridad para Producción
|
||||||
|
|
||||||
|
### ANTES DE DEPLOY:
|
||||||
|
- [ ] Eliminar usuarios mock de AuthContext.tsx
|
||||||
|
- [ ] Implementar verificación de roles en el backend
|
||||||
|
- [ ] Crear tabla user_roles separada
|
||||||
|
- [ ] Implementar RLS policies en Supabase
|
||||||
|
- [ ] Migrar tokens a HttpOnly cookies
|
||||||
|
- [ ] Implementar rate limiting
|
||||||
|
- [ ] Auditar todos los endpoints de API
|
||||||
|
- [ ] Habilitar CORS solo para dominios conocidos
|
||||||
|
- [ ] Configurar CSP (Content Security Policy)
|
||||||
|
- [ ] Implementar logging de intentos de acceso no autorizado
|
||||||
|
- [ ] Agregar validación en TODOS los formularios
|
||||||
|
- [ ] Sanitizar HTML en campos de texto rico
|
||||||
|
- [ ] Configurar HTTPS obligatorio
|
||||||
|
- [ ] Implementar 2FA para administradores
|
||||||
|
- [ ] Revisar dependencias con `npm audit`
|
||||||
|
- [ ] Configurar variables de entorno adecuadamente
|
||||||
|
- [ ] Implementar monitoreo de seguridad
|
||||||
|
|
||||||
|
### TESTING DE SEGURIDAD:
|
||||||
|
- [ ] Pruebas de penetración
|
||||||
|
- [ ] Análisis de vulnerabilidades XSS
|
||||||
|
- [ ] Análisis de inyección SQL
|
||||||
|
- [ ] Pruebas de escalación de privilegios
|
||||||
|
- [ ] Pruebas de autenticación bypass
|
||||||
|
- [ ] Pruebas de CSRF
|
||||||
|
|
||||||
|
## 🔗 Recursos Adicionales
|
||||||
|
|
||||||
|
- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
|
||||||
|
- [Supabase Security Best Practices](https://supabase.com/docs/guides/auth/row-level-security)
|
||||||
|
- [JWT Security Best Practices](https://tools.ietf.org/html/rfc8725)
|
||||||
|
- [Input Validation with Zod](https://zod.dev/)
|
||||||
|
|
||||||
|
## 📞 Contacto de Seguridad
|
||||||
|
|
||||||
|
Si descubres una vulnerabilidad de seguridad, por favor NO la publiques públicamente. Contacta al equipo de desarrollo directamente.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Última actualización**: ${new Date().toISOString().split('T')[0]}
|
||||||
|
**Versión del documento**: 1.0
|
||||||
|
**Estado**: Desarrollo - NO apto para producción sin correcciones
|
||||||
@@ -125,6 +125,7 @@ const DashboardGate = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ⚠️ SECURITY WARNING: Client-side role check - see SECURITY.md
|
||||||
const role = (user as any)?.role;
|
const role = (user as any)?.role;
|
||||||
console.log('🚪 DashboardGate - checking role:', role, 'isAdmin?', (role === 'admin' || role === 'super_admin'));
|
console.log('🚪 DashboardGate - checking role:', role, 'isAdmin?', (role === 'admin' || role === 'super_admin'));
|
||||||
|
|
||||||
|
|||||||
@@ -74,7 +74,9 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
|||||||
try {
|
try {
|
||||||
console.log('Login attempt with:', { email, password: '***' });
|
console.log('Login attempt with:', { email, password: '***' });
|
||||||
|
|
||||||
// Mock users for testing
|
// ⚠️ SECURITY WARNING: Mock users for DEVELOPMENT ONLY
|
||||||
|
// ⚠️ REMOVE BEFORE PRODUCTION DEPLOYMENT
|
||||||
|
// These hardcoded credentials are a CRITICAL security vulnerability in production
|
||||||
const mockUsers = {
|
const mockUsers = {
|
||||||
'superadmin@karibeo.com': {
|
'superadmin@karibeo.com': {
|
||||||
id: '1',
|
id: '1',
|
||||||
|
|||||||
@@ -14,7 +14,13 @@ export const useAdminData = () => {
|
|||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
// Check if user has admin permissions
|
// ⚠️ CRITICAL SECURITY WARNING: CLIENT-SIDE ROLE VERIFICATION
|
||||||
|
// This checks roles from localStorage which can be easily manipulated by attackers
|
||||||
|
// For PRODUCTION, you MUST implement server-side role verification:
|
||||||
|
// 1. Create a separate 'user_roles' table in database
|
||||||
|
// 2. Verify roles on EVERY API endpoint in the backend
|
||||||
|
// 3. Use Row Level Security (RLS) policies in Supabase
|
||||||
|
// This client-side check is ONLY for UI/UX purposes, NOT for actual security
|
||||||
const isAdmin = user?.role === 'admin' || user?.role === 'super_admin';
|
const isAdmin = user?.role === 'admin' || user?.role === 'super_admin';
|
||||||
const isSuperAdmin = user?.role === 'super_admin';
|
const isSuperAdmin = user?.role === 'super_admin';
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,10 @@ export const useEmergencyData = () => {
|
|||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
// Check if user has emergency permissions
|
// ⚠️ CRITICAL SECURITY WARNING: CLIENT-SIDE ROLE VERIFICATION
|
||||||
|
// This checks roles from localStorage which can be manipulated by users
|
||||||
|
// For PRODUCTION: Implement server-side verification with user_roles table and RLS
|
||||||
|
// This is ONLY for UI/UX, NOT for actual security
|
||||||
const isOfficer = user?.role === 'politur' || user?.role === 'admin' || user?.role === 'super_admin';
|
const isOfficer = user?.role === 'politur' || user?.role === 'admin' || user?.role === 'super_admin';
|
||||||
const isAdmin = user?.role === 'admin' || user?.role === 'super_admin';
|
const isAdmin = user?.role === 'admin' || user?.role === 'super_admin';
|
||||||
|
|
||||||
|
|||||||
133
src/lib/validation.ts
Normal file
133
src/lib/validation.ts
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SECURITY: Input validation schemas using zod
|
||||||
|
* All user inputs must be validated to prevent injection attacks
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Authentication schemas
|
||||||
|
export const loginSchema = z.object({
|
||||||
|
email: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.min(1, 'El email es requerido')
|
||||||
|
.email('Email inválido')
|
||||||
|
.max(255, 'Email demasiado largo'),
|
||||||
|
password: z
|
||||||
|
.string()
|
||||||
|
.min(6, 'La contraseña debe tener al menos 6 caracteres')
|
||||||
|
.max(128, 'Contraseña demasiado larga'),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const registerSchema = z.object({
|
||||||
|
fullName: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.min(2, 'El nombre debe tener al menos 2 caracteres')
|
||||||
|
.max(100, 'Nombre demasiado largo')
|
||||||
|
.regex(/^[a-zA-ZáéíóúÁÉÍÓÚñÑ\s]+$/, 'El nombre solo puede contener letras'),
|
||||||
|
email: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.min(1, 'El email es requerido')
|
||||||
|
.email('Email inválido')
|
||||||
|
.max(255, 'Email demasiado largo'),
|
||||||
|
password: z
|
||||||
|
.string()
|
||||||
|
.min(8, 'La contraseña debe tener al menos 8 caracteres')
|
||||||
|
.max(128, 'Contraseña demasiado larga')
|
||||||
|
.regex(
|
||||||
|
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/,
|
||||||
|
'La contraseña debe contener al menos una mayúscula, una minúscula y un número'
|
||||||
|
),
|
||||||
|
confirmPassword: z.string(),
|
||||||
|
userType: z.enum(['tourist', 'business'], {
|
||||||
|
errorMap: () => ({ message: 'Tipo de usuario inválido' }),
|
||||||
|
}),
|
||||||
|
}).refine((data) => data.password === data.confirmPassword, {
|
||||||
|
message: 'Las contraseñas no coinciden',
|
||||||
|
path: ['confirmPassword'],
|
||||||
|
});
|
||||||
|
|
||||||
|
// User profile update schema
|
||||||
|
export const profileUpdateSchema = z.object({
|
||||||
|
name: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.min(2, 'El nombre debe tener al menos 2 caracteres')
|
||||||
|
.max(100, 'Nombre demasiado largo')
|
||||||
|
.regex(/^[a-zA-ZáéíóúÁÉÍÓÚñÑ\s]+$/, 'El nombre solo puede contener letras')
|
||||||
|
.optional(),
|
||||||
|
phone: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.regex(/^\+?[0-9\s\-()]+$/, 'Número de teléfono inválido')
|
||||||
|
.max(20, 'Número demasiado largo')
|
||||||
|
.optional(),
|
||||||
|
address: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.max(500, 'Dirección demasiado larga')
|
||||||
|
.optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Contact form schema
|
||||||
|
export const contactFormSchema = z.object({
|
||||||
|
name: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.min(2, 'El nombre debe tener al menos 2 caracteres')
|
||||||
|
.max(100, 'Nombre demasiado largo'),
|
||||||
|
email: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.email('Email inválido')
|
||||||
|
.max(255, 'Email demasiado largo'),
|
||||||
|
phone: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.regex(/^\+?[0-9\s\-()]+$/, 'Número de teléfono inválido')
|
||||||
|
.max(20, 'Número demasiado largo')
|
||||||
|
.optional(),
|
||||||
|
message: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.min(10, 'El mensaje debe tener al menos 10 caracteres')
|
||||||
|
.max(2000, 'Mensaje demasiado largo'),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Review schema
|
||||||
|
export const reviewSchema = z.object({
|
||||||
|
rating: z
|
||||||
|
.number()
|
||||||
|
.min(1, 'La calificación mínima es 1')
|
||||||
|
.max(5, 'La calificación máxima es 5')
|
||||||
|
.int('La calificación debe ser un número entero'),
|
||||||
|
comment: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.min(10, 'El comentario debe tener al menos 10 caracteres')
|
||||||
|
.max(1000, 'Comentario demasiado largo')
|
||||||
|
.optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Search query schema
|
||||||
|
export const searchSchema = z.object({
|
||||||
|
query: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.min(1, 'La búsqueda no puede estar vacía')
|
||||||
|
.max(200, 'Búsqueda demasiado larga'),
|
||||||
|
category: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.max(50, 'Categoría inválida')
|
||||||
|
.optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type LoginInput = z.infer<typeof loginSchema>;
|
||||||
|
export type RegisterInput = z.infer<typeof registerSchema>;
|
||||||
|
export type ProfileUpdateInput = z.infer<typeof profileUpdateSchema>;
|
||||||
|
export type ContactFormInput = z.infer<typeof contactFormSchema>;
|
||||||
|
export type ReviewInput = z.infer<typeof reviewSchema>;
|
||||||
|
export type SearchInput = z.infer<typeof searchSchema>;
|
||||||
@@ -6,6 +6,7 @@ import { useAuth } from '@/contexts/AuthContext';
|
|||||||
import { useLanguage } from '@/contexts/LanguageContext';
|
import { useLanguage } from '@/contexts/LanguageContext';
|
||||||
import { Apple, Eye, EyeOff } from 'lucide-react';
|
import { Apple, Eye, EyeOff } from 'lucide-react';
|
||||||
import { FaGoogle } from 'react-icons/fa';
|
import { FaGoogle } from 'react-icons/fa';
|
||||||
|
import { loginSchema, type LoginInput } from '@/lib/validation';
|
||||||
|
|
||||||
const SignIn = () => {
|
const SignIn = () => {
|
||||||
const [email, setEmail] = useState('');
|
const [email, setEmail] = useState('');
|
||||||
@@ -24,8 +25,10 @@ const SignIn = () => {
|
|||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
setError('');
|
setError('');
|
||||||
|
|
||||||
|
// SECURITY: Validate inputs before processing
|
||||||
try {
|
try {
|
||||||
await login(email, password);
|
const validatedData = loginSchema.parse({ email, password });
|
||||||
|
await login(validatedData.email, validatedData.password);
|
||||||
// Decide destination based on role
|
// Decide destination based on role
|
||||||
const cached = localStorage.getItem('karibeo-user');
|
const cached = localStorage.getItem('karibeo-user');
|
||||||
const u = user ?? (cached ? JSON.parse(cached) : null);
|
const u = user ?? (cached ? JSON.parse(cached) : null);
|
||||||
@@ -36,7 +39,12 @@ const SignIn = () => {
|
|||||||
navigate('/dashboard');
|
navigate('/dashboard');
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
|
// Handle validation errors from zod
|
||||||
|
if (err.name === 'ZodError') {
|
||||||
|
setError(err.errors[0]?.message || 'Datos inválidos');
|
||||||
|
} else {
|
||||||
setError(err.message);
|
setError(err.message);
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { useAuth } from '@/contexts/AuthContext';
|
|||||||
import { useLanguage } from '@/contexts/LanguageContext';
|
import { useLanguage } from '@/contexts/LanguageContext';
|
||||||
import { Apple, Eye, EyeOff } from 'lucide-react';
|
import { Apple, Eye, EyeOff } from 'lucide-react';
|
||||||
import { FaGoogle } from 'react-icons/fa';
|
import { FaGoogle } from 'react-icons/fa';
|
||||||
|
import { registerSchema, type RegisterInput } from '@/lib/validation';
|
||||||
|
|
||||||
const SignUp = () => {
|
const SignUp = () => {
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
@@ -36,19 +37,17 @@ const SignUp = () => {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setError('');
|
setError('');
|
||||||
|
|
||||||
if (formData.password !== formData.confirmPassword) {
|
|
||||||
setError('Las contraseñas no coinciden');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!agreeToTerms) {
|
if (!agreeToTerms) {
|
||||||
setError('Debes aceptar los términos de servicio');
|
setError('Debes aceptar los términos de servicio');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SECURITY: Validate all inputs before processing
|
||||||
|
try {
|
||||||
|
const validatedData = registerSchema.parse(formData);
|
||||||
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
try {
|
|
||||||
await register({
|
await register({
|
||||||
name: formData.fullName,
|
name: formData.fullName,
|
||||||
email: formData.email,
|
email: formData.email,
|
||||||
@@ -59,7 +58,12 @@ const SignUp = () => {
|
|||||||
});
|
});
|
||||||
navigate('/dashboard');
|
navigate('/dashboard');
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
|
// Handle validation errors from zod
|
||||||
|
if (err.name === 'ZodError') {
|
||||||
|
setError(err.errors[0]?.message || 'Datos inválidos');
|
||||||
|
} else {
|
||||||
setError(err.message);
|
setError(err.message);
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user