Connect nixamp with OAuth 2.1, and put a watch party on it as a room - #217
Merged
Merged
Conversation
A watch party on bittorrented.com becomes a room on nixamp.com, joinable from every nixamp surface -- the web app, a terminal, the desktop app, a television and an agent over MCP -- while the film stays here, on the site that has the torrent and the rights. nixamp is the authorization server and we are the client. It already keeps accounts, revocable tokens and public handles; we keep none of those, so the direction was not really a choice. The flow is OAuth 2.1, which means PKCE on every request (src/lib/nixamp/oauth.ts is the first PKCE in this repo), a redirect URI matched byte for byte, and refresh tokens that ROTATE. That last one shapes src/lib/nixamp/accounts.ts more than anything else. Refreshing is a write, not a read: nixamp retires the token just used, and presenting a retired one withdraws the whole family. So the new pair is stored BEFORE the access token is handed to a caller, and a refusal deletes the row rather than being retried -- retrying is what turns a recoverable state into a connection that has silently died. The bridge (src/lib/nixamp/rooms.ts) puts the room under the HOST's nixamp account, not a service credential: the room is a person's over there, appears in their events, and is theirs to end. A service account would make every party on nixamp belong to "bittorrented", which is nobody. POST /api/watch-party now takes the signed-in user as the host rather than a guest string when there is one. A party still needs no account -- an anonymous host gets a guest id exactly as before -- but putting one on nixamp has to be provably the host's doing, and `guest_1757...` proves nothing. Also here: two tables (bt_nixamp_accounts, bt_watch_party_rooms) following the bt_youtube_accounts shape; a Connections tab in settings with connect and disconnect; an "Also on nixamp" panel in the party room that pushes the host's position every 15s so somebody joining from a terminal an hour in lands an hour in; and /api/v1/nixamp added to the proxy's profile-exempt paths, or a signed-in user with no profile cookie gets bounced to /select-profile in the middle of the consent round trip and loses the code, state and verifier. The handle is what is shown anywhere a person is named. On nixamp the account email is the OAuth linking key and is a credential; the handle is the name that is safe in a room full of strangers. 2871 tests pass, typecheck clean, lint clean for the new files, build green. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QuUkTrofSjQ15j79mRuy4f
ThreatCrush Security Scan99 finding(s) HIGH/CRITICAL: 11 | MEDIUM: 29 | LOW: 59
…and 49 more. Full results in the Security tab. Snippets are redacted; ThreatCrush never prints matched credential material. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A watch party on bittorrented.com becomes a room on nixamp.com, joinable from every nixamp surface -- the web app, a terminal, the desktop app, a television, an agent over MCP -- while the film stays here, on the site that has the torrent and the rights.
Pairs with profullstack/nixamp#127, which makes nixamp the OAuth 2.1 authorization server. This is the client half.
The direction, and why
nixamp keeps accounts, revocable tokens and public handles; we keep none of those. So nixamp is the AS, we are the client, and
src/lib/nixamp/is a third-party-OAuth integration in the shapesrc/lib/youtube/already uses -- plain fetch, a per-modulegetNixampOAuthConfig(), env read inside a function so Next cannot inline a secret at build time.The new part is PKCE, which nothing else in this repo does and which OAuth 2.1 requires of every client. The verifier and the CSRF state ride out together in one httpOnly cookie and never reach page JavaScript.
The thing most likely to bite whoever touches this next
nixamp rotates refresh tokens. Refreshing is a write, not a read: nixamp retires the token just used, and presenting a retired one withdraws the whole family. So in
accounts.ts:NixampConnectionLostso the UI can say "connect it again" instead of "something went wrong".The bridge
src/lib/nixamp/rooms.tsputs the room under the host's own nixamp account, not a service credential. The room is a person's over there: it appears in their events and is theirs to end. A service account would make every party on nixamp belong to "bittorrented", which is nobody.POST /api/watch-partynow takes the signed-in user as the host when there is one. A party still needs no account -- an anonymous host gets a guest id exactly as before -- but putting one on nixamp has to be provably the host's doing, andguest_1757...proves nothing. The bridge endpoint checks twice: signed-in user, and host of the party as the store has it.GET /api/watch-party/nixamp?code=is public, because a member handed a code needs the room link and that link is not the secret -- the room's own visibility on nixamp decides who may be in it.Also here
bt_youtube_accountsshape:bt_nixamp_accountsandbt_watch_party_rooms, with RLS and theupdated_attrigger./settings?tab=connections), the first management UI for any connected account in this repo./api/v1/nixampadded toPROFILE_EXEMPT_PATHSinsrc/proxy.ts. Without it a signed-in user with no profile cookie is bounced to/select-profilemid-consent and loses the code, the state and the verifier..env.exampledocuments the fourNIXAMP_*vars. In production none of them need setting: nixamp ships knowingbittorrentedand its redirect URIs.The handle is what is shown anywhere a person is named. On nixamp the account email is the OAuth linking key and is a credential; the handle is the name that is safe in a room full of strangers.
Testing
pnpm test: 2871 pass, 5 skipped, 0 fail (28 new).pnpm typecheckclean.pnpm linthas no warnings from any new file.pnpm buildgreen, with all four new routes registering.Known gaps
Mapbeside the route, so a bridged party survives a deploy on the nixamp side and not on ours.bt_watch_party_roomsis durable, which means the room link outlives the party -- worth persisting the party itself next, per the schema already sketched inplans/bittorrented-expansion-plan.md.🤖 Generated with Claude Code
https://claude.ai/code/session_01QuUkTrofSjQ15j79mRuy4f