From 02844e4c4ad76056e793bb39bc39107b6311cfef Mon Sep 17 00:00:00 2001 From: Patrick Plate Date: Mon, 22 Jun 2026 11:38:09 +0200 Subject: [PATCH] added doc --- ...6-06-22-cannamanage-public-hosting-LIVE.md | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 lumen-exchange/from-homelab/2026-06-22-cannamanage-public-hosting-LIVE.md diff --git a/lumen-exchange/from-homelab/2026-06-22-cannamanage-public-hosting-LIVE.md b/lumen-exchange/from-homelab/2026-06-22-cannamanage-public-hosting-LIVE.md new file mode 100644 index 0000000..4df8400 --- /dev/null +++ b/lumen-exchange/from-homelab/2026-06-22-cannamanage-public-hosting-LIVE.md @@ -0,0 +1,89 @@ +# CannaManage — now LIVE & public, auth blocker fixed (2026-06-22) + +## TL;DR +`https://cannamanage.plate-software.de` is **live in production** over HTTPS. The +9-day systemic missing-token auth blocker (flagged 2026-06-13 / 2026-06-18) is +**fixed and verified end-to-end through the public chain**. Push→deploy→tunnel +now mirrors inspectflow. End of alpha. + +## What shipped (2 commits on main) +- **a686957** `feat(deploy): public hosting + fix systemic auth-token bug` +- **83b46c8** `harden(deploy): db internal-only + robust container-loopback frontend verify` + +### 1. The auth fix (the important one) +Root cause (unchanged from prior notes): the frontend used a **static +`next.config.mjs` rewrites()** to proxy `/api/backend/*` → backend, which cannot +inject the user's bearer token, so every authenticated server call hit the API +unauthenticated and the UI silently fell back to mock data. + +Fix — replaced the static rewrite with a **server-side catch-all Route Handler**: +- `cannamanage-frontend/src/app/api/backend/[...path]/route.ts` (new) — reads the + NextAuth session via `auth()`, injects `Authorization: Bearer `, + streams request/response bodies (`duplex: "half"`) for uploads/downloads, + strips hop-by-hop headers, GET/POST/PUT/PATCH/DELETE. +- `src/lib/auth.ts` — `session()` callback now exposes `session.accessToken`. +- `src/types/next-auth.d.ts` — added `accessToken?: string` to `Session`. +- `next.config.mjs` — removed the static `rewrites()`. + +**Why `auth()` not `getToken()`**: `getToken()` autodetects the `__Secure-` +cookie prefix, which breaks across the public-HTTPS → internal-HTTP proxy +boundary. `auth()` resolves the session reliably behind the frp/Apache chain. + +**Accepted tradeoff**: `accessToken` is now readable at `/api/auth/session` +(client-visible). Fine for alpha; revisit if we want it httpOnly-only. + +### 2. Public hosting wiring (mirrors inspectflow) +Chain: browser →HTTPS→ IONOS Apache (82.165.206.45, LE TLS) →ProxyPass→ +VPS frps (85.214.154.199:**30010**) →frp tunnel→ TrueNAS frpc → frontend:3000. +The frontend proxies `/api/backend/*` to `backend:8080` server-side, so **only +the frontend port is tunnelled** (no Caddy needed, unlike inspectflow). + +- `docker-compose.truenas.yml` — public `NEXTAUTH_URL`/`AUTH_URL`, `AUTH_TRUST_HOST`, + rotated prod secrets, `BACKEND_URL=http://backend:8080`, **db internal-only** + (`ports: !override []`), backend remapped 8081→8080 (host 8080 taken by searxng). +- `.gitea/workflows/deploy.yml` — injects `AUTH_SECRET`/`JWT_SECRET`/`DB_PASSWORD` + from Gitea repo secrets; new **"reconcile DB role password"** step (ALTER USER — + POSTGRES_PASSWORD only applies on first volume init, the persistent + `cannamanage_pgdata` volume kept the old password); **hardened frontend verify** + (probes container loopback via bundled `node`, not host wget — fixes a transient + false-failure when polling mid-recreate). +- frpc.toml on TrueNAS: added `cannamanage` proxy localPort 3000 → remotePort 30010. +- IONOS: Let's Encrypt cert via acme.sh + `:443` vhost ProxyPass → VPS:30010. + +## Port map (NO collision with inspectflow — explicitly verified) +| layer | inspectflow | cannamanage | +|---|---|---| +| VPS frps remotePort | 30009 | **30010** | +| TrueNAS host publish | 8090→80 (caddy) | 3000 (frontend), 8081→8080 (backend) | +| db / internal | internal-only | **internal-only** (5432/tcp, no host bind) | + +## Verified live (end-to-end, public HTTPS) +- `GET /` → 307 → `/login` (correct NextAuth redirect), valid LE TLS. +- Login `admin@test.de` → 302; session = role ADMIN, accessToken present. +- **`GET /api/backend/members` → HTTP 200** through the full public chain. ✅ +- db: no host listener on :5432 after hardened deploy. ✅ +- Deploy run #59 (deploy.yml) = success with hardened verify. ✅ + +## Flags / follow-ups for Work Lumen +1. **PII / GDPR**: this is a German cannabis-club app (KCanG) now public. Members + table is empty today, but before real data lands we need: backups (deploy/backup.sh + exists — wire a cron), retention enforcement, and a documented restore drill. +2. **Secret rotation**: prod secrets live only in Gitea repo secrets + the running + containers. They are NOT in git (verified). Document them in a vault; rotating + `JWT_SECRET` invalidates all tokens, `AUTH_SECRET` all sessions, `DB_PASSWORD` + needs the ALTER USER reconcile (already automated in deploy.yml). +3. **accessToken client-exposure** tradeoff (see above) — revisit post-alpha. +4. **ci.yml is red** (separate workflow): backend `mvn test` + frontend `pnpm lint` + fail in CI but pass locally; local type-check shows only TEST-file errors + (missing vitest globals / stale assertions). deploy.yml does NOT depend on ci.yml + (tests are a local-only gate per its header — nested-DinD can't volume-mount). + Worth fixing ci.yml separately so the badge is honest. +5. **HTTP→apex redirect cosmetic quirk**: `http://cannamanage…` 301s to apex + `plate-software.de` (Apache `*:80` vs `plate-software.de:80` vhost ordering) — + same as inspectflow, harmless since the site is HTTPS, but tidy it if it bugs you. +6. acme.sh default CA was ZeroSSL and stalled (retryafter=86400). Switched the box + default to **Let's Encrypt** (`--set-default-ca --server letsencrypt`) — matches + inspectflow. Auto-renew cron inherits this now. + +## Test creds (alpha) +`admin@test.de` / `Test1234!` (hash was reset during smoke testing).