43e1d0145e
- GET /api/auth/totp/setup: returns provisioning_uri + secret (400 if already enabled) - POST /api/auth/totp/enable: rate-limited 10/min, verifies TOTP code with Redis replay prevention, returns 10 backup codes - DELETE /api/auth/totp: disables TOTP, clears secret, deletes backup codes - POST /api/auth/password-reset: always returns 202 (anti-enumeration), enqueues Celery email task - POST /api/auth/password-reset/confirm: validates token, strength, HIBP; updates password; no auto-login (AUTH-05) - config.py: added frontend_url setting for password reset link construction - test_auth_totp.py: all 11 tests passing (GREEN)
92 lines
3.0 KiB
Python
92 lines
3.0 KiB
Python
from pathlib import Path
|
|
|
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
|
|
|
|
class Settings(BaseSettings):
|
|
"""Phase 1 Pydantic Settings — reads all Phase 1 env vars from environment or .env file."""
|
|
|
|
model_config = SettingsConfigDict(
|
|
env_file=".env",
|
|
env_file_encoding="utf-8",
|
|
extra="ignore",
|
|
env_list_separator=",",
|
|
)
|
|
|
|
# Data directory — used only for the flat-file settings.json path (Phase 1)
|
|
data_dir: str = "/app/data"
|
|
|
|
# PostgreSQL
|
|
database_url: str = "postgresql+psycopg://docuvault_app:changeme_app@postgres:5432/docuvault"
|
|
database_migrate_url: str = "postgresql+psycopg://docuvault_migrate:changeme_migrate@postgres:5432/docuvault"
|
|
|
|
# MinIO
|
|
minio_endpoint: str = "minio:9000"
|
|
minio_access_key: str = "docuvault_app"
|
|
minio_secret_key: str = "changeme_minio_app"
|
|
minio_bucket: str = "docuvault"
|
|
|
|
# Redis / Celery
|
|
redis_url: str = "redis://:changeme_redis@redis:6379/0"
|
|
|
|
# Security (Phase 2 — documented now, not read by Phase 1 code paths)
|
|
secret_key: str = "CHANGEME"
|
|
|
|
# Auth / JWT (Phase 2)
|
|
access_token_expire_minutes: int = 15
|
|
refresh_token_expire_days: int = 30
|
|
|
|
# SMTP (Phase 2 — D-01)
|
|
smtp_host: str = ""
|
|
smtp_port: int = 587
|
|
smtp_user: str = ""
|
|
smtp_password: str = ""
|
|
smtp_from: str = "noreply@docuvault.local"
|
|
|
|
# Admin bootstrap (Phase 2 — D-04)
|
|
admin_email: str = ""
|
|
admin_password: str = ""
|
|
|
|
# CORS (Phase 2 — D-09)
|
|
cors_origins: list[str] = ["http://localhost:5173"]
|
|
|
|
# Frontend URL — used to build password reset links (D-02, D-03)
|
|
frontend_url: str = "http://localhost:5173"
|
|
|
|
|
|
settings = Settings()
|
|
|
|
# SETTINGS_FILE: still flat-file in Phase 1; migrates to users.ai_provider in Phase 2
|
|
SETTINGS_FILE = Path(settings.data_dir) / "settings.json"
|
|
|
|
DEFAULT_SYSTEM_PROMPT = """You are a document classification assistant. When given a document's text content and a list of existing topics, you must:
|
|
1. Assign the document to one or more relevant topics from the list.
|
|
2. If no existing topics fit well, suggest new topic names.
|
|
Return ONLY valid JSON in this exact format, with no additional text or explanation:
|
|
{"assigned_topics": ["topic1"], "new_topic_suggestions": ["new topic name"]}
|
|
If the document fits no topics and you have no suggestions, return: {"assigned_topics": [], "new_topic_suggestions": []}"""
|
|
|
|
DEFAULT_SETTINGS = {
|
|
"system_prompt": DEFAULT_SYSTEM_PROMPT,
|
|
"active_provider": "lmstudio",
|
|
"providers": {
|
|
"anthropic": {
|
|
"api_key": "",
|
|
"model": "claude-sonnet-4-6"
|
|
},
|
|
"openai": {
|
|
"api_key": "",
|
|
"model": "gpt-4o",
|
|
"base_url": None
|
|
},
|
|
"ollama": {
|
|
"base_url": "http://host.docker.internal:11434",
|
|
"model": "llama3.2"
|
|
},
|
|
"lmstudio": {
|
|
"base_url": "http://host.docker.internal:1234",
|
|
"model": "gemma-4-e4b-it"
|
|
}
|
|
}
|
|
}
|