Implement Phase 2: Refresh tokens, 2FA, password reset, and audit logging

Co-authored-by: mblanke <9078342+mblanke@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2025-12-09 17:30:12 +00:00
parent ddf287cde7
commit c8c0c762c5
15 changed files with 716 additions and 9 deletions

52
backend/app/core/audit.py Normal file
View File

@@ -0,0 +1,52 @@
from typing import Optional, Dict, Any
from sqlalchemy.orm import Session
from fastapi import Request
from app.models.audit_log import AuditLog
def log_action(
db: Session,
user_id: Optional[int],
tenant_id: int,
action: str,
resource_type: str,
resource_id: Optional[int] = None,
details: Optional[Dict[str, Any]] = None,
request: Optional[Request] = None
):
"""
Log an action to the audit log
Args:
db: Database session
user_id: ID of user performing action (None for system actions)
tenant_id: Tenant ID
action: Action type (CREATE, READ, UPDATE, DELETE, LOGIN, etc.)
resource_type: Type of resource (user, host, case, etc.)
resource_id: ID of the resource (if applicable)
details: Additional details as JSON
request: FastAPI request object (for IP and user agent)
"""
ip_address = None
user_agent = None
if request:
ip_address = request.client.host if request.client else None
user_agent = request.headers.get("user-agent")
audit_log = AuditLog(
user_id=user_id,
tenant_id=tenant_id,
action=action,
resource_type=resource_type,
resource_id=resource_id,
details=details,
ip_address=ip_address,
user_agent=user_agent
)
db.add(audit_log)
db.commit()
return audit_log

View File

@@ -7,7 +7,18 @@ class Settings(BaseSettings):
database_url: str = "postgresql://postgres:postgres@db:5432/velocicompanion"
secret_key: str = "your-secret-key-change-in-production-min-32-chars-long"
access_token_expire_minutes: int = 30
refresh_token_expire_days: int = 30
algorithm: str = "HS256"
# Email settings (for password reset)
smtp_host: str = "localhost"
smtp_port: int = 587
smtp_user: str = ""
smtp_password: str = ""
from_email: str = "noreply@velocicompanion.com"
# WebSocket settings
ws_enabled: bool = True
class Config:
env_file = ".env"

View File

@@ -1,5 +1,7 @@
from datetime import datetime, timedelta, timezone
from typing import Optional
import secrets
import pyotp
from jose import JWTError, jwt
from passlib.context import CryptContext
@@ -56,3 +58,63 @@ def verify_token(token: str) -> Optional[dict]:
return payload
except JWTError:
return None
def create_refresh_token() -> str:
"""
Create a secure random refresh token
Returns:
Random token string
"""
return secrets.token_urlsafe(32)
def create_reset_token() -> str:
"""
Create a secure random password reset token
Returns:
Random token string
"""
return secrets.token_urlsafe(32)
def generate_totp_secret() -> str:
"""
Generate a TOTP secret for 2FA
Returns:
Base32 encoded secret
"""
return pyotp.random_base32()
def verify_totp(secret: str, code: str) -> bool:
"""
Verify a TOTP code
Args:
secret: TOTP secret
code: 6-digit code from authenticator app
Returns:
True if code is valid
"""
totp = pyotp.TOTP(secret)
return totp.verify(code, valid_window=1)
def get_totp_uri(secret: str, username: str) -> str:
"""
Get TOTP provisioning URI for QR code
Args:
secret: TOTP secret
username: User's username
Returns:
otpauth:// URI
"""
totp = pyotp.TOTP(secret)
return totp.provisioning_uri(name=username, issuer_name="VelociCompanion")