# Análisis del Módulo auth_signup de Odoo **Módulo:** auth_signup **Versión Odoo:** 18.0 Community **Prioridad:** P0 (Crítico) **Mapeo MGN:** MGN-001 (Fundamentos - Autenticación) --- ## Descripción Módulo que extiende `base` para agregar funcionalidades de: - Registro de usuarios (signup) - Reseteo de contraseña - Invitación de usuarios - Tokens de verificación --- ## Modelos Extendidos ### res.users (extensión) **Campos añadidos:** ```python signup_token = fields.Char(copy=False) # Token único para signup signup_type = fields.Selection([ ('signup', 'Signup'), ('reset', 'Reset Password') ]) signup_expiration = fields.Datetime() signup_valid = fields.Boolean(compute='_compute_signup_valid') ``` **Métodos clave:** ```python def signup(self, values, token=None): """ Proceso de registro de usuario """ # 1. Validar token # 2. Crear usuario # 3. Invalidar token # 4. Enviar email de bienvenida def reset_password(self, login): """ Generar token para reset de password """ # 1. Buscar usuario por email # 2. Generar token único # 3. Configurar expiración (24h) # 4. Enviar email con link def change_password(self, old_passwd, new_passwd): """ Cambio de contraseña """ # 1. Validar contraseña actual # 2. Validar nueva contraseña (complejidad) # 3. Actualizar hash # 4. Invalidar sesiones activas ``` --- ## Lógica de Negocio Destacable ### 1. Generación de Tokens Seguros ```python def _generate_signup_token(self): """ Generar token aleatorio único """ return secrets.token_urlsafe(32) # 32 bytes = 256 bits ``` **Aplicabilidad MGN-001:** ⭐⭐⭐⭐⭐ ```typescript // Node.js equivalent import crypto from 'crypto'; function generateToken(): string { return crypto.randomBytes(32).toString('base64url'); } ``` ### 2. Expiración de Tokens ```python @api.depends('signup_token', 'signup_expiration') def _compute_signup_valid(self): for user in self: if user.signup_token and user.signup_expiration: user.signup_valid = datetime.now() <= user.signup_expiration else: user.signup_valid = False ``` **Aplicabilidad MGN-001:** ⭐⭐⭐⭐⭐ - Tokens expiran en 24-48 horas - Validación automática antes de usar ### 3. Proceso de Signup ```python def signup(self, values, token=None): # Validar token if token: user = self.search([('signup_token', '=', token), ('signup_valid', '=', True)]) if not user: raise SignupError('Invalid token') # Crear usuario user = self.create({ 'login': values['email'], 'password': values['password'], 'name': values['name'], 'groups_id': [(6, 0, [self.env.ref('base.group_user').id])] }) # Invalidar token if token: user.signup_token = False # Enviar email bienvenida user._send_welcome_email() return user ``` --- ## Flujos de Usuario ### Flujo 1: Registro de Usuario (Signup) ``` 1. Usuario visita /signup 2. Completa formulario (nombre, email, password) 3. POST /web/signup 4. Backend: a. Valida datos (email único, password fuerte) b. Crea usuario con estado "pendiente" o "activo" c. Envía email de verificación (opcional) 5. Usuario recibe email 6. Click en link de verificación 7. GET /web/signup/verify?token=XXX 8. Backend activa usuario 9. Redirect a /login ``` ### Flujo 2: Reset de Contraseña ``` 1. Usuario visita /reset 2. Ingresa email 3. POST /web/reset_password 4. Backend: a. Busca usuario por email b. Genera token único c. Configura expiración (24h) d. Envía email con link 5. Usuario recibe email 6. Click en link 7. GET /web/reset_password?token=XXX 8. Muestra formulario de nueva contraseña 9. POST /web/reset_password/confirm 10. Backend: a. Valida token b. Actualiza password c. Invalida token 11. Redirect a /login ``` --- ## Validaciones ### 1. Complejidad de Contraseña Odoo no implementa validación de complejidad por defecto, pero es buena práctica. **Recomendación MGN-001:** ```typescript // Validación de password fuerte const passwordSchema = z.string() .min(8, 'Mínimo 8 caracteres') .regex(/[A-Z]/, 'Debe contener mayúscula') .regex(/[a-z]/, 'Debe contener minúscula') .regex(/[0-9]/, 'Debe contener número') .regex(/[^A-Za-z0-9]/, 'Debe contener carácter especial'); ``` ### 2. Email Único ```python _sql_constraints = [ ('login_unique', 'UNIQUE (login)', 'Email already exists') ] ``` ### 3. Token Válido ```python if not user.signup_valid: raise AccessDenied('Token expired or invalid') ``` --- ## Seguridad ### 1. Protección contra Timing Attacks ```python # Odoo NO implementa esto, pero debería import secrets def verify_token(token_from_user, token_in_db): # secrets.compare_digest es resistente a timing attacks return secrets.compare_digest(token_from_user, token_in_db) ``` **Aplicabilidad MGN-001:** ⭐⭐⭐⭐⭐ ```typescript import { timingSafeEqual } from 'crypto'; function verifyToken(tokenFromUser: string, tokenInDB: string): boolean { const bufA = Buffer.from(tokenFromUser); const bufB = Buffer.from(tokenInDB); if (bufA.length !== bufB.length) return false; return timingSafeEqual(bufA, bufB); } ``` ### 2. Rate Limiting Odoo NO implementa rate limiting para signup/reset. **Recomendación MGN-001:** ```typescript // Usar express-rate-limit import rateLimit from 'express-rate-limit'; const signupLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutos max: 5, // 5 intentos por IP message: 'Demasiados intentos, intente más tarde' }); app.post('/api/v1/auth/signup', signupLimiter, signupController); ``` ### 3. CAPTCHA Odoo soporta reCAPTCHA (opcional). **Recomendación MGN-001:** Implementar Google reCAPTCHA v3 --- ## Emails Transaccionales ### 1. Email de Verificación ```html

