v9 P3: Account linking (OAuth) #24

Open
opened 2026-06-22 23:54:54 +00:00 by rbrooks · 0 comments
Owner

Let a researcher sign in with their real Bluesky/Mastodon account so watches run with their session: higher rate limits, and visibility into content their account can already see (e.g. follows-only posts) but the public can't. Linking is additive - watches work without it (established in P1).

Schema:

  • linked_accounts table: id uuid PK, user_id uuid NOT NULL REFERENCES users ON DELETE CASCADE, platform text NOT NULL CHECK (platform IN ('bluesky','mastodon')), instance_url text (Mastodon home instance), handle text, access_token text NOT NULL (encrypted at rest via the existing encryptPrivateKey/decryptPrivateKey AP-key pattern), refresh_token text (encrypted), dpop_private_key text (encrypted; Bluesky only), token_expires_at timestamptz, created_at timestamptz NOT NULL DEFAULT now(). UNIQUE (user_id, platform, instance_url).

Mastodon (standard OAuth2 authorization-code flow):

  • Dynamic app registration (POST /api/v1/apps) once per instance the first time any user links an account there; cache in a new mastodon_app_registrations table (instance_url UNIQUE, client_id, client_secret encrypted); reuse for all subsequent logins to that instance. Works the same for public or self-hosted instances.

Bluesky (AT Protocol OAuth - the most novel piece; spike this before committing to a timeline):

  • client_id is a URL to a client-metadata JSON document Tea Leaves must host publicly (a new well-known endpoint, alongside the existing .well-known/tea-leaves.json and WebFinger routes)
  • Flow requires identity resolution (handle -> DID -> PDS -> authorization server) and DPoP-bound tokens; no drop-in fit with the openid-client library used for Authentik - budget for adopting @atproto/oauth-client-node (verify it is still current at implementation time) or hand-rolling DPoP signing

Wiring + security:

  • Once linked, the relevant P1/P2 adapters and stream connections switch from public access to the researcher's session for that platform
  • Disconnecting (Settings) revokes and deletes the stored tokens
  • OAuth tokens are far more sensitive than P1/P2's read-only public access: encrypted at rest, never returned in any API response, refresh handled per-provider rules, revocable from Settings. mastodon_app_registrations.client_secret is encrypted the same way.

Originated in ROADMAP.md (now retired; this tracker is the single source of truth).

Let a researcher sign in with their real Bluesky/Mastodon account so watches run with their session: higher rate limits, and visibility into content their account can already see (e.g. follows-only posts) but the public can't. Linking is additive - watches work without it (established in P1). **Schema:** - [ ] `linked_accounts` table: `id uuid PK`, `user_id uuid NOT NULL REFERENCES users ON DELETE CASCADE`, `platform text NOT NULL CHECK (platform IN ('bluesky','mastodon'))`, `instance_url text` (Mastodon home instance), `handle text`, `access_token text NOT NULL` (encrypted at rest via the existing `encryptPrivateKey`/`decryptPrivateKey` AP-key pattern), `refresh_token text` (encrypted), `dpop_private_key text` (encrypted; Bluesky only), `token_expires_at timestamptz`, `created_at timestamptz NOT NULL DEFAULT now()`. `UNIQUE (user_id, platform, instance_url)`. **Mastodon (standard OAuth2 authorization-code flow):** - [ ] Dynamic app registration (`POST /api/v1/apps`) once per instance the first time any user links an account there; cache in a new `mastodon_app_registrations` table (`instance_url UNIQUE`, `client_id`, `client_secret` encrypted); reuse for all subsequent logins to that instance. Works the same for public or self-hosted instances. **Bluesky (AT Protocol OAuth - the most novel piece; spike this before committing to a timeline):** - [ ] `client_id` is a URL to a client-metadata JSON document Tea Leaves must host publicly (a new well-known endpoint, alongside the existing `.well-known/tea-leaves.json` and WebFinger routes) - [ ] Flow requires identity resolution (handle -> DID -> PDS -> authorization server) and DPoP-bound tokens; no drop-in fit with the `openid-client` library used for Authentik - budget for adopting `@atproto/oauth-client-node` (verify it is still current at implementation time) or hand-rolling DPoP signing **Wiring + security:** - [ ] Once linked, the relevant P1/P2 adapters and stream connections switch from public access to the researcher's session for that platform - [ ] Disconnecting (Settings) revokes and deletes the stored tokens - [ ] OAuth tokens are far more sensitive than P1/P2's read-only public access: encrypted at rest, never returned in any API response, refresh handled per-provider rules, revocable from Settings. `mastodon_app_registrations.client_secret` is encrypted the same way. --- _Originated in `ROADMAP.md` (now retired; this tracker is the single source of truth)._
Sign in to join this conversation.
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
rbrooks/TeaLeaves#24
No description provided.