✨ New Features: - AI-powered habit creation with natural language processing - HuggingFace transformers integration for sentiment analysis (tracked via Git LFS) - Advanced predictive analytics and behavioral insights - Voice & image input capabilities for hands-free habit tracking - Real-time notifications and community features - Plugin system with extensible architecture 🔧 Technical Improvements: - Comprehensive FastAPI backend with 30+ endpoints - React frontend with PWA capabilities - Advanced authentication with 2FA support - RBAC authorization system - Comprehensive security features (CSRF, rate limiting, audit logging) - Database migrations and health monitoring - Docker containerization support - Git LFS configured for large AI model files (2+ GB) 📚 Documentation & DevOps: - Complete deployment guides for multiple platforms - Professional README with feature highlights - GitHub Actions CI/CD workflows - Comprehensive API documentation - Security audit roadmap and compliance framework - Setup scripts for development environment 🧪 Testing & Quality: - Comprehensive test suite with 20+ test modules - Setup verification scripts - Working development environment with both backend and frontend - Health checks and monitoring systems 🌟 Ready for: - Portfolio showcasing - Community contributions - Production deployment - Professional presentation
130 lines
5.0 KiB
Python
130 lines
5.0 KiB
Python
"""
|
|
Pydantic models for request validation and security
|
|
"""
|
|
from pydantic import BaseModel, EmailStr, Field, validator
|
|
from typing import Optional, List
|
|
import re
|
|
|
|
class LoginRequest(BaseModel):
|
|
email: EmailStr
|
|
password: str = Field(..., min_length=8, max_length=128)
|
|
totp_code: Optional[str] = Field(None, pattern=r'^\d{6}$')
|
|
recovery_code: Optional[str] = Field(None, min_length=8, max_length=64)
|
|
|
|
class SignupRequest(BaseModel):
|
|
email: EmailStr
|
|
password: str = Field(..., min_length=8, max_length=128)
|
|
display_name: Optional[str] = Field(None, max_length=100)
|
|
|
|
@validator('password')
|
|
def validate_password(cls, v):
|
|
"""Enhanced NIST password guidelines with entropy checking"""
|
|
if len(v) < 8:
|
|
raise ValueError('Password must be at least 8 characters long')
|
|
if len(v) > 128:
|
|
raise ValueError('Password must be less than 128 characters')
|
|
|
|
# Check for common weak passwords
|
|
weak_patterns = [
|
|
r'^password\d*$', r'^123456\d*$', r'^qwerty\d*$',
|
|
r'^admin\d*$', r'^letmein\d*$', r'^welcome\d*$',
|
|
r'^football\d*$', r'^master\d*$', r'^guest\d*$'
|
|
]
|
|
for pattern in weak_patterns:
|
|
if re.match(pattern, v.lower()):
|
|
raise ValueError('Password is too common and easily guessable')
|
|
|
|
# Check for repeated characters (e.g., "aaaaaaaa")
|
|
if len(set(v)) < 4:
|
|
raise ValueError('Password must contain at least 4 unique characters')
|
|
|
|
# Check for sequential patterns
|
|
sequences = ['012345', '123456', '234567', '345678', '456789',
|
|
'abcdef', 'bcdefg', 'cdefgh', 'defghi']
|
|
for seq in sequences:
|
|
if seq in v.lower() or seq[::-1] in v.lower():
|
|
raise ValueError('Password cannot contain sequential patterns')
|
|
|
|
# Encourage complexity for shorter passwords
|
|
if len(v) < 12:
|
|
char_types = 0
|
|
if re.search(r'[a-z]', v): char_types += 1
|
|
if re.search(r'[A-Z]', v): char_types += 1
|
|
if re.search(r'[0-9]', v): char_types += 1
|
|
if re.search(r'[!@#$%^&*(),.?":{}|<>]', v): char_types += 1
|
|
|
|
if char_types < 3:
|
|
raise ValueError('Passwords under 12 characters must contain at least 3 character types (uppercase, lowercase, numbers, symbols)')
|
|
|
|
return v
|
|
|
|
class TwoFAEnableRequest(BaseModel):
|
|
code: str = Field(..., pattern=r'^\d{6}$')
|
|
|
|
class TwoFADisableRequest(BaseModel):
|
|
password: str = Field(..., min_length=8, max_length=128)
|
|
code: Optional[str] = Field(None, pattern=r'^\d{6}$')
|
|
|
|
class HabitCreateRequest(BaseModel):
|
|
title: str = Field(..., min_length=1, max_length=200)
|
|
description: Optional[str] = Field(None, max_length=1000)
|
|
category: Optional[str] = Field(None, max_length=50)
|
|
difficulty: Optional[int] = Field(1, ge=1, le=5)
|
|
|
|
@validator('title')
|
|
def validate_title(cls, v):
|
|
# Prevent XSS in titles
|
|
if '<' in v or '>' in v or 'script' in v.lower():
|
|
raise ValueError('Invalid characters in title')
|
|
return v.strip()
|
|
|
|
class HabitUpdateRequest(BaseModel):
|
|
title: Optional[str] = Field(None, min_length=1, max_length=200)
|
|
description: Optional[str] = Field(None, max_length=1000)
|
|
category: Optional[str] = Field(None, max_length=50)
|
|
difficulty: Optional[int] = Field(None, ge=1, le=5)
|
|
completed: Optional[bool] = None
|
|
|
|
@validator('title')
|
|
def validate_title(cls, v):
|
|
if v is not None:
|
|
if '<' in v or '>' in v or 'script' in v.lower():
|
|
raise ValueError('Invalid characters in title')
|
|
return v.strip()
|
|
return v
|
|
|
|
class ProjectCreateRequest(BaseModel):
|
|
title: str = Field(..., min_length=1, max_length=200)
|
|
description: Optional[str] = Field(None, max_length=2000)
|
|
|
|
@validator('title')
|
|
def validate_title(cls, v):
|
|
if '<' in v or '>' in v or 'script' in v.lower():
|
|
raise ValueError('Invalid characters in title')
|
|
return v.strip()
|
|
|
|
class ProjectUpdateRequest(BaseModel):
|
|
title: Optional[str] = Field(None, min_length=1, max_length=200)
|
|
description: Optional[str] = Field(None, max_length=2000)
|
|
|
|
@validator('title')
|
|
def validate_title(cls, v):
|
|
if v is not None:
|
|
if '<' in v or '>' in v or 'script' in v.lower():
|
|
raise ValueError('Invalid characters in title')
|
|
return v.strip()
|
|
return v
|
|
|
|
class TokenCreateRequest(BaseModel):
|
|
name: str = Field(..., min_length=1, max_length=100)
|
|
permissions: List[str] = Field(default_factory=list)
|
|
expires_in_days: Optional[int] = Field(30, ge=1, le=365)
|
|
|
|
@validator('permissions')
|
|
def validate_permissions(cls, v):
|
|
allowed_permissions = ['read:habits', 'read:projects', 'read:analytics']
|
|
for perm in v:
|
|
if perm not in allowed_permissions:
|
|
raise ValueError(f'Invalid permission: {perm}')
|
|
return v
|