docs: add EPPP architecture & programme documentation #366
@@ -0,0 +1,42 @@
|
||||
# Security & operations
|
||||
|
||||
## Security baseline (§32)
|
||||
|
||||
Minimum controls: Argon2id passwords; secure HttpOnly cookies; CSRF mitigation; CSP/security headers; login + abuse-endpoint rate limiting; parameterised SQL; JSON Schema input validation; output escaping; safe URL validation; upload limits; no raw plugin HTML by default; extension manifest validation; no arbitrary runtime package installation; secrets outside source control; least-privilege PostgreSQL role where possible.
|
||||
|
||||
v1.1 additions: SSRF-hardened outbound fetch (§29-A.4); image re-encoding + metadata stripping (§29.2); embed provider allowlist enforced in renderer + generated CSP `frame-src` (§29-A.2); click-to-load live embeds (§29-A.3). Raw HTML is not an initial block; any future raw-HTML capability requires a dedicated privileged threat model.
|
||||
|
||||
## Threat boundaries (§63)
|
||||
|
||||
**Trusted:** core code, reviewed built-in/build-time extension code, operator deployment config.
|
||||
**Untrusted:** HTTP input, admin form input, post content, uploaded files, imported bundles, cookies, extension setting values, external URLs, link-check responses, future webhooks. Every untrusted boundary validates before domain use.
|
||||
|
||||
## Authentication & sessions (§26)
|
||||
|
||||
Local administrator; Argon2id hash; opaque DB-backed session (no JWT for browser admin). Browser holds a random opaque token; DB stores only a one-way lookup hash. Cookie `eppp_admin`: Secure (prod), HttpOnly, SameSite=Lax, Path=/. Server sessions enable logout/revocation/forced expiry/password-change invalidation.
|
||||
|
||||
## Visitor preferences (§27)
|
||||
|
||||
Separate from admin auth. Cookie `eppp_pref` (opaque token; only a lookup hash at rest). Identity created lazily only when a preference changes (≥256-bit randomness); no tracking record per anonymous page view. Generic keys: `appearance.theme`, `appearance.density`, `reading.grouping`, `locale.language`.
|
||||
|
||||
## Error model & observability (§31)
|
||||
|
||||
Canonical errors: ValidationError, AuthenticationError, AuthorizationError, NotFoundError, ConflictError, ExtensionError, ExtensionCompatibilityError, ExtensionMigrationError, ThemeResolutionError, StorageError, DatabaseError. Admin API error shape: `{ error: { code, message, requestId } }`. Never expose stack traces, SQL, tokens/cookies, filesystem internals, or secrets. Pino logs carry timestamp, level, request_id, operation, duration_ms, site_id, extension_id, content_id, error_code; redact passwords/hashes/cookies/tokens/db secrets.
|
||||
|
||||
## Docker Compose (§35–§38)
|
||||
|
||||
Reference operator flow: `cp .env.example .env` → set secrets → `docker compose up -d` → browse `http://localhost:8080`. No host Node/pnpm/PostgreSQL/Vite/Argon2 toolchains required. `compose.yml` runs `db` (postgres, health-gated) + `app` (non-root, read-only root fs, tmpfs `/tmp`); optional `worker` via `--profile worker`; optional Caddy edge profile (`compose.edge.yml`) for TLS. Required secrets: `POSTGRES_PASSWORD`, `EPPP_SESSION_SECRET` (production docs must include a copy/paste secret-generation command).
|
||||
|
||||
## Health/readiness (§40)
|
||||
|
||||
`/health/live` answers only whether the process can respond (not gated on PostgreSQL). `/health/ready` verifies PostgreSQL reachable, core migrations current, registry graph valid, required extensions activated. A broken optional extension is marked failed/disabled rather than making unrelated routes unready.
|
||||
|
||||
## Backup, restore, export/import (§41, v1.1)
|
||||
|
||||
A complete backup: PostgreSQL + media volume + deployment config (secrets regenerated) + release identifier/image digest + extension IDs/versions. Logical `pg_dump`/restore is the portable baseline; restore drill runs monthly as an automated scheduled job against the latest real backup (v1.1). Backups are encrypted client-side and stored off-host; reference retention 48 hourly / 30 daily / 12 weekly / 12 monthly (v1.1).
|
||||
|
||||
Portable export contains versioned manifest, site config, content, chosen revisions, page compositions, navigation, extension config, selected media. Import validates format version, core compatibility, extension dependencies, block versions, settings schemas, duplicate IDs/slugs — never silently partially import invalid data.
|
||||
|
||||
## Operational failure philosophy (§65)
|
||||
|
||||
Prefer "degrade optional capability + clear diagnostic" over "entire site unavailable": missing optional theme → fallback; broken Reading → Reading route unavailable, blog remains; corrupt preference → ignore/default; PostgreSQL unavailable → not ready; invalid core migration → not ready.
|
||||
Reference in New Issue
Block a user