UMBRA/README.md
Kyle Pope ad102c24ed Apply QA suggestions and update all documentation
Code changes (S-01, S-02, S-05):
- DRY nginx proxy blocks via shared proxy-params.conf include
- Add ENVIRONMENT and CORS_ORIGINS to .env.example
- Remove unused X-Requested-With from CORS allow_headers

Documentation updates:
- README.md: reflect auth upgrade, security hardening, production
  deployment guide with secret generation commands, updated architecture
  diagram, current project structure and feature list
- CLAUDE.md: codify established dev workflow (branch → implement →
  test → QA → merge), update auth/infra/stack sections, add authority
  links for progress.md and ntfy.md
- progress.md: add Phase 11 (auth upgrade) and Phase 12 (pentest
  remediation), update file inventory, fix outstanding items
- ui_refresh.md: update current status line

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 20:36:12 +08:00

250 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# UMBRA
A self-hosted personal life administration app with a dark-themed UI. Manage your todos, calendar events, projects, reminders, contacts, and locations from a single dashboard.
## Features
- **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 (5 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)
## 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 (25 migrations) |
| Auth | Username/password with Argon2id hashing, DB-backed sessions (signed cookies), optional TOTP MFA |
| 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 a username and password.
## 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
- **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, burst=5) on `/api/auth/login`, `/verify-password`, `/change-password`, `/totp-verify`
- **Application-level rate limiting** — in-memory IP-based rate limit (5 attempts / 5 min) + DB-backed account lockout (10 failures = 30-min lock)
- **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 cookies
- **Optional TOTP MFA** — authenticator app support with backup codes
### 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
ENVIRONMENT=production
# Enable secure cookies (requires HTTPS)
COOKIE_SECURE=true
```
Additionally for production:
- Place behind a reverse proxy with TLS termination (e.g., Caddy, Traefik, or nginx with Let's Encrypt)
- Set `COOKIE_SECURE=true` to enforce HTTPS-only session cookies
- Set `ENVIRONMENT=production` to disable API documentation endpoints
- 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.
| Endpoint | Description |
|-----------------------|-------------|
| `GET /health` | Health check |
| `/api/auth/*` | Login, logout, setup, status, password change, TOTP MFA |
| `/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 + password change + 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/ # 25 migrations (001025)
│ └── app/
│ ├── main.py # FastAPI app, router registration, health endpoint
│ ├── config.py # Pydantic BaseSettings (DATABASE_URL, SECRET_KEY, CORS, etc.)
│ ├── database.py # Async SQLAlchemy engine + session factory
│ ├── models/ # 17 SQLAlchemy ORM models
│ ├── schemas/ # Pydantic v2 request/response schemas
│ ├── routers/ # 14 API route handlers
│ ├── services/ # Auth (Argon2id), recurrence, TOTP, ntfy
│ └── jobs/ # APScheduler notification dispatch
└── frontend/
├── Dockerfile
├── nginx.conf
├── proxy-params.conf # Shared proxy settings (DRY include)
├── package.json
└── src/
├── App.tsx # Routes and auth guard
├── lib/ # api.ts, date-utils.ts, utils.ts
├── hooks/ # useAuth, useSettings, useTheme, useCalendars, useConfirmAction, useCategoryOrder, useTableVisibility
├── types/ # TypeScript interfaces
└── components/
├── ui/ # 18 base components (Button, Dialog, Sheet, Card, Input, Select, Switch, etc.)
├── shared/ # EntityTable, EntityDetailPanel, CategoryFilterBar, CategoryAutocomplete, CopyableField
├── layout/ # AppLayout, Sidebar, LockOverlay
├── auth/ # LockScreen, AmbientBackground
├── 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.