Friend Onboarding
A self-serve external-user platform behind my own front door.
self-hosted access OAuth identity role-gated provisioning invite lifecycle forward-auth outbound-only home path Flask request workflows
The short version
Friends join a self-hosted media library and, where granted, the local AI assistant through a single invite link. The system verifies identity, auto-provisions access to Plex and Discord, and gates assistant sessions so each person reaches only their own workspace. Nothing in this platform listens for connections at my house. A cloud droplet is the only public-facing surface, and the home side reaches it by opening connections outbound over Tailscale.
It has run in my lab since 2026-07-05, serving my friends and family. Fifteen people use it besides me: five with assistant access (the three nursing and law pilots and two friends on the general assistant), and ten on media only.
What a friend experiences
The onboarding flow runs in six steps:
- Invite-code gate. I mint a single-use, 30-day-expiring code from the admin panel and send the link. The code carries that friend's capabilities: media access by default, assistant access when I choose. If a friend returns later with an unfinished flow, they resume automatically on re-authentication instead of needing a fresh code.
- Sign in. Google OAuth is the primary path. Friends who use Apple or typed-email accounts hit a fallback that accepts their email directly. There is no Sign in with Apple button: it would require a paid developer account and Apple's per-app relay email differs from the friend's Plex address, breaking the matching logic.
- Plex auto-invite. The captured email receives a Plex share through the API. Only the Movies and TV libraries are shared.
- Discord (recommended, skippable). A friend who joins gets an auto-assigned member role from the server bot. Their Discord ID links to their onboarding record for attribution and "your request is ready" notifications. Someone who skips keeps full function through the web request page; they just lose chat pings.
- Request tool import. The request tool is the self-hosted page where friends ask for a film or a show. Signing into it with Plex auto-imports the user with a default permission set and maps their display name to the invite label.
- Tutorial redirect. On completion, the friend reaches the tutorial page. It covers how to request content, how to watch on each device, and where to get help.
What I get as the admin
Before this existed, each new person meant the same hand work in several places: the media share, the request tool, the chat server role, and the assistant's user setup. Now it is one link and one checkbox.
A single per-friend status dashboard tracks four stages: invited, accepted Plex, signed into the request tool, Discord linked. It flags mismatches when a multi-account user picks the wrong identity. Each stage is recorded against the email key, so a closed browser or a failed step leaves the record intact and the friend resumes where they stopped after signing in again. The dashboard shows which stage a friend is stuck on, and an admin view-as mode lets me see the flow the way they see it.
Minting a code is one click. The panel returns a ready-to-send link that auto-fills the join form with the correct code and capability set. Adding assistant access is explicit: I check a capability flag and the provisioning adapter handles the rest. Plex invites, request-tool imports, and Discord role assignments all happen automatically.
The admin panel requires a Google-verified admin email, password login is disabled, and an identity that arrived through the email fallback is never treated as an administrator.
How it reaches the internet
Friends reach the platform through three public DNS records. Traffic follows this path:
| Hop | What happens |
|---|---|
| Public DNS A records | Point to a static public IP on a cloud droplet. No dynamic DNS needed. |
| Droplet reverse proxy | Serves HTTPS via Let's Encrypt, issued and renewed automatically. This is the only public-facing web surface. |
| Tailscale overlay | A private overlay to the home network. It fills the same role as a vendor tunnel service, with the traffic path under my control. |
| Home reverse proxy | Sees only outbound-established traffic. Home infrastructure ports stay closed. |
| Applications | The onboarding app, the request tool, and the assistant orchestrator behind it. |
Media streaming is the exception. It still uses the media server vendor's relay service, outside this path, and I do not route it through the same front door, so the platform's outbound-only claim is about the onboarding, request, and assistant traffic, not about streaming. I chose this architecture so the public edge and the certificates stay under my control and I did not have to move nameservers. Tailscale still relies on its own coordination service, but the tunnel itself is direct and no third party terminates or inspects the traffic.
The trade-off is that there is no managed Web Application Firewall in front of it. What stands in for one is a small attack surface. SSH on the droplet is reachable only over the overlay. The firewall exposes only the HTTPS port to the public and locks the DNS port to known addresses. A default-deny catch-all makes the proxy answer only the names it serves, and the admin interfaces never face the public.
Identity and gating
Email is the primary key, captured at join time. Three authentication providers are recognized: Google, Plex, and typed email. Only Google and Plex count as verified; a typed email is never treated as proof of identity, gets no personalization, and cannot reach admin features. A typed email is enough to receive a media share, because the code is single-use and went to one person and a share can be revoked. It is not enough to reach the assistant or the admin panel, both of which require a verified identity.
The assistant's front door uses a forward-auth gate at the edge. An unauthenticated request is redirected into the onboarding start flow. Only a verified admin or a provisioned friend passes through. The gate sets an authenticated-identity header that tells the orchestrator which user to pin sessions to. The proxy strips any client-supplied copies of this header before the forward-auth check runs, so a user cannot forge another identity.
Per-user assistant provisioning is handled by a data-driven adapter. When a friend receives assistant access, the system cross-references their persona (general, nursing, or law), creates their private namespace, and notifies me via Discord. Existing users were backfilled by a script run first as a dry run, then with a commit flag.
Components and current numbers
The onboarding application is one small Flask container deployed in the same Compose stack as the media apps. It follows the lightweight-admin pattern used elsewhere in the lab. The modules:
| Module | Responsibility |
|---|---|
| Web routes, one set per public name | Google OAuth, typed-email fallback, resumable flow completion. |
| Forward-auth endpoint | Identity verification for the assistant front door. |
| Admin mint panel | Code generation with capability checkboxes; ready-to-send link output. |
| Invites | Granular caps, single-use enforcement, 30-day expiry tracking. |
| Email-keyed store | Resumable records keyed to identity. Onboarding resumes automatically after re-authentication. |
| Plex integration | PIN sign-in, owner share of Movies and TV, runtime resolution of library section IDs. |
| Request-tool import | Display name mapped to the invite label; default permission set applied. |
| Assistant-persona module | Cross-reference against general / nursing / law personas, provisioning adapter, Discord notification pings. |
Current usage across the platform:
| Metric | Count |
|---|---|
| Provisioned assistant profiles (all personas) | 5 |
| Friends on media-only access | 10 |
| Public DNS records for the front door | 3 |
The persona system (general / nursing / law) drives which grounding corpus each assistant user reaches. See that page for the two-layer design: persona-shared reference corpora plus per-user writable namespaces.
Current Lab overview · Media stack architecture · Home AI Orchestrator · Persona pilots