Both the index.html inline script and useTheme now use setProperty with 'important' priority flag. This is the highest CSS cascade priority and cannot be overridden by Vite's stylesheet injection, @layer rules, or source order. Removes the <style> tag injection approach which was being overridden. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
UMBRA
A self-hosted, multi-user life administration app with a dark-themed UI and role-based access control. Manage your todos, calendar events, projects, reminders, contacts, and locations from a single dashboard.
Features
- Multi-user RBAC - Admin and standard user roles, per-user data isolation, admin portal with IAM, system config, and audit logs
- Dashboard - Contextual greeting, week timeline, stat cards, upcoming events, weather widget, day briefing
- Todos - Task management with priorities, due dates, recurrence, and grouped sections (overdue/today/upcoming)
- Calendar - Multi-calendar system with month/week/day views, recurring events, drag-and-drop, event templates
- Projects - Project boards with kanban view, nested tasks/subtasks, comments, progress tracking
- Reminders - Time-based reminders with snooze, dismiss, recurrence, and real-time alert notifications (dashboard banner + toasts)
- People - Contact directory with avatar initials, favourites, birthday tracking, category filtering
- Locations - Location management with OSM search integration, category filtering, frequent locations
- Weather - Dashboard weather widget with temperature, conditions, and contextual rain warnings
- Settings - Accent color picker (8 presets), first day of week, weather city, ntfy push notifications, TOTP two-factor auth, auto-lock, password management
- Notifications - ntfy push notifications for reminders (configurable per-user)
- Admin Portal - User management (create, delete, activate/deactivate, role assignment, password reset), system configuration (open registration, MFA enforcement), audit log viewer
Tech Stack
| Layer | Technology |
|---|---|
| Frontend | React 18, TypeScript, Vite 6, Tailwind CSS 3 |
| UI Components | Custom shadcn/ui-style components, FullCalendar 6, Lucide icons, Sonner toasts |
| Fonts | Sora (headings), DM Sans (body) via Google Fonts |
| State | TanStack Query v5, React Router v6 |
| Backend | FastAPI, Python 3.12, Pydantic v2 |
| Database | PostgreSQL 16, SQLAlchemy 2.0 (async), Alembic (37 migrations) |
| Auth | Argon2id hashing, DB-backed sessions (signed httpOnly cookies), TOTP MFA, CSRF middleware, role-based access control |
| Scheduler | APScheduler (async) for ntfy notification dispatch |
| Deployment | Docker Compose (3 services), Nginx reverse proxy |
Quick Start
Prerequisites
- Docker and Docker Compose
Setup
-
Clone the repository
git clone https://your-gitea-instance/youruser/umbra.git cd umbra -
Configure environment variables
cp .env.example .envEdit
.envand set secure values (see Production Hardening below for generation commands):POSTGRES_USER=umbra POSTGRES_PASSWORD=your-secure-password POSTGRES_DB=umbra DATABASE_URL=postgresql+asyncpg://umbra:your-secure-password@db:5432/umbra SECRET_KEY=your-random-secret-key OPENWEATHERMAP_API_KEY=your-openweathermap-api-keyWeather widget: The dashboard weather widget requires a free OpenWeatherMap API key. Set
OPENWEATHERMAP_API_KEYin.env, then configure your city in Settings. -
Build and run
docker-compose up --build -
Open the app
Navigate to
http://localhostin your browser. On first launch you'll be prompted to create an admin account.
Architecture
+-----------+
| Browser |
+-----+-----+
|
port 80 (HTTP)
|
+-------+-------+
| Nginx |
| (frontend) |
| non-root:8080 |
+---+-------+---+
| |
static | | /api/*
files | | (rate-limited auth)
v v
+---+-------+---+
| FastAPI |
| (backend) |
| non-root |
+-------+-------+
|
+-------+-------+
| PostgreSQL |
| (db) |
| port 5432 |
+---------------+
- Frontend is built as static files and served by
nginxinc/nginx-unprivileged. Nginx also reverse-proxies API requests to the backend with rate limiting on auth endpoints. - Backend runs Alembic migrations on startup as a non-root user (
appuser), then serves the FastAPI application with--no-server-header. - Database uses a named Docker volume (
postgres_data) for persistence. - Backend port 8000 is not exposed externally — only accessible via the internal Docker network.
Security
Hardened by default
- Multi-user data isolation — all resources scoped by
user_idwith per-query filtering; pentest-verified (51+ test cases, 0 exploitable IDOR findings) - Role-based access control —
adminandstandardroles withrequire_admindependency on all admin endpoints - CSRF protection — global
CSRFHeaderMiddlewarerequiresX-Requested-With: XMLHttpRequeston all mutating requests - Input validation —
extra="forbid"on all Pydantic schemas prevents mass-assignment;max_lengthon all string fields;ge=1, le=2147483647on path IDs - Non-root containers — both backend (
appuser:1000) and frontend (nginx-unprivileged) run as non-root - No external backend port — port 8000 is internal-only; all traffic flows through nginx
- Server version suppression —
server_tokens off(nginx) and--no-server-header(uvicorn) - Rate limiting — nginx
limit_req_zone(10 req/min) on/api/auth/login(burst=5),/verify-password(burst=5),/change-password(burst=5),/totp-verify(burst=5),/setup(burst=3) - DB-backed account lockout — 10 failed attempts triggers 30-minute lock per account
- Inactive user blocking — disabled accounts rejected at login (HTTP 403) without session creation, lockout reset, or last_login_at update
- Timing-safe login — dummy Argon2id hash for non-existent users prevents username enumeration
- Password reuse prevention — change-password endpoint rejects same password as old
- Dotfile blocking —
/.env,/.git/config, etc. return 404 (.well-knownpreserved for ACME) - CSP headers — Content-Security-Policy on all responses, scoped for Google Fonts
- CORS — configurable origins with explicit method/header allowlists
- API docs disabled in production — Swagger/ReDoc/OpenAPI only available when
ENVIRONMENT=development - Argon2id password hashing with transparent bcrypt migration on first login
- DB-backed sessions — revocable, with signed itsdangerous httpOnly cookies, 7-day sliding window with 30-day hard ceiling
- Optional TOTP MFA — authenticator app support with backup codes, admin-enforced MFA for new users
Production Hardening
Before deploying to production, generate secure values for your .env:
# Generate a secure SECRET_KEY (64-char hex string)
python3 -c "import secrets; print(secrets.token_hex(32))"
# or: openssl rand -hex 32
# Generate a secure database password
python3 -c "import secrets; print(secrets.token_urlsafe(24))"
# or: openssl rand -base64 24
# Set ENVIRONMENT to disable Swagger/ReDoc and auto-enable secure cookies
ENVIRONMENT=production
Additionally for production:
- Place behind a reverse proxy with TLS termination (e.g., Caddy, Traefik, or nginx with Let's Encrypt)
- Set
ENVIRONMENT=production— this disables API docs and auto-enables HTTPS-only session cookies (COOKIE_SECUREderives fromENVIRONMENT; override withCOOKIE_SECURE=falseif running non-TLS prod behind a proxy) - Set
CORS_ORIGINSto your actual domain (e.g.,https://umbra.example.com) - Consider adding HSTS headers at the TLS-terminating proxy layer
API Overview
All endpoints require authentication (signed session cookie) except auth routes and the health check. Admin endpoints require the admin role.
| Endpoint | Description |
|---|---|
GET /health |
Health check |
/api/auth/* |
Login, logout, setup, register, status, password change, TOTP MFA |
/api/admin/* |
User management, system config, audit logs (admin only) |
/api/todos/* |
Todos CRUD + toggle completion |
/api/events/* |
Calendar events CRUD (incl. recurring) |
/api/event-templates/* |
Event templates CRUD |
/api/calendars/* |
User calendars CRUD + visibility |
/api/reminders/* |
Reminders CRUD + dismiss + snooze + due alerts |
/api/projects/* |
Projects + nested tasks + comments CRUD |
/api/people/* |
People CRUD |
/api/locations/* |
Locations CRUD + search |
/api/settings/* |
App settings + ntfy config |
/api/dashboard |
Dashboard aggregation |
/api/upcoming |
Unified upcoming items feed |
/api/weather/* |
Weather data proxy |
API documentation is available at /api/docs (Swagger UI) when ENVIRONMENT=development.
Development
Rebuild a single service
docker-compose up --build backend # Backend only
docker-compose up --build frontend # Frontend only
View logs
docker-compose logs -f # All services
docker-compose logs -f backend # Backend only
Reset database
docker-compose down -v && docker-compose up --build
Stop all services
docker-compose down
Project Structure
umbra/
├── docker-compose.yaml
├── .env / .env.example
├── backend/
│ ├── Dockerfile
│ ├── requirements.txt
│ ├── alembic.ini
│ ├── alembic/versions/ # 37 migrations (001–037)
│ └── app/
│ ├── main.py # FastAPI app, CSRF middleware, router registration, health endpoint
│ ├── config.py # Pydantic BaseSettings (DATABASE_URL, SECRET_KEY, CORS, etc.)
│ ├── database.py # Async SQLAlchemy engine + session factory
│ ├── models/ # 18 SQLAlchemy ORM models (incl. User, UserSession, SystemConfig, AuditLog)
│ ├── schemas/ # 13 Pydantic v2 request/response schema modules (incl. admin)
│ ├── routers/ # 14 API route handlers (incl. auth, admin, totp)
│ ├── services/ # Auth (Argon2id), recurrence, TOTP, ntfy, audit
│ └── jobs/ # APScheduler notification dispatch
└── frontend/
├── Dockerfile
├── nginx.conf
├── proxy-params.conf # Shared proxy settings (DRY include)
├── package.json
└── src/
├── App.tsx # Routes, ProtectedRoute, AdminRoute auth guards
├── lib/ # api.ts (axios + 401 interceptor), date-utils.ts, utils.ts
├── hooks/ # useAuth, useAdmin, useSettings, useTheme, useCalendars, useConfirmAction, useCategoryOrder, useTableVisibility
├── types/ # TypeScript interfaces
└── components/
├── ui/ # 17 base components (Button, Dialog, Sheet, Card, Input, Select, Switch, etc.)
├── shared/ # EntityTable, EntityDetailPanel, CategoryFilterBar, CategoryAutocomplete, CopyableField
├── layout/ # AppLayout, Sidebar, LockOverlay
├── auth/ # LockScreen, AmbientBackground
├── admin/ # AdminPortal, IAMPage, ConfigPage, AdminDashboardPage, CreateUserDialog, UserActionsMenu, UserDetailSection
├── dashboard/ # DashboardPage + 8 widgets
├── calendar/ # CalendarPage, CalendarSidebar, CalendarForm, EventForm, TemplateForm
├── todos/ # TodosPage, TodoList, TodoItem, TodoForm
├── reminders/ # RemindersPage, ReminderList, ReminderItem, ReminderForm, SnoozeDropdown, AlertBanner
├── projects/ # ProjectsPage, ProjectCard, ProjectDetail, ProjectForm, KanbanBoard, TaskRow, TaskForm, TaskDetailPanel
├── people/ # PeoplePage, PersonForm
├── locations/ # LocationsPage, LocationForm
└── settings/ # SettingsPage, NtfySettingsSection, TotpSetupSection
License
This project is for personal use. Feel free to fork and adapt for your own needs.