mirror of
https://github.com/mblanke/ThreatHunt.git
synced 2026-03-01 22:00:22 -05:00
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:
52
backend/app/core/audit.py
Normal file
52
backend/app/core/audit.py
Normal 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
|
||||
@@ -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"
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user