Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 48 additions & 3 deletions src/config/env.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,59 @@ const requiredEnvVars = [
'SUPABASE_BUCKET_STORES',
]

/**
* Valida que los secretos cumplan requisitos mínimos de seguridad.
* En desarrollo permite valores cortos, en producción exige mínimo 32 caracteres.
*
* - Rechaza secretos vacíos o solo whitespace
* - Valida longitud mínima
* - Advierte en desarrollo, falla en producción
*/
function validateSecretStrength(secret, name) {
const isProduction = process.env.NODE_ENV === 'production'
const minLength = isProduction ? 32 : 16

// Validar que no sea solo whitespace o vacío
const trimmed = secret.trim()
if (!trimmed || trimmed.length === 0) {
throw new Error(`${name} no puede estar vacío o contener solo espacios`)
}

// Validar longitud mínima del valor real (sin espacios)
if (trimmed.length < minLength) {
const env = isProduction ? 'producción' : 'desarrollo'
console.warn(`[WARNING] ${name} es débil (${trimmed.length} chars, mín ${minLength} en ${env})`)

if (isProduction) {
throw new Error(`${name} debe tener mínimo ${minLength} caracteres en producción`)
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

/**
* Valida todas las variables de entorno requeridas.
*
* 1. Verifica que todas las vars obligatorias existan
* 2. Valida fortaleza de secrets críticos (JWT_SECRET, SUPABASE_SERVICE_ROLE_KEY)
* 3. Falla al iniciar si hay problemas en producción
*/
export function validateEnv() {
// 1. Validar que todas las vars existan
const missing = requiredEnvVars.filter(key => !process.env[key])

if (missing.length > 0) {
console.error('❌ Faltan las siguientes variables de entorno:')
console.error('[ERROR] Faltan las siguientes variables de entorno:')
missing.forEach(key => console.error(` - ${key}`))
process.exit(1)
}

console.log('✅ Variables de entorno validadas correctamente')
// 2. Validar fortaleza de secretos críticos
try {
validateSecretStrength(process.env.JWT_SECRET, 'JWT_SECRET')
validateSecretStrength(process.env.SUPABASE_SERVICE_ROLE_KEY, 'SUPABASE_SERVICE_ROLE_KEY')
} catch (error) {
console.error(`[ERROR] ${error.message}`)
process.exit(1)
}

console.log('[OK] Variables de entorno validadas correctamente')
}
60 changes: 60 additions & 0 deletions src/lib/utils/password.utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import bcrypt from 'bcrypt'

/**
* Coste de bcrypt adaptado al ambiente:
* - Desarrollo: 10 (rápido para testing)
* - Producción: 12 (más seguro, ~100ms por hash)
*
* En 2025 se recomienda 12+, pero el hardware del servidor define el máximo
* antes de timeouts (típicamente 100-200ms).
*/
const BCRYPT_ROUNDS = process.env.NODE_ENV === 'production' ? 12 : 10

/**
* Hash de contraseña con bcrypt.
* NUNCA devuelve la contraseña en claro.
*
* @param {string} plainPassword - Contraseña sin hashear
* @returns {Promise<string>} Hash bcrypt seguro
* @throws {Error} Si la contraseña es inválida o el hash falla
*/
export async function hashPassword(plainPassword) {
if (!plainPassword || typeof plainPassword !== 'string') {
throw new Error('La contraseña debe ser un string no vacío')
}

if (plainPassword.length < 6) {
throw new Error('La contraseña debe tener mínimo 6 caracteres')
}

try {
return await bcrypt.hash(plainPassword, BCRYPT_ROUNDS)
} catch (error) {
console.error('[PASSWORD_HASH_ERROR]', error.message)
throw new Error('Error al procesar la contraseña')
}
}

/**
* Compara contraseña en claro con hash bcrypt.
* NUNCA loguea la contraseña.
*
* @param {string} plainPassword - Contraseña ingresada por el usuario
* @param {string} hashedPassword - Hash almacenado en BD
* @returns {Promise<boolean>} true si coincide, false si no
* @throws {Error} Si hay error técnico en bcrypt (hash corrupto, etc)
*/
export async function verifyPassword(plainPassword, hashedPassword) {
if (!plainPassword || !hashedPassword) {
return false
}

try {
return await bcrypt.compare(plainPassword, hashedPassword)
} catch (error) {
// Loguear el error técnico para debugging en desarrollo
// pero NO exponer detalles en respuesta al cliente
console.error('[PASSWORD_VERIFY_ERROR]', error.message)
throw new Error('Error al verificar contraseña')
}
}
Loading
Loading