Hola ${object.name},

Gracias por registrarte en ${object.company_id.name}.

Para completar tu registro, haz click en el siguiente enlace:

Verificar mi cuenta

Este enlace expira en 24 horas.

``` ### 2. Email de Reset de Contraseña ```html

Hola ${object.name},

Has solicitado restablecer tu contraseña.

Haz click en el siguiente enlace para crear una nueva contraseña:

Restablecer contraseña

Si no solicitaste este cambio, ignora este email.

Este enlace expira en 24 horas.

``` **Aplicabilidad MGN-001:** ⭐⭐⭐⭐⭐ - Usar servicio de emails transaccionales (SendGrid, AWS SES) - Templates con Handlebars o React Email --- ## Mapeo a MGN-001 ### Requerimientos Funcionales **RF-AUTH-005: Registro de usuarios** - Endpoint: `POST /api/v1/auth/signup` - Validaciones: email único, password fuerte - Email de verificación opcional - Activación automática o manual **RF-AUTH-006: Reset de contraseña** - Endpoint: `POST /api/v1/auth/reset-password` - Generación de token seguro - Expiración 24 horas - Email con link de reset **RF-AUTH-007: Cambio de contraseña** - Endpoint: `PUT /api/v1/auth/change-password` - Requiere autenticación - Validar contraseña actual - Validar nueva contraseña **RF-AUTH-008: Verificación de email** - Endpoint: `GET /api/v1/auth/verify?token=XXX` - Validar token - Activar usuario - Invalidar token ### Especificaciones Técnicas **ET-AUTH-005-backend: API de Registro** ```typescript // POST /api/v1/auth/signup interface SignupRequest { name: string; email: string; password: string; recaptchaToken?: string; } interface SignupResponse { message: string; requiresVerification: boolean; } async function signup(req: SignupRequest): Promise { // 1. Validar reCAPTCHA // 2. Validar datos (Zod schema) // 3. Verificar email único // 4. Hash password (bcrypt) // 5. Crear usuario // 6. Generar token de verificación // 7. Enviar email // 8. Return response } ``` **ET-AUTH-006-backend: API de Reset Password** ```typescript // POST /api/v1/auth/reset-password interface ResetPasswordRequest { email: string; } interface ResetPasswordResponse { message: string; } async function resetPassword(req: ResetPasswordRequest): Promise { // 1. Buscar usuario por email // 2. Generar token (crypto.randomBytes) // 3. Guardar token + expiration (24h) // 4. Enviar email con link // 5. Return response genérico (no revelar si email existe) } ``` --- ## Implementación Recomendada ### Tabla auth.password_reset_tokens ```sql CREATE TABLE auth.password_reset_tokens ( id SERIAL PRIMARY KEY, user_id INT NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, token VARCHAR(100) NOT NULL UNIQUE, expires_at TIMESTAMPTZ NOT NULL, used BOOLEAN DEFAULT FALSE, created_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX idx_password_reset_tokens_token ON auth.password_reset_tokens(token) WHERE used = FALSE AND expires_at > NOW(); ``` ### Tabla auth.email_verifications ```sql CREATE TABLE auth.email_verifications ( id SERIAL PRIMARY KEY, user_id INT NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, token VARCHAR(100) NOT NULL UNIQUE, expires_at TIMESTAMPTZ NOT NULL, verified BOOLEAN DEFAULT FALSE, created_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX idx_email_verifications_token ON auth.email_verifications(token) WHERE verified = FALSE AND expires_at > NOW(); ``` --- ## Recomendaciones de Implementación ### 1. Proceso de Signup **Con verificación de email (recomendado):** ```typescript // 1. Usuario se registra await createUser({ email, password, name, email_verified: false, active: false // Inactivo hasta verificar }); // 2. Generar token const token = crypto.randomBytes(32).toString('base64url'); await createEmailVerification({ user_id, token, expires_at: new Date(Date.now() + 24 * 60 * 60 * 1000) // 24h }); // 3. Enviar email await sendEmail({ to: email, subject: 'Verifica tu cuenta', template: 'email-verification', data: { name, verificationUrl: `${APP_URL}/auth/verify?token=${token}` } }); ``` **Sin verificación de email (menos seguro):** ```typescript // Usuario activo inmediatamente await createUser({ email, password, name, email_verified: false, active: true // Activo de inmediato }); ``` ### 2. Proceso de Reset Password ```typescript // 1. Usuario solicita reset const user = await findUserByEmail(email); if (!user) { // NO revelar que el email no existe (seguridad) return { message: 'Si el email existe, recibirás instrucciones' }; } // 2. Generar token const token = crypto.randomBytes(32).toString('base64url'); await createPasswordResetToken({ user_id: user.id, token, expires_at: new Date(Date.now() + 24 * 60 * 60 * 1000) }); // 3. Enviar email await sendEmail({ to: email, subject: 'Restablece tu contraseña', template: 'password-reset', data: { name: user.name, resetUrl: `${APP_URL}/auth/reset?token=${token}` } }); return { message: 'Si el email existe, recibirás instrucciones' }; ``` ### 3. Validación de Token ```typescript async function verifyEmailToken(token: string): Promise { const verification = await findEmailVerification(token); if (!verification) { throw new Error('Token inválido'); } if (verification.verified) { throw new Error('Token ya utilizado'); } if (verification.expires_at < new Date()) { throw new Error('Token expirado'); } // Activar usuario await updateUser(verification.user_id, { email_verified: true, active: true }); // Marcar token como usado await markVerificationAsUsed(verification.id); } ``` --- ## Conclusión El módulo `auth_signup` de Odoo proporciona funcionalidades esenciales de autenticación que deben implementarse en MGN-001. **Funcionalidades clave a implementar:** 1. Registro de usuarios con validación 2. Verificación de email (token seguro) 3. Reset de contraseña (token con expiración) 4. Cambio de contraseña (autenticado) **Mejoras vs Odoo:** 1. Validación de complejidad de contraseña 2. Rate limiting en endpoints sensibles 3. reCAPTCHA en signup/reset 4. Timing-safe token comparison 5. JWT en lugar de sesiones **Nivel de reutilización:** 85% de patrones aplicables --- **Fecha:** 2025-11-23 **Analista:** Architecture-Analyst **Estado:** ✅ Análisis completo