
- Built complete CEX platform with FastAPI and Python - JWT-based authentication system with secure password hashing - Multi-currency crypto wallet support (BTC, ETH, USDT) - Fiat account management (USD, EUR, GBP) - Local transaction signing without external APIs - Comprehensive transaction handling (send/receive/deposit/withdraw) - SQLAlchemy models with Alembic migrations - Security middleware (rate limiting, headers, logging) - Input validation and sanitization - Encrypted private key storage with PBKDF2 - Standardized codebase architecture with service layer pattern - Complete API documentation with health endpoints - Comprehensive README with setup instructions Features: - User registration and authentication - Crypto wallet creation and management - Secure transaction signing using local private keys - Fiat deposit/withdrawal system - Transaction history and tracking - Rate limiting and security headers - Input validation for all endpoints - Error handling and logging
133 lines
4.1 KiB
Python
133 lines
4.1 KiB
Python
import re
|
|
from fastapi import HTTPException, status
|
|
|
|
|
|
class ValidationUtils:
|
|
@staticmethod
|
|
def validate_email(email: str) -> bool:
|
|
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
|
|
return re.match(pattern, email) is not None
|
|
|
|
@staticmethod
|
|
def validate_password(password: str) -> bool:
|
|
# At least 8 characters, one uppercase, one lowercase, one digit
|
|
if len(password) < 8:
|
|
return False
|
|
if not re.search(r'[A-Z]', password):
|
|
return False
|
|
if not re.search(r'[a-z]', password):
|
|
return False
|
|
if not re.search(r'\d', password):
|
|
return False
|
|
return True
|
|
|
|
@staticmethod
|
|
def validate_phone_number(phone: str) -> bool:
|
|
# Basic international phone number validation
|
|
pattern = r'^\+?[1-9]\d{1,14}$'
|
|
return re.match(pattern, phone) is not None
|
|
|
|
@staticmethod
|
|
def validate_amount(amount: float, min_amount: float = 0.0001, max_amount: float = 1000000) -> bool:
|
|
if amount <= 0:
|
|
return False
|
|
if amount < min_amount or amount > max_amount:
|
|
return False
|
|
return True
|
|
|
|
@staticmethod
|
|
def validate_currency_code(currency: str) -> bool:
|
|
valid_currencies = ['BTC', 'ETH', 'USDT', 'USD', 'EUR', 'GBP']
|
|
return currency.upper() in valid_currencies
|
|
|
|
@staticmethod
|
|
def sanitize_string(input_string: str, max_length: int = 255) -> str:
|
|
if not input_string:
|
|
return ""
|
|
|
|
# Remove potentially dangerous characters
|
|
sanitized = re.sub(r'[<>"\']', '', input_string)
|
|
|
|
# Trim to max length
|
|
return sanitized[:max_length].strip()
|
|
|
|
@staticmethod
|
|
def validate_kyc_level(level: int) -> bool:
|
|
return level in [0, 1, 2]
|
|
|
|
@staticmethod
|
|
def validate_transaction_type(transaction_type: str) -> bool:
|
|
valid_types = ['SEND', 'RECEIVE', 'DEPOSIT', 'WITHDRAWAL']
|
|
return transaction_type.upper() in valid_types
|
|
|
|
|
|
def validate_required_fields(data: dict, required_fields: list) -> None:
|
|
"""Validate that all required fields are present and not empty"""
|
|
missing_fields = []
|
|
for field in required_fields:
|
|
if field not in data or not data[field]:
|
|
missing_fields.append(field)
|
|
|
|
if missing_fields:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail=f"Missing required fields: {', '.join(missing_fields)}"
|
|
)
|
|
|
|
|
|
def validate_user_registration(email: str, password: str, full_name: str) -> None:
|
|
"""Validate user registration data"""
|
|
if not ValidationUtils.validate_email(email):
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="Invalid email format"
|
|
)
|
|
|
|
if not ValidationUtils.validate_password(password):
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="Password must be at least 8 characters with uppercase, lowercase, and digit"
|
|
)
|
|
|
|
if len(full_name.strip()) < 2:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="Full name must be at least 2 characters"
|
|
)
|
|
|
|
|
|
def validate_transaction_amount(amount: float, currency: str) -> None:
|
|
"""Validate transaction amount based on currency"""
|
|
min_amounts = {
|
|
'BTC': 0.00001,
|
|
'ETH': 0.001,
|
|
'USDT': 0.01,
|
|
'USD': 0.01,
|
|
'EUR': 0.01,
|
|
'GBP': 0.01
|
|
}
|
|
|
|
max_amounts = {
|
|
'BTC': 100,
|
|
'ETH': 1000,
|
|
'USDT': 100000,
|
|
'USD': 100000,
|
|
'EUR': 100000,
|
|
'GBP': 100000
|
|
}
|
|
|
|
currency = currency.upper()
|
|
min_amount = min_amounts.get(currency, 0.01)
|
|
max_amount = max_amounts.get(currency, 100000)
|
|
|
|
if amount < min_amount:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail=f"Minimum amount for {currency} is {min_amount}"
|
|
)
|
|
|
|
if amount > max_amount:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail=f"Maximum amount for {currency} is {max_amount}"
|
|
) |