Kyle Pope bcfebbc9ae feat(backend): Phase 1 passwordless login — migration, models, toggle endpoints, unlock, delete guard, admin controls
- Migration 062: adds users.passwordless_enabled and system_config.allow_passwordless (both default false)
- User model: passwordless_enabled field after must_change_password
- SystemConfig model: allow_passwordless field after enforce_mfa_new_users
- auth.py login(): block passwordless-enabled accounts from password login path (403) with audit log
- auth.py auth_status(): change has_passkeys query to full COUNT, add passkey_count + passwordless_enabled to response
- auth.py get_current_user(): add /api/auth/passkeys/login/begin and /login/complete to lock_exempt set
- passkeys.py: add PasswordlessEnableRequest + PasswordlessDisableRequest schemas
- passkeys.py: PUT /passwordless/enable — verify password, check system config, require >= 2 passkeys, set flag
- passkeys.py: POST /passwordless/disable/begin — generate user-bound challenge for passkey auth ceremony
- passkeys.py: PUT /passwordless/disable — verify passkey auth response, clear flag, update sign count
- passkeys.py: PasskeyLoginCompleteRequest.unlock field — passkey re-auth into locked session without new session
- passkeys.py: delete guard — 409 if passwordless user attempts to drop below 2 passkeys
- schemas/admin.py: add passwordless_enabled to UserListItem + UserDetailResponse; add allow_passwordless to SystemConfigResponse + SystemConfigUpdate; add TogglePasswordlessRequest
- admin.py: PUT /users/{user_id}/passwordless — admin-only disable (enabled=False only), revokes all sessions, audit log
- admin.py: update_system_config handles allow_passwordless field

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 00:15:39 +08:00
..
2026-02-15 16:13:41 +08:00
2026-02-15 16:13:41 +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).