12 KiB
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:
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:
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
def _generate_signup_token(self):
""" Generar token aleatorio único """
return secrets.token_urlsafe(32) # 32 bytes = 256 bits
Aplicabilidad MGN-001: ⭐⭐⭐⭐⭐
// Node.js equivalent
import crypto from 'crypto';
function generateToken(): string {
return crypto.randomBytes(32).toString('base64url');
}
2. Expiración de Tokens
@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
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:
// 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
_sql_constraints = [
('login_unique', 'UNIQUE (login)', 'Email already exists')
]
3. Token Válido
if not user.signup_valid:
raise AccessDenied('Token expired or invalid')
Seguridad
1. Protección contra Timing Attacks
# 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: ⭐⭐⭐⭐⭐
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:
// 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
<!-- Template Odoo -->
<p>Hola ${object.name},</p>
<p>Gracias por registrarte en ${object.company_id.name}.</p>
<p>Para completar tu registro, haz click en el siguiente enlace:</p>
<a href="${object.signup_url}">Verificar mi cuenta</a>
<p>Este enlace expira en 24 horas.</p>
2. Email de Reset de Contraseña
<p>Hola ${object.name},</p>
<p>Has solicitado restablecer tu contraseña.</p>
<p>Haz click en el siguiente enlace para crear una nueva contraseña:</p>
<a href="${object.reset_url}">Restablecer contraseña</a>
<p>Si no solicitaste este cambio, ignora este email.</p>
<p>Este enlace expira en 24 horas.</p>
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
// 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<SignupResponse> {
// 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
// POST /api/v1/auth/reset-password
interface ResetPasswordRequest {
email: string;
}
interface ResetPasswordResponse {
message: string;
}
async function resetPassword(req: ResetPasswordRequest): Promise<ResetPasswordResponse> {
// 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
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
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):
// 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):
// Usuario activo inmediatamente
await createUser({
email,
password,
name,
email_verified: false,
active: true // Activo de inmediato
});
2. Proceso de Reset Password
// 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
async function verifyEmailToken(token: string): Promise<void> {
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:
- Registro de usuarios con validación
- Verificación de email (token seguro)
- Reset de contraseña (token con expiración)
- Cambio de contraseña (autenticado)
Mejoras vs Odoo:
- Validación de complejidad de contraseña
- Rate limiting en endpoints sensibles
- reCAPTCHA en signup/reset
- Timing-safe token comparison
- JWT en lugar de sesiones
Nivel de reutilización: 85% de patrones aplicables
Fecha: 2025-11-23 Analista: Architecture-Analyst Estado: ✅ Análisis completo