New file: vip-feature-review-payments-security-ux.md (detailed review + mermaid-like text flows, step-by-step client/admin examples, data model notes, state/gaps). Updated: README.md (links the review + high-level summary of the two UXes, security, integrations post-M1/M1.5 + MCP content work). |
||
|---|---|---|
| .. | ||
| billing.md | ||
| dates.md | ||
| gallery.md | ||
| messages.md | ||
| README.md | ||
| settings.md | ||
| story.md | ||
| vip-feature-review-payments-security-ux.md | ||
quinn.vip — Feature Overview
Domain: quinn.vip (prod), vip.quinn.apricot.lan (dev)
Feature source: codebase/@features/vip/frontend-client/ (Vite + React SPA, PWA)
Backend: codebase/@features/api/ (shared NestJS API, port 3031)
What it is
A private, per-client portal. Each invited client gets a unique token URL (/portal/:token). Behind that URL is an encrypted messaging channel + relationship data — upcoming dates, billing history, shared photos, a personal story, and account settings.
The app is a PWA installable to the home screen. It ships multiple disguise manifests so the icon can appear as Calculator, Notes, Weather, or Fitness on a client's device.
Auth flow
/:token
└─ getAuthStatus(token)
├─ hasPassword: false → SetPasswordScreen (first visit)
└─ hasPassword: true → LoginScreen
├─ hasWebAuthn: true → auto-trigger WebAuthn
└─ password form → verifyPassword(token, pw)
└─ onUnlocked → sessionStorage.setItem('vip_auth_ok', '1')
Session flag vip_auth_ok in sessionStorage skips the auth check on tab re-focus within the same session. Closing the browser tab clears it.
Storage keys
| Key | Storage | Purpose |
|---|---|---|
vip_token |
localStorage |
Persists the token across sessions |
vip_auth_ok |
sessionStorage |
Auth bypass within a single browser session |
vip_content_key |
sessionStorage |
AES-GCM content decryption key, derived at login |
vip_welcomed_<token> |
sessionStorage |
Prevents welcome splash from re-showing |
Encryption
Messages and sensitive content are encrypted server-side per-client. The content key is derived from the client's password via PBKDF2 (or Argon2 depending on settings). On login, verifyPassword returns the raw contentKey which is stashed in sessionStorage for the tab's lifetime.
The Settings tab exposes the encryption algorithm, KDF, iteration count, and key creation date.
Disguise mode (PWA)
public/ contains multiple manifest-*.json files. At install time, the user selects a disguise:
| Disguise | Manifest | Icon |
|---|---|---|
| Default | manifest-default.json |
Crown/star |
| Calculator | manifest-calculator.json |
Calculator |
| Notes | manifest-notes.json |
Notes |
| Weather | manifest-weather.json |
Weather |
| Fitness | manifest-fitness.json |
Fitness |
DisguisePicker.tsx handles selection. The service worker (sw.js) caches the correct manifest.
API base
| Env | Value | How |
|---|---|---|
Dev (vip.quinn.apricot.lan) |
'' (empty) |
Relative URLs → Vite proxy → localhost:3030 |
Prod (vip.transquinnftw.com) |
https://my.transquinnftw.com/api/v2 |
Set at build time in deploy.sh |
The prod path works because my.transquinnftw.com/api/v2/ nginx block proxies to :3030/.
Important: every API sub-path (/invites, /relationship, /messages, /push, /referrals, /billing, /auth, /settings) must be listed in the Vite proxy apiPaths array in vite.config.ts. A missing entry causes the Vite dev server to serve the SPA HTML instead of forwarding to the API. The dev server must be restarted after any change to vite.config.ts.
Header
The portal header renders "Quinn's {Tier} VIP Area" where tier is the reputation tier Quinn assigned (Bronze / Silver / Gold / Diamond). Falls back to "Quinn's VIP Area" when no tier is set. Tier is fetched from getRelationship() on unlock.
Tabs
| Tab | Internal id | Doc |
|---|---|---|
| Messages | messages |
messages.md |
| Dates | reservations |
dates.md |
| Billing | billing |
billing.md |
| Story | story |
story.md |
| Gallery | gallery |
gallery.md |
| Settings | settings |
settings.md |
Key files
| Path | Purpose |
|---|---|
frontend-client/src/pages/VipPortalPage.tsx |
Main component — all tabs, auth states, nav |
frontend-client/src/api.ts |
All API calls + TypeScript types |
frontend-client/src/push.ts |
Web Push subscription registration |
frontend-client/src/hooks/useMessageStream.ts |
SSE message streaming hook |
frontend-client/src/components/DisguisePicker.tsx |
PWA disguise selection UI |
frontend-client/src/disguises.ts |
Disguise config constants |
frontend-client/vite.config.ts |
Build config, PWA plugin |
Payments, Unlocks, Wallet, Security & Integrations (M1/M1.5 Review)
See the full review + illustrations at vip-feature-review-payments-security-ux.md.
Summary of the two experiences (post recent payments + wallet + MCP work):
- Client (VIP member/fan): Private PWA portal behind per-client token + X-VIP-Token header. Password + optional WebAuthn auth. Dual-key per-client encryption (contentKey returned on login for client-side decrypt of messages/gallery). Tabs for encrypted messages, dates/reservations, billing history (now includes unlocks/top-ups), story, private gallery, settings, quotes. New (backend complete): Prepaid wallet balance (top-up via enabled VIP payment methods with memo → admin confirm → credit + billing revenue booked at top-up). Instant spend from balance for content unlocks (
balance-purchasefor targetType clip/library_tier etc. with targetRef to drops/gallery) or tips. List onlyvip_unlock_enabledmethods. (Client UI wiring for unlock sheet/balance is the pending M1 frontend.) - Provider/Admin/Agent: quinn.admin + MCP (
quinn-admin-mcp). Manage payment methods (togglevip_unlock_enabledindependent of public visibility;exposes_legal_nameadvisory). Confirm payments (write paid billing, credit wallet on top-ups; exactly-once via transitioned gate). Full lifecycle for VIP clients/tokens/invites/quotes/priority/reservations/gifts/etc. Content creation: Upload gallery photos + create/publish content_drops (with buy_links for platforms + published_at for retro "posted on X on date Y"). New MCP tools exactly for "upload content and make posts" (these feed VIP unlock targets). Reliable background emails for contact + VIP payments + activity.
Security highlights (see review for flows):
- Token in URL + strict X-VIP-Token header match on every call.
- argon2id password + WebAuthn (platform, userVerification required).
- Per-token content encryption (AES-GCM; dual: pw-derived key + service key from SERVICE_TOKEN+clientId). Client gets contentKey only on successful auth for decrypt.
- ServiceTokenAuth for admin ops (confirm payments, resets).
- Wallet atomic guard:
UPDATE ... SET balance = balance - X WHERE ... AND balance >= X(no negatives). - Unlock exactly-once via
transitionedflag. - Media protection pipeline on gallery/content-drops.
- Best-effort side effects (emails) decoupled with timeout so they never block UX.
Integrations (key ones):
- Content-drops + gallery-item: Public shoppables with external buy links + retro dates/platforms. VIP unlocks target the same via targetRef (direct paid access, 100% to Quinn via wallet or other enabled methods).
- vip-billing: Unified paid ledger (visible in client BillingTab + admin). Top-ups book revenue at funding time; balance spends are pure wallet ledger (no double revenue).
- My/clients/relationship + processors: VIP clients linked; full CRM, stats, classifiers apply.
- Messages/vip-conversation + gallery attachments: Encrypted per-client using the contentKey.
- MCP/agents: New content_drop + upload tools + updated payment methods tools (for VIP flags).
- Public site: Parallel marketing (external links) vs VIP direct paid.
- Protection/analytics/edge: Shared media protection, markers, edge for public reads.
- Emails: Now reliable (background withTimeout + logger) for contact submissions, payment confirms (incl. wallet top-ups), priority requests, quote responses.
State: Backend payments/wallet/content MCP complete and on main (with tests, reliable notifs, memory tracking). Portal UI for unlock/balance sheet + M3 grant pending. See the full review doc for diagrams, journeys, and gaps.
Related Docs in this Folder
- billing.md — Client billing history.
- gallery.md — Private shared photos.
- messages.md — Encrypted comms.
- settings.md — Auth/encryption controls.
- story.md, dates.md — Other portal areas.
- (This review adds the payments/unlocks/security/cross-feature view.)