Kyle Pope 44e6c8e3e5 Fix 3 pentest findings: lockout status disclosure, timing side-channel, XFF trust scope
F-01 (passkeys.py): Add constant-time DB no-op on login/begin when username not
found. Without it the absent credential-fetch query makes the "no user" path
measurably faster, leaking username existence via timing.

F-02 (session.py, auth.py, passkeys.py, totp.py): Change check_account_lockout
from HTTP 423 to 401 — status-code analysis can no longer distinguish a locked
account from an invalid credential. record_failed_login now returns remaining
attempt count; callers use it for progressive UX warnings (<=3 attempts left,
and on the locking attempt) without changing the 401 status code visible to
attackers. Session-lock 423 path in get_current_user is unaffected.

F-03 (nginx.conf): Replace set_real_ip_from 0.0.0.0/0 with RFC 1918 ranges
(172.16.0.0/12, 10.0.0.0/8) to prevent external clients from spoofing
X-Forwarded-For to bypass rate limiting.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 01:01:19 +08:00
..

UMBRA Backend

FastAPI backend for the UMBRA life management application with async SQLAlchemy, PostgreSQL, multi-user RBAC, and comprehensive security.

Features

  • FastAPI with async/await and Pydantic v2
  • SQLAlchemy 2.0 async engine with Mapped[] types
  • PostgreSQL 16 via asyncpg
  • Alembic database migrations (001-061)
  • Authentication: Argon2id passwords + signed httpOnly cookies + optional TOTP MFA + passkey (WebAuthn/FIDO2)
  • Multi-user RBAC: admin/standard roles, per-user resource scoping
  • Session management: DB-backed sessions, sliding window expiry, concurrent session cap
  • Account security: Account lockout (10 failures = 30-min lock), CSRF protection, rate limiting
  • APScheduler for background notification dispatch

Project Structure

backend/
├── alembic/versions/      # 61 database migrations
├── app/
│   ├── models/            # 21 SQLAlchemy 2.0 models
│   ├── schemas/           # 14 Pydantic v2 schema modules
│   ├── routers/           # 17 API routers
│   ├── services/          # Auth, session, passkey, TOTP, audit, recurrence, etc.
│   ├── jobs/              # APScheduler notification dispatch
│   ├── config.py          # Pydantic Settings (env vars)
│   ├── database.py        # Async engine + session factory
│   └── main.py            # FastAPI app + CSRF middleware
├── requirements.txt
├── Dockerfile
├── alembic.ini
└── start.sh

Setup

1. Install Dependencies

pip install -r requirements.txt

2. Configure Environment

Copy .env.example to .env and configure:

DATABASE_URL=postgresql+asyncpg://postgres:postgres@db:5432/umbra
SECRET_KEY=generate-a-strong-random-key
ENVIRONMENT=production

# WebAuthn / Passkeys (required for passkey auth)
WEBAUTHN_RP_ID=your-domain.com
WEBAUTHN_RP_NAME=UMBRA
WEBAUTHN_ORIGIN=https://your-domain.com

3. Run Migrations

alembic upgrade head

4. Start Server

uvicorn app.main:app --host 0.0.0.0 --port 8000

API Routes

All routes require authentication (signed session cookie) except /api/auth/* and /health.

Prefix Description
/api/auth Login, logout, register, setup, status, password, TOTP, passkeys
/api/admin User management, system config, audit logs (admin only)
/api/todos Task management with categories and priorities
/api/events Calendar events with recurrence support
/api/event-invitations Event invitation RSVP and management
/api/event-templates Reusable event templates
/api/calendars Calendar CRUD
/api/shared-calendars Calendar sharing with permission levels
/api/reminders Reminder management with snooze
/api/projects Projects with tasks, comments, and collaboration
/api/people Contact management
/api/locations Location management
/api/connections User connections (friend requests)
/api/notifications In-app notification centre
/api/settings User preferences and ntfy configuration
/api/dashboard Aggregated dashboard data
/api/weather Weather widget data

Authentication

UMBRA supports three authentication methods:

  1. Password (Argon2id) - Primary login method
  2. TOTP MFA - Optional second factor via authenticator apps
  3. Passkeys (WebAuthn/FIDO2) - Optional passwordless login via biometrics, security keys, or password managers

Passkey login bypasses TOTP (a passkey is inherently two-factor: possession + biometric/PIN).

Security

  • CSRF protection via X-Requested-With header middleware
  • All Pydantic schemas use extra="forbid" (mass-assignment prevention)
  • Nginx rate limiting on auth, registration, and admin endpoints
  • DB-backed account lockout after 10 failed attempts
  • Timing-safe dummy hash for non-existent users (prevents enumeration)
  • SSRF validation on ntfy webhook URLs
  • Naive datetimes throughout (Docker runs UTC)

Docker

The backend runs as non-root appuser in python:3.12-slim:

docker build -t umbra-backend .
docker run -p 8000:8000 --env-file .env umbra-backend

In production, use Docker Compose (see root docker-compose.yaml).