Skip to main content

Autenticación y Seguridad

Sistema de Autenticación JWT

JWTManager Class

# managementApp/utilsfolder/JWTUtils.py
import jwt
import datetime
from django.conf import settings
from django.contrib.auth import authenticate

class JWTManager:
@staticmethod
def generate_token(user):
payload = {
'user_id': user.id,
'email': user.email,
'exp': datetime.datetime.utcnow() + datetime.timedelta(seconds=settings.JWT_AUTH_VALID_FOR),
'iat': datetime.datetime.utcnow()
}

token = jwt.encode(
payload,
settings.JWT_AUTH_SECRET_KEY,
algorithm='HS256'
)

return token

@staticmethod
def decode_token(token):
try:
payload = jwt.decode(
token,
settings.JWT_AUTH_SECRET_KEY,
algorithms=['HS256']
)
return payload
except jwt.ExpiredSignatureError:
raise Exception('Token has expired')
except jwt.InvalidTokenError:
raise Exception('Invalid token')

@staticmethod
def authenticate_user(email, password):
user = authenticate(username=email, password=password)
if user and user.is_active:
return user
return None

Configuración JWT

# managementProject/settings.py
JWT_AUTH_SECRET_KEY = os.getenv('JWT_AUTH_SECRET_KEY')
JWT_AUTH_VALID_FOR = int(os.getenv('JWT_AUTH_VALID_FOR', '36000')) # 10 hours

# Payload del Token
{
'user_id': 123,
'email': 'user@example.com',
'exp': 1672531200, # Timestamp de expiración
'iat': 1672494800 # Timestamp de creación
}

Login y Registro

LoginView

# managementApp/viewsfolder/AuthViews.py
class LoginView(APIView):
permission_classes = []

def post(self, request):
email = request.data.get('email')
password = request.data.get('password')

if not email or not password:
return Response({
'success': False,
'message': 'Email y contraseña son requeridos'
}, status=status.HTTP_400_BAD_REQUEST)

try:
user = JWTManager.authenticate_user(email, password)

if user:
token = JWTManager.generate_token(user)

# Encrypt sensitive data
user_data = {
'user_id': user.id,
'email': user.email,
'first_name': user.first_name,
'last_name': user.last_name,
'phone': user.phone,
}

encrypted_data = encrypt_data(user_data)

return Response({
'success': True,
'message': 'Login exitoso',
'data': {
'token': token,
'user': encrypted_data,
'expires_in': settings.JWT_AUTH_VALID_FOR
}
}, status=status.HTTP_200_OK)
else:
return Response({
'success': False,
'message': 'Credenciales inválidas'
}, status=status.HTTP_401_UNAUTHORIZED)

