Files
PROJECT-CONTORL/backend/app/models/audit_log.py
beabigegg 35c90fe76b feat: implement 5 QA-driven security and quality proposals
Implemented proposals from comprehensive QA review:

1. extend-csrf-protection
   - Add POST to CSRF protected methods in frontend
   - Global CSRF middleware for all state-changing operations
   - Update tests with CSRF token fixtures

2. tighten-cors-websocket-security
   - Replace wildcard CORS with explicit method/header lists
   - Disable query parameter auth in production (code 4002)
   - Add per-user WebSocket connection limit (max 5, code 4005)

3. shorten-jwt-expiry
   - Reduce JWT expiry from 7 days to 60 minutes
   - Add refresh token support with 7-day expiry
   - Implement token rotation on refresh
   - Frontend auto-refresh when token near expiry (<5 min)

4. fix-frontend-quality
   - Add React.lazy() code splitting for all pages
   - Fix useCallback dependency arrays (Dashboard, Comments)
   - Add localStorage data validation in AuthContext
   - Complete i18n for AttachmentUpload component

5. enhance-backend-validation
   - Add SecurityAuditMiddleware for access denied logging
   - Add ErrorSanitizerMiddleware for production error messages
   - Protect /health/detailed with admin authentication
   - Add input length validation (comment 5000, desc 10000)

All 521 backend tests passing. Frontend builds successfully.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 23:19:05 +08:00

94 lines
3.2 KiB
Python

import uuid
from sqlalchemy import Column, String, Text, DateTime, ForeignKey, Enum, Index, JSON
from sqlalchemy.sql import func
from sqlalchemy.orm import relationship
from app.core.database import Base
import enum
class AuditAction(str, enum.Enum):
CREATE = "create"
UPDATE = "update"
DELETE = "delete"
RESTORE = "restore"
LOGIN = "login"
LOGOUT = "logout"
ACCESS_DENIED = "access_denied"
AUTH_FAILED = "auth_failed"
class SensitivityLevel(str, enum.Enum):
LOW = "low"
MEDIUM = "medium"
HIGH = "high"
CRITICAL = "critical"
# Event type to sensitivity level mapping
EVENT_SENSITIVITY = {
"task.create": SensitivityLevel.LOW,
"task.update": SensitivityLevel.LOW,
"task.delete": SensitivityLevel.MEDIUM,
"task.restore": SensitivityLevel.MEDIUM,
"task.assign": SensitivityLevel.LOW,
"task.blocker": SensitivityLevel.MEDIUM,
"project.create": SensitivityLevel.MEDIUM,
"project.update": SensitivityLevel.MEDIUM,
"project.delete": SensitivityLevel.HIGH,
"user.login": SensitivityLevel.LOW,
"user.logout": SensitivityLevel.LOW,
"user.role_change": SensitivityLevel.HIGH,
"user.admin_change": SensitivityLevel.CRITICAL,
"user.permission_change": SensitivityLevel.CRITICAL,
"role.permission_change": SensitivityLevel.CRITICAL,
"attachment.upload": SensitivityLevel.LOW,
"attachment.download": SensitivityLevel.LOW,
"attachment.delete": SensitivityLevel.MEDIUM,
# Security events
"security.access_denied": SensitivityLevel.MEDIUM,
"security.auth_failed": SensitivityLevel.MEDIUM,
"security.suspicious_auth_pattern": SensitivityLevel.HIGH,
}
# Events that should trigger alerts
ALERT_EVENTS = {
"project.delete",
"user.permission_change",
"user.admin_change",
"role.permission_change",
"security.suspicious_auth_pattern",
}
class AuditLog(Base):
__tablename__ = "pjctrl_audit_logs"
id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
event_type = Column(String(50), nullable=False)
resource_type = Column(String(50), nullable=False)
resource_id = Column(String(36), nullable=True)
user_id = Column(String(36), ForeignKey("pjctrl_users.id", ondelete="SET NULL"), nullable=True)
action = Column(
Enum("create", "update", "delete", "restore", "login", "logout", "access_denied", "auth_failed", name="audit_action_enum"),
nullable=False
)
changes = Column(JSON, nullable=True)
request_metadata = Column(JSON, nullable=True)
sensitivity_level = Column(
Enum("low", "medium", "high", "critical", name="sensitivity_level_enum"),
default="low",
nullable=False
)
checksum = Column(String(64), nullable=False)
created_at = Column(DateTime, server_default=func.now(), nullable=False)
# Relationships
user = relationship("User", foreign_keys=[user_id])
alerts = relationship("AuditAlert", back_populates="audit_log", cascade="all, delete-orphan")
__table_args__ = (
Index("idx_audit_user", "user_id", "created_at"),
Index("idx_audit_resource", "resource_type", "resource_id", "created_at"),
Index("idx_audit_time", "created_at"),
)