Add encryption and setup wizard features
ENCRYPTION: - Add cryptography library for secure storage - Implement Fernet encryption for sensitive settings - Key derived from SESSION_SECRET - Auto-encrypt/decrypt transparent to app - "enc:" prefix identifies encrypted values - Graceful fallback if crypto unavailable SETUP WIZARD: - Detect fresh installs needing configuration - 3-step wizard: Infrastructure, Security, AI Services - "/" redirects to wizard if setup required - Auto-generate session_secret option - Skip option for advanced users - Beautiful UI with progress indicators UI IMPROVEMENTS: - Enhanced sensitive field display - Lock icon showing encryption status - Improved show/hide toggle for passwords - Better visual hierarchy FILES: - app/utils/encryption.py - Encryption utilities - app/utils/setup_wizard.py - Wizard detection logic - app/views/wizard.py - Wizard routes - frontend/templates/setup_wizard.html - Wizard UI - requirements.txt - Added cryptography - IMPLEMENTATION_CHECKLIST.md - Status tracking Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
This commit is contained in:
@@ -11,9 +11,11 @@ from app.views.dropbox import router as dropbox_router
|
||||
from app.views.google_drive import router as google_drive_router
|
||||
from app.views.license_routes import router as license_router # Add the license router
|
||||
from app.views.settings import router as settings_router
|
||||
from app.views.wizard import router as wizard_router
|
||||
|
||||
# Create a main router that includes all the view routers
|
||||
router = APIRouter()
|
||||
router.include_router(wizard_router) # Wizard first (for /setup)
|
||||
router.include_router(general_router)
|
||||
router.include_router(status_router)
|
||||
router.include_router(onedrive_router)
|
||||
|
||||
+19
-1
@@ -14,7 +14,25 @@ router = APIRouter()
|
||||
|
||||
@router.get("/", include_in_schema=False)
|
||||
async def serve_index(request: Request, db: Session = Depends(get_db)):
|
||||
"""Serve the index/home page."""
|
||||
"""
|
||||
Serve the index/home page.
|
||||
|
||||
If the system requires initial setup, redirect to the setup wizard.
|
||||
"""
|
||||
# Check if setup wizard is needed
|
||||
from app.utils.setup_wizard import is_setup_required
|
||||
from app.utils.settings_service import get_setting_from_db
|
||||
|
||||
# Check if setup was explicitly skipped
|
||||
setup_skipped = get_setting_from_db(db, "_setup_wizard_skipped")
|
||||
|
||||
# Check setup completion query param
|
||||
setup_complete = request.query_params.get("setup") == "complete"
|
||||
|
||||
if not setup_skipped and not setup_complete and is_setup_required():
|
||||
logger.info("System requires initial setup, redirecting to wizard")
|
||||
return RedirectResponse(url="/setup?step=1", status_code=303)
|
||||
|
||||
# Get provider information from config validator
|
||||
providers = get_provider_status()
|
||||
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
"""
|
||||
Setup wizard views for initial system configuration.
|
||||
"""
|
||||
|
||||
import os
|
||||
import logging
|
||||
import secrets
|
||||
from fastapi import Request, Depends, Form
|
||||
from fastapi.responses import RedirectResponse
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.views.base import APIRouter, templates, get_db
|
||||
from app.utils.setup_wizard import (
|
||||
is_setup_required,
|
||||
get_required_settings,
|
||||
get_wizard_steps,
|
||||
get_missing_required_settings
|
||||
)
|
||||
from app.utils.settings_service import save_setting_to_db
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/setup")
|
||||
async def setup_wizard(request: Request, step: int = 1):
|
||||
"""
|
||||
Setup wizard for first-time configuration.
|
||||
|
||||
This wizard guides users through configuring essential settings
|
||||
needed for the system to operate properly.
|
||||
"""
|
||||
# Get wizard steps
|
||||
wizard_steps = get_wizard_steps()
|
||||
max_step = max(wizard_steps.keys())
|
||||
|
||||
# Validate step number
|
||||
if step < 1:
|
||||
step = 1
|
||||
elif step > max_step:
|
||||
step = max_step
|
||||
|
||||
# Get settings for current step
|
||||
current_settings = wizard_steps.get(step, [])
|
||||
|
||||
# Get step category (all settings in a step should have same category)
|
||||
step_category = current_settings[0].get("wizard_category", "Configuration") if current_settings else "Configuration"
|
||||
|
||||
return templates.TemplateResponse(
|
||||
"setup_wizard.html",
|
||||
{
|
||||
"request": request,
|
||||
"current_step": step,
|
||||
"max_step": max_step,
|
||||
"settings": current_settings,
|
||||
"step_category": step_category,
|
||||
"progress_percent": int((step / max_step) * 100)
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@router.post("/setup")
|
||||
async def setup_wizard_save(
|
||||
request: Request,
|
||||
step: int = Form(...),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
Save settings from the current wizard step.
|
||||
"""
|
||||
try:
|
||||
# Get form data
|
||||
form_data = await request.form()
|
||||
|
||||
# Get settings for current step
|
||||
wizard_steps = get_wizard_steps()
|
||||
current_settings = wizard_steps.get(step, [])
|
||||
|
||||
# Save each setting from the form
|
||||
saved_count = 0
|
||||
for setting in current_settings:
|
||||
key = setting["key"]
|
||||
value = form_data.get(key)
|
||||
|
||||
# Skip empty values unless it's explicitly allowed
|
||||
if value and value.strip():
|
||||
# Auto-generate session_secret if needed
|
||||
if key == "session_secret" and value == "auto-generate":
|
||||
value = secrets.token_hex(32)
|
||||
logger.info("Auto-generated session secret")
|
||||
|
||||
# Save to database
|
||||
if save_setting_to_db(db, key, value):
|
||||
saved_count += 1
|
||||
logger.info(f"Setup wizard: Saved {key}")
|
||||
|
||||
logger.info(f"Setup wizard step {step}: Saved {saved_count} settings")
|
||||
|
||||
# Determine next step
|
||||
max_step = max(wizard_steps.keys())
|
||||
next_step = step + 1
|
||||
|
||||
if next_step > max_step:
|
||||
# Setup complete, redirect to home
|
||||
return RedirectResponse(url="/?setup=complete", status_code=303)
|
||||
else:
|
||||
# Go to next step
|
||||
return RedirectResponse(url=f"/setup?step={next_step}", status_code=303)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error saving wizard settings: {e}")
|
||||
return RedirectResponse(url=f"/setup?step={step}&error=save_failed", status_code=303)
|
||||
|
||||
|
||||
@router.get("/setup/skip")
|
||||
async def setup_wizard_skip(request: Request):
|
||||
"""
|
||||
Skip the setup wizard (for advanced users).
|
||||
|
||||
Creates a marker to indicate setup was skipped.
|
||||
"""
|
||||
try:
|
||||
db = next(get_db())
|
||||
try:
|
||||
# Save a marker to indicate setup was skipped
|
||||
save_setting_to_db(db, "_setup_wizard_skipped", "true")
|
||||
logger.info("Setup wizard skipped by user")
|
||||
return RedirectResponse(url="/", status_code=303)
|
||||
finally:
|
||||
db.close()
|
||||
except Exception as e:
|
||||
logger.error(f"Error skipping setup wizard: {e}")
|
||||
return RedirectResponse(url="/", status_code=303)
|
||||
Reference in New Issue
Block a user