# 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](https://docs.docker.com/get-docker/) and [Docker Compose](https://docs.docker.com/compose/install/) ### Setup 1. **Clone the repository** ```bash git clone https://your-gitea-instance/youruser/umbra.git cd umbra ``` 2. **Configure environment variables** ```bash cp .env.example .env ``` Edit `.env` and set secure values (see [Production Hardening](#production-hardening) below for generation commands): ```env 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-key ``` > **Weather widget**: The dashboard weather widget requires a free [OpenWeatherMap](https://openweathermap.org/api) API key. Set `OPENWEATHERMAP_API_KEY` in `.env`, then configure your city in Settings. 3. **Build and run** ```bash docker-compose up --build ``` 4. **Open the app** Navigate to `http://localhost` in 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_id` with per-query filtering; pentest-verified (51+ test cases, 0 exploitable IDOR findings) - **Role-based access control** — `admin` and `standard` roles with `require_admin` dependency on all admin endpoints - **CSRF protection** — global `CSRFHeaderMiddleware` requires `X-Requested-With: XMLHttpRequest` on all mutating requests - **Input validation** — `extra="forbid"` on all Pydantic schemas prevents mass-assignment; `max_length` on all string fields; `ge=1, le=2147483647` on 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-known` preserved 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`: ```bash # 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_SECURE` derives from `ENVIRONMENT`; override with `COOKIE_SECURE=false` if running non-TLS prod behind a proxy) - Set `CORS_ORIGINS` to 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 ```bash docker-compose up --build backend # Backend only docker-compose up --build frontend # Frontend only ``` ### View logs ```bash docker-compose logs -f # All services docker-compose logs -f backend # Backend only ``` ### Reset database ```bash docker-compose down -v && docker-compose up --build ``` ### Stop all services ```bash 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.