except Exception as e:
return Response({
'success': False,
'message': f'Error en autenticación: {str(e)}'
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

SignUpView

class SignUpView(APIView):
permission_classes = []

def post(self, request):
try:
data = request.data

# Validate required fields
required_fields = ['email', 'password', 'first_name', 'last_name']
for field in required_fields:
if not data.get(field):
return Response({
'success': False,
'message': f'{field} es requerido'
}, status=status.HTTP_400_BAD_REQUEST)

# Check if user exists
if CustomUser.objects.filter(email=data['email']).exists():
return Response({
'success': False,
'message': 'El email ya está registrado'
}, status=status.HTTP_400_BAD_REQUEST)

# Create user
user = CustomUser.objects.create_user(
username=data['email'],
email=data['email'],
password=data['password'],
first_name=data['first_name'],
last_name=data['last_name'],
phone=data.get('phone', ''),
document_id=data.get('document_id', '')
)

# Generate token
token = JWTManager.generate_token(user)

return Response({
'success': True,
'message': 'Usuario creado exitosamente',
'data': {
'token': token,
'user_id': user.id
}
}, status=status.HTTP_201_CREATED)

except Exception as e:
return Response({
'success': False,
'message': f'Error creando usuario: {str(e)}'
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

Encriptación de Datos

AES-256 Encryption

# managementApp/utilsfolder/Crypto_utils.py
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import base64
import json
import os

# Encryption key (base64 encoded)
ENCRYPTION_KEY_BASE64 = "8J+ZkPCfmZDwn5mQ8J+ZkPCfmZDwn5mQ8J+ZkPCfmZA="
ENCRYPTION_KEY = base64.b64decode(ENCRYPTION_KEY_BASE64)

def encrypt_data(data):
"""Encrypt data using AES-256 CBC mode"""
try:
# Convert to JSON if dict or list
if isinstance(data, (dict, list)):
data = json.dumps(data)

# Create AES cipher in CBC mode
cipher = AES.new(ENCRYPTION_KEY, AES.MODE_CBC)

# Encrypt with padding
ct_bytes = cipher.encrypt(pad(data.encode(), AES.block_size))

# Combine IV with encrypted data
iv = cipher.iv
encrypted_data = base64.b64encode(iv + ct_bytes).decode()

return encrypted_data
except Exception as e:
raise ValueError(f"Error encrypting data: {e}")

def decrypt_data(encrypted_data):
"""Decrypt data using AES-256 CBC mode"""
try:
# Decode base64
try:
encrypted_data = base64.b64decode(encrypted_data)
except Exception:
# If base64 decode fails, assume data is not encrypted
return encrypted_data

# Extract IV (first 16 bytes)
iv = encrypted_data[:AES.block_size]
# Extract ciphertext (remaining bytes)
ct = encrypted_data[AES.block_size:]

# Create cipher with IV
cipher = AES.new(ENCRYPTION_KEY, AES.MODE_CBC, iv)

# Decrypt and remove padding
pt = unpad(cipher.decrypt(ct), AES.block_size)
decrypted_data = pt.decode()

# Try to parse as JSON
try:
return json.loads(decrypted_data)
except json.JSONDecodeError:
return decrypted_data

except Exception as e:
raise ValueError(f"Error decrypting data: {e}")

Hash Generation

def generate_hash(data):
"""Generate SHA-256 hash for data integrity"""
import hashlib

if isinstance(data, dict):
data = json.dumps(data, sort_keys=True)

return hashlib.sha256(data.encode()).hexdigest()

Password Security

BCrypt Configuration

# managementProject/settings.py
PASSWORD_HASHERS = [
"django.contrib.auth.hashers.BCryptPasswordHasher",
]

# Password validation
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
'OPTIONS': {
'min_length': 8,
}
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]

Password Reset Flow

class PasswordResetView(APIView):
permission_classes = []

def post(self, request):
email = request.data.get('email')

try:
user = CustomUser.objects.get(email=email)

# Generate reset token
reset_token = JWTManager.generate_token(user)

# Send reset email
email_manager = EmailManager()
result = email_manager.send_password_reset_email(
user.email,
user.first_name,
reset_token
)

return Response({
'success': True,
'message': 'Email de recuperación enviado'
})

except CustomUser.DoesNotExist:
return Response({
'success': False,
'message': 'Email no encontrado'
}, status=404)

Digital Signatures

Signature Hash Manager

# managementApp/utilsfolder/SignatureHashUtils.py
import hashlib
import json
from datetime import datetime

class SignatureHashManager:
@staticmethod
def generate_signature_hash(contract_id, user_id, signature_data, timestamp=None):
"""Generate unique hash for digital signature"""
if timestamp is None:
timestamp = datetime.now().isoformat()

# Create signature payload
signature_payload = {
'contract_id': contract_id,
'user_id': user_id,
'signature_data': signature_data,
'timestamp': timestamp
}

# Convert to JSON string (sorted keys for consistency)
json_string = json.dumps(signature_payload, sort_keys=True)

# Generate SHA-256 hash
signature_hash = hashlib.sha256(json_string.encode()).hexdigest()

return signature_hash

@staticmethod
def verify_signature_hash(signature_hash, contract_id, user_id, signature_data, timestamp):
"""Verify signature hash integrity"""
expected_hash = SignatureHashManager.generate_signature_hash(
contract_id, user_id, signature_data, timestamp
)

return signature_hash == expected_hash

@staticmethod
def generate_contract_hash(contract_data):
"""Generate hash for entire contract"""
# Remove dynamic fields
contract_copy = contract_data.copy()
dynamic_fields = ['created_at', 'updated_at', 'id']

for field in dynamic_fields:
contract_copy.pop(field, None)

# Generate hash
json_string = json.dumps(contract_copy, sort_keys=True)
return hashlib.sha256(json_string.encode()).hexdigest()

Contract Signature Process

class ContractSignatureView(APIView):
def post(self, request):
contract_id = request.data.get('contract_id')
signature_data = request.data.get('signature_data') # Base64 image

try:
contract = ContractModel.objects.get(id=contract_id)
user = request.user

# Generate signature hash
signature_hash = SignatureHashManager.generate_signature_hash(
contract_id=contract.id,
user_id=user.id,
signature_data=signature_data
)

# Save signature
ContractSignature.objects.create(
contract=contract,
signer=user,
signature_data=signature_data,
signature_hash=signature_hash,
ip_address=get_client_ip(request),
user_agent=request.META.get('HTTP_USER_AGENT', '')
)

# Update contract with signature hash
if user == contract.landlord:
contract.landlord_signature_hash = signature_hash
elif user == contract.tenant:
contract.tenant_signature_hash = signature_hash
elif user == contract.guarantor:
contract.guarantor_signature_hash = signature_hash

contract.save()

return Response({
'success': True,
'message': 'Firma registrada exitosamente',
'signature_hash': signature_hash
})

except ContractModel.DoesNotExist:
return Response({
'success': False,
'message': 'Contrato no encontrado'
}, status=404)

CORS y Middleware

CORS Configuration

# managementProject/settings.py
CORS_ALLOW_ALL_ORIGINS = True
CORS_ALLOW_METHODS = ["DELETE", "GET", "OPTIONS", "PATCH", "POST", "PUT"]
CORS_ALLOW_HEADERS = [
"accept",
"accept-encoding",
"authorization",
"content-type",
"dnt",
"origin",
"user-agent",
"x-csrftoken",
"x-requested-with",
]

Authentication Middleware

# managementApp/middleware/AuthMiddleware.py
from django.utils.deprecation import MiddlewareMixin
from django.http import JsonResponse
from .utilsfolder.JWTUtils import JWTManager

class JWTAuthenticationMiddleware(MiddlewareMixin):
def process_request(self, request):
# Skip auth for certain paths
excluded_paths = ['/auth/login', '/auth/signup', '/health']
if request.path in excluded_paths:
return None

auth_header = request.META.get('HTTP_AUTHORIZATION')

if auth_header and auth_header.startswith('Bearer '):
token = auth_header[7:] # Remove 'Bearer ' prefix

try:
payload = JWTManager.decode_token(token)
user_id = payload.get('user_id')

# Set user in request
user = CustomUser.objects.get(id=user_id)
request.user = user

except Exception as e:
return JsonResponse({
'success': False,
'message': 'Token inválido'
}, status=401)
else:
return JsonResponse({
'success': False,
'message': 'Token de autorización requerido'
}, status=401)

return None

Validaciones de Seguridad

Input Validation

# managementApp/utilsfolder/ValidationUtils.py
import re
from django.core.validators import validate_email
from django.core.exceptions import ValidationError

def validate_phone_number(phone):
"""Validate Colombian phone number format"""
pattern = r'^\+57\s?[3][0-9]{9}$|^[3][0-9]{9}$'
if not re.match(pattern, phone):
raise ValidationError('Formato de teléfono inválido')

def validate_document_id(document_id):
"""Validate Colombian document ID"""
if not document_id.isdigit() or len(document_id) < 6:
raise ValidationError('Número de documento inválido')

def validate_password_strength(password):
"""Validate password meets security requirements"""
if len(password) < 8:
raise ValidationError('La contraseña debe tener al menos 8 caracteres')

if not re.search(r'[A-Z]', password):
raise ValidationError('La contraseña debe contener al menos una mayúscula')

if not re.search(r'[0-9]', password):
raise ValidationError('La contraseña debe contener al menos un número')

SQL Injection Prevention

  • Uso de Django ORM para queries seguras
  • Validación de entrada en serializadores
  • Parámetros sanitizados en queries raw cuando es necesario

XSS Prevention

  • Escape automático de templates Django
  • Validación de contenido HTML en campos de texto
  • Headers de seguridad configurados