Phase 1: Add role, mfa_enforce_pending, must_change_password to users table. Create system_config (singleton) and audit_log tables. Migration 026. Phase 2: Add user_id FK to all 8 data tables (todos, reminders, projects, calendars, people, locations, event_templates, ntfy_sent) with 4-step nullable→backfill→FK→NOT NULL pattern. Migrations 027-034. Phase 3: Harden auth schemas (extra="forbid" on RegisterRequest), add MFA enforcement token serializer with distinct salt, rewrite auth router with require_role() factory and registration endpoint. Phase 4: Scope all 12 routers by user_id, fix dependency type bugs, bound weather cache (SEC-15), multi-user ntfy dispatch. Phase 5: Create admin router (14 endpoints), admin schemas, audit service, rate limiting in nginx. SEC-08 CSRF via X-Requested-With. Phase 6: Update frontend types, useAuth hook (role/isAdmin/register), App.tsx (AdminRoute guard), Sidebar (admin link), api.ts (XHR header). Security findings addressed: SEC-01, SEC-02, SEC-03, SEC-04, SEC-05, SEC-06, SEC-07, SEC-08, SEC-12, SEC-13, SEC-15. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
84 lines
3.2 KiB
Python
84 lines
3.2 KiB
Python
from fastapi import FastAPI
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from contextlib import asynccontextmanager
|
|
|
|
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
|
|
|
from app.config import settings
|
|
from app.database import engine
|
|
from app.routers import auth, todos, events, calendars, reminders, projects, people, locations, settings as settings_router, dashboard, weather, event_templates
|
|
from app.routers import totp, admin
|
|
from app.jobs.notifications import run_notification_dispatch
|
|
|
|
# Import models so Alembic's autogenerate can discover them
|
|
from app.models import user as _user_model # noqa: F401
|
|
from app.models import session as _session_model # noqa: F401
|
|
from app.models import totp_usage as _totp_usage_model # noqa: F401
|
|
from app.models import backup_code as _backup_code_model # noqa: F401
|
|
from app.models import system_config as _system_config_model # noqa: F401
|
|
from app.models import audit_log as _audit_log_model # noqa: F401
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI):
|
|
scheduler = AsyncIOScheduler()
|
|
scheduler.add_job(
|
|
run_notification_dispatch,
|
|
"interval",
|
|
minutes=1,
|
|
id="ntfy_dispatch",
|
|
max_instances=1, # prevent overlap if a run takes longer than 60s
|
|
)
|
|
scheduler.start()
|
|
yield
|
|
scheduler.shutdown(wait=False)
|
|
await engine.dispose()
|
|
|
|
|
|
_is_dev = settings.ENVIRONMENT == "development"
|
|
|
|
app = FastAPI(
|
|
title="UMBRA API",
|
|
description="Backend API for UMBRA application",
|
|
version="1.0.0",
|
|
lifespan=lifespan,
|
|
docs_url="/docs" if _is_dev else None,
|
|
redoc_url="/redoc" if _is_dev else None,
|
|
openapi_url="/openapi.json" if _is_dev else None,
|
|
)
|
|
|
|
# CORS configuration
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=[o.strip() for o in settings.CORS_ORIGINS.split(",") if o.strip()],
|
|
allow_credentials=True,
|
|
allow_methods=["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
|
|
allow_headers=["Content-Type", "Authorization", "Cookie"],
|
|
)
|
|
|
|
# Include routers with /api prefix
|
|
app.include_router(auth.router, prefix="/api/auth", tags=["Authentication"])
|
|
app.include_router(todos.router, prefix="/api/todos", tags=["Todos"])
|
|
app.include_router(events.router, prefix="/api/events", tags=["Calendar Events"])
|
|
app.include_router(calendars.router, prefix="/api/calendars", tags=["Calendars"])
|
|
app.include_router(reminders.router, prefix="/api/reminders", tags=["Reminders"])
|
|
app.include_router(projects.router, prefix="/api/projects", tags=["Projects"])
|
|
app.include_router(people.router, prefix="/api/people", tags=["People"])
|
|
app.include_router(locations.router, prefix="/api/locations", tags=["Locations"])
|
|
app.include_router(settings_router.router, prefix="/api/settings", tags=["Settings"])
|
|
app.include_router(dashboard.router, prefix="/api", tags=["Dashboard"])
|
|
app.include_router(weather.router, prefix="/api/weather", tags=["Weather"])
|
|
app.include_router(event_templates.router, prefix="/api/event-templates", tags=["Event Templates"])
|
|
app.include_router(totp.router, prefix="/api/auth", tags=["TOTP MFA"])
|
|
app.include_router(admin.router, prefix="/api/admin", tags=["Admin"])
|
|
|
|
|
|
@app.get("/")
|
|
async def root():
|
|
return {"message": "UMBRA API is running"}
|
|
|
|
|
|
@app.get("/health")
|
|
async def health_check():
|
|
return {"status": "healthy"}
|