Family onboarding site: invite-gated signup, template config, /get/tv #7

Merged
richiexec merged 1 commit from feature/onboarding-site into main 2026-07-22 16:24:50 +00:00
Owner

The distribution site — a family member gets fully set up from a browser: account, addon config, TV login. No PC install, no typing addon URLs.

Pages (pb_public/, served by PocketBase itself at / — same origin, no new service)

  • / landing — create-account CTA, TV sign-in steps, and login-gated Windows/Linux downloads (existing update manifest + file tokens).
  • /onboarding.html wizard — invite code → account → named default profile ("Kdo se bude dívat?" — the app adopts the earliest-created profile) → family template encrypted in the browser under the new user's password and pushed as their addon_config ciphertext. Crypto is byte-compatible with the app's AddonConfigCrypto (PBKDF2-HMAC-SHA256 210k → AES-256-GCM, nonce‖ct‖tag): a wizard-produced blob was decrypted with the app's Dart crypto in a probe test, all fields intact. Adult fields are never in the template (adultAddonUrl: null, adultEnabled: false) — mirrors the #14/#20 stripping.
  • /get/tv — public direct download of the newest clean Android APK (Content-Disposition: amber-tv.apk), so the TV's Downloader app can sideload from a plain URL. Adult builds stay account-gated, unchanged.
  • Final wizard step routes TV login through the existing /device approve flow (#12) — QR or typed code, vault key sealed to the TV.

Backend

  • onboarding_template collection: one admin-maintained record (addonUrl, czechAddonUrl, tmdbKey, note), all rules null — the invite-gated hook is the only reader; you edit it in the admin UI.
  • AMBER_INVITE_CODE env gates both doors, fail-closed when unset:
    • users create requires it (X-Amber-Invite header or ?invite=; superusers bypass). ⚠️ This also closes the previously-open in-app registration — the app's register screen will refuse until it grows an invite field. Family onboarding happens via the site, so acceptable for now.
    • GET /api/amber/onboarding-template?code=… returns the plaintext template (403 bad code, 429 after 10 bad codes / 5 min / IP, 404 no record, 503 env unset).
  • Dockerfile ships pb_public + --publicDir; compose passes the env var for local runs.

Deploy (docs/onboarding-contract.md has the full checklist)

  1. DNS amber.petruzalekr.cz → server; Domains field → https://pb.petruzalekr.cz,https://amber.petruzalekr.cz (the site is host-agnostic).
  2. Env AMBER_INVITE_CODE=<code>, redeploy.
  3. Admin UI → onboarding_template → fill the record.
  4. Publish 1.0.0 releases so /get/tv + downloads serve.

Verification (local, pocketbase 0.39.6 against the repo dirs)

Signup: refused w/o + w/ wrong code (Czech error), accepted via header and query · template route: 200/403/429/404 all hit · full wizard run in a real browser → user + profile + blob created; blob decrypted with the app's Dart AddonConfigCrypto · /get/tv streamed a published dummy APK byte-identical · landing download login handled the no-releases state. Also fixed en route: e.request.url.query() throws on record-request events — requestInfo().query is the correct accessor.

🤖 Generated with Claude Code

The distribution site — a family member gets fully set up **from a browser**: account, addon config, TV login. No PC install, no typing addon URLs. ## Pages (pb_public/, served by PocketBase itself at `/` — same origin, no new service) - **`/` landing** — create-account CTA, TV sign-in steps, and login-gated Windows/Linux downloads (existing update manifest + file tokens). - **`/onboarding.html` wizard** — invite code → account → named default profile ("Kdo se bude dívat?" — the app adopts the earliest-created profile) → **family template encrypted in the browser** under the new user's password and pushed as their `addon_config` ciphertext. Crypto is byte-compatible with the app's `AddonConfigCrypto` (PBKDF2-HMAC-SHA256 210k → AES-256-GCM, `nonce‖ct‖tag`): **a wizard-produced blob was decrypted with the app's Dart crypto in a probe test, all fields intact.** Adult fields are never in the template (`adultAddonUrl: null`, `adultEnabled: false`) — mirrors the #14/#20 stripping. - **`/get/tv`** — public direct download of the newest **clean** Android APK (`Content-Disposition: amber-tv.apk`), so the TV's Downloader app can sideload from a plain URL. Adult builds stay account-gated, unchanged. - Final wizard step routes TV login through the existing `/device` approve flow (#12) — QR or typed code, vault key sealed to the TV. ## Backend - **`onboarding_template`** collection: one admin-maintained record (`addonUrl`, `czechAddonUrl`, `tmdbKey`, `note`), all rules null — the invite-gated hook is the only reader; you edit it in the admin UI. - **`AMBER_INVITE_CODE`** env gates **both** doors, fail-closed when unset: - `users` create requires it (`X-Amber-Invite` header or `?invite=`; superusers bypass). ⚠️ This also closes the previously-open **in-app** registration — the app's register screen will refuse until it grows an invite field. Family onboarding happens via the site, so acceptable for now. - `GET /api/amber/onboarding-template?code=…` returns the plaintext template (403 bad code, **429 after 10 bad codes / 5 min / IP**, 404 no record, 503 env unset). - Dockerfile ships `pb_public` + `--publicDir`; compose passes the env var for local runs. ## Deploy (docs/onboarding-contract.md has the full checklist) 1. DNS `amber.petruzalekr.cz` → server; Domains field → `https://pb.petruzalekr.cz,https://amber.petruzalekr.cz` (the site is host-agnostic). 2. Env `AMBER_INVITE_CODE=<code>`, redeploy. 3. Admin UI → `onboarding_template` → fill the record. 4. Publish 1.0.0 releases so `/get/tv` + downloads serve. ## Verification (local, pocketbase 0.39.6 against the repo dirs) Signup: refused w/o + w/ wrong code (Czech error), accepted via header and query · template route: 200/403/429/404 all hit · **full wizard run in a real browser** → user + profile + blob created; blob **decrypted with the app's Dart `AddonConfigCrypto`** · `/get/tv` streamed a published dummy APK byte-identical · landing download login handled the no-releases state. Also fixed en route: `e.request.url.query()` throws on record-request events — `requestInfo().query` is the correct accessor. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
richiexec added 1 commit 2026-07-22 16:23:31 +00:00
Distribution site served by PocketBase itself (pb_public/, same origin):
- Landing (/): create-account CTA, TV sign-in steps, login-gated PC
  downloads via the existing update manifest + file tokens
- Wizard (/onboarding.html): invite code -> account -> named default
  profile -> family template fetched and encrypted IN THE BROWSER under
  the new user's password (PBKDF2-210k + AES-GCM, byte-compatible with
  the app's AddonConfigCrypto - a wizard blob decrypts in Dart, verified)
  -> pushed as their addon_config ciphertext. Adult fields never included.
- /get/tv: public direct download of the latest clean Android APK
  (Downloader-friendly; adult builds stay account-gated)

Backend:
- onboarding_template collection (admin-only, maintained via admin UI)
- AMBER_INVITE_CODE env gates BOTH users creation (X-Amber-Invite header
  or ?invite=) and the template route (per-IP rate limit 10/5min);
  fail-closed when unset. Note: this closes the previously-open in-app
  registration too.
- Dockerfile ships pb_public + --publicDir; compose passes the env var

Contract + Coolify steps (second domain amber.petruzalekr.cz) in
docs/onboarding-contract.md. Verified locally end-to-end against
pocketbase 0.39.6 (real-browser wizard run + Dart decrypt interop).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
richiexec merged commit 0e33a83d90 into main 2026-07-22 16:24:50 +00:00
richiexec deleted branch feature/onboarding-site 2026-07-22 16:24:50 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: richiexec/amber-backend#7
No description provided.