security: remove hardcoded credentials and externalize user profiles

- DATABASE_URL now fails fast if not set (no default with password)
- User profiles loaded from JSON config (config/user_profiles.json)
- Add .env.example as template for environment variables
- Add config/user_profiles.json.example as template for user configuration
- Update .gitignore to exclude sensitive config files
This commit is contained in:
AI Station Server 2026-01-20 17:21:24 +01:00
parent c3931d78b1
commit d27421a267
4 changed files with 104 additions and 48 deletions

23
.env.example Normal file
View File

@ -0,0 +1,23 @@
# AI Station - Environment Variables Template
# Copy this file to .env and fill in your values
# IMPORTANT: Never commit .env to version control!
# === GOOGLE OAUTH ===
OAUTH_GOOGLE_CLIENT_ID=your_client_id_here
OAUTH_GOOGLE_CLIENT_SECRET=your_client_secret_here
# === CHAINLIT CONFIGURATION ===
CHAINLIT_URL=https://ai.dffm.it
CHAINLIT_AUTH_SECRET=generate_a_secure_random_string_here
# === DATABASE ===
# Format: postgresql+asyncpg://username:password@hostname:port/database_name
DATABASE_URL=postgresql+asyncpg://ai_user:your_secure_password@postgres:5432/ai_station
# === AI SERVICES ===
OLLAMA_URL=http://192.168.1.243:11434
QDRANT_URL=http://qdrant:6333
BGE_API_URL=http://192.168.1.243:8001/embed
# === OPTIONAL ===
# GEMINI_API_KEY=your_gemini_api_key_here

1
.gitignore vendored
View File

@ -7,3 +7,4 @@ qdrant_storage/.files/
__pycache__/ __pycache__/
.env .env
.files/ .files/
config/user_profiles.json

72
app.py
View File

@ -1,5 +1,6 @@
import os import os
import re import re
import json
import uuid import uuid
import shutil import shutil
import requests import requests
@ -37,10 +38,15 @@ except ImportError:
# ========================= # =========================
# CONFIG # CONFIG
# ========================= # =========================
DATABASE_URL = os.getenv( # SECURITY: Fail fast if DATABASE_URL is not set
"DATABASE_URL", DATABASE_URL = os.getenv("DATABASE_URL")
"postgresql+asyncpg://ai_user:secure_password_here@postgres:5432/ai_station", if not DATABASE_URL:
raise EnvironmentError(
"DATABASE_URL environment variable is required. "
"Set it via: export DATABASE_URL='postgresql+asyncpg://user:pass@host:5432/db'"
) )
# Service URLs - can be overridden via environment
OLLAMA_URL = os.getenv("OLLAMA_URL", "http://192.168.1.243:11434") OLLAMA_URL = os.getenv("OLLAMA_URL", "http://192.168.1.243:11434")
QDRANT_URL = os.getenv("QDRANT_URL", "http://qdrant:6333") QDRANT_URL = os.getenv("QDRANT_URL", "http://qdrant:6333")
BGE_API_URL = os.getenv("BGE_API_URL", "http://192.168.1.243:8001/embed") BGE_API_URL = os.getenv("BGE_API_URL", "http://192.168.1.243:8001/embed")
@ -64,50 +70,24 @@ os.makedirs(STORAGE_DIR, exist_ok=True)
os.makedirs(WORKSPACES_DIR, exist_ok=True) os.makedirs(WORKSPACES_DIR, exist_ok=True)
# ========================= # =========================
# USER PROFILES # USER PROFILES (loaded from JSON for security)
# ========================= # =========================
USER_PROFILES = { def load_user_profiles() -> Dict[str, Dict]:
"giuseppe@defranceschi.pro": { """Load user profiles from JSON config file."""
"role": "admin", config_path = os.getenv("USER_PROFILES_PATH", "config/user_profiles.json")
"name": "Giuseppe", try:
"workspace": "admin_workspace", with open(config_path, "r", encoding="utf-8") as f:
"rag_collection": "admin_docs", config = json.load(f)
"capabilities": ["debug", "all"], return config.get("user_profiles", {})
"show_code": True, except FileNotFoundError:
}, print(f"WARNING: User profiles config not found at {config_path}")
"federica.tecchio@gmail.com": { return {}
"role": "business", except json.JSONDecodeError as e:
"name": "Federica", print(f"WARNING: Invalid JSON in user profiles config: {e}")
"workspace": "business_workspace", return {}
"rag_collection": "contabilita",
"capabilities": ["basic_chat"], # Load profiles at startup
"show_code": False, USER_PROFILES = load_user_profiles()
},
"riccardob545@gmail.com": {
"role": "engineering",
"name": "Riccardo",
"workspace": "engineering_workspace",
"rag_collection": "engineering_docs",
"capabilities": ["code"],
"show_code": True,
},
"giuliadefranceschi05@gmail.com": {
"role": "architecture",
"name": "Giulia",
"workspace": "architecture_workspace",
"rag_collection": "architecture_manuals",
"capabilities": ["visual"],
"show_code": False,
},
"giuseppe.defranceschi@gmail.com": {
"role": "architecture",
"name": "Giuseppe",
"workspace": "architecture_workspace",
"rag_collection": "architecture_manuals",
"capabilities": ["visual"],
"show_code": False,
},
}
GUEST_PROFILE = { GUEST_PROFILE = {
"role": "guest", "role": "guest",

View File

@ -0,0 +1,52 @@
{
"user_profiles": {
"giuseppe@defranceschi.pro": {
"role": "admin",
"name": "Giuseppe",
"workspace": "admin_workspace",
"rag_collection": "admin_docs",
"capabilities": ["debug", "all"],
"show_code": true
},
"federica.tecchio@gmail.com": {
"role": "business",
"name": "Federica",
"workspace": "business_workspace",
"rag_collection": "contabilita",
"capabilities": ["basic_chat"],
"show_code": false
},
"riccardob545@gmail.com": {
"role": "engineering",
"name": "Riccardo",
"workspace": "engineering_workspace",
"rag_collection": "engineering_docs",
"capabilities": ["code"],
"show_code": true
},
"giuliadefranceschi05@gmail.com": {
"role": "architecture",
"name": "Giulia",
"workspace": "architecture_workspace",
"rag_collection": "architecture_manuals",
"capabilities": ["visual"],
"show_code": false
},
"giuseppe.defranceschi@gmail.com": {
"role": "architecture",
"name": "Giuseppe",
"workspace": "architecture_workspace",
"rag_collection": "architecture_manuals",
"capabilities": ["visual"],
"show_code": false
}
},
"guest_profile": {
"role": "guest",
"name": "Guest",
"workspace": "guest",
"rag_collection": "public",
"capabilities": ["basic_chat"],
"show_code": false
}
}