Self-hosted multi-platform broadcast newsroom for activist collectives: publishing pipeline with group approval and DST-proof scheduling, TweetDeck-style column wall with hostile-content-safe rendering, live control room with Auto Director, bridged chat governance with polls and quorum, distributed sandboxed transcode workers, encrypted content-addressed archive, monthly analytics. MIT licensed. See README.md and docs/.
51 lines
7.9 KiB
Markdown
51 lines
7.9 KiB
Markdown
# Connecting platform accounts
|
|
|
|
The API resolves authorization behavior through connector methods and manifests. It currently registers Mastodon, PeerTube, and Telegram. Other providers are not offered as connectable until a connector implements the same authorization surface.
|
|
|
|
## Configuration
|
|
|
|
| Environment variable | Meaning |
|
|
| --- | --- |
|
|
| `APP_URL` | Web origin used for the final `/connections?connect=success` or `failure` redirect. Default `http://localhost:3000`; set `http://localhost:3001` when running the repository's usual API-on-3000/web-on-3001 setup. |
|
|
| `API_URL` | Public API base. Default `http://localhost:3000`. |
|
|
| `CONNECTIONS_CALLBACK_URL` | Optional exact callback URI override; default `${API_URL}/connections/callback`. Register this exact URI with configured provider applications. |
|
|
| `CORS_ORIGINS` | Comma-separated permitted browser origins. Include the web origin; cookie credentials are enabled only for these explicit origins. |
|
|
| `REDIS_URL` | Pending authorization store; default `redis://localhost:6379`. Production should use an authenticated `rediss://` URL. |
|
|
| `SUPERPOSTER_KEK` | Production-required 32-byte base64 envelope encryption key. Keep it stable across replicas/restarts and secure its backups. The existing development key fallback is unsuitable for production. |
|
|
| `MASTODON_INSTANCE_URL` | Instance origin to which the configured Mastodon client belongs. |
|
|
| `MASTODON_CLIENT_ID`, `MASTODON_CLIENT_SECRET` | Optional pre-registered Mastodon application credentials, used only for `MASTODON_INSTANCE_URL`. |
|
|
| `PEERTUBE_INSTANCE_URL` | Instance origin to which the configured PeerTube bridge client belongs. |
|
|
| `PEERTUBE_CLIENT_ID`, `PEERTUBE_CLIENT_SECRET` | Optional credentials for that instance. Otherwise the connector discovers the local client through `/api/v1/oauth-clients/local`. |
|
|
| `PEERTUBE_AUTHORIZE_PATH`, `PEERTUBE_TOKEN_PATH` | Required to enable a PeerTube redirect bridge. Both must be paths beginning with `/`, without a second leading slash, on the selected instance origin. |
|
|
|
|
Use HTTPS and the same browser site for the web and API (for example `app.example.org` and `api.example.org`), or proxy the API under the web origin. The callback binding cookie is HttpOnly, SameSite=Lax, and Secure in production. For development, consistently use `localhost` or consistently use `127.0.0.1` for both web and API; mixing the two breaks browser cookie delivery.
|
|
|
|
### Mastodon
|
|
|
|
Without a configured client for the selected instance, the connector registers an application using `POST /api/v1/apps`, with the exact callback URI and `read write:statuses write:media` scopes. This is the supported path for arbitrary self-hosted instances; there is no universal client secret shared by all instances. Reconnect reuses that connection's registered client. Both legs use S256 PKCE. [Mastodon's OAuth documentation](https://docs.joinmastodon.org/methods/oauth/) describes the authorization-code grant and PKCE support (Mastodon 4.3+).
|
|
|
|
### PeerTube limitation and bridge contract
|
|
|
|
Stock PeerTube documents local OAuth client discovery followed by a **password grant**, rather than an authorization-code consent flow. The connect API never accepts account passwords, so the default PeerTube connect button is disabled with an explanation. See the [official PeerTube authentication guide](https://docs.joinpeertube.org/api/rest-getting-started).
|
|
|
|
An instance administrator can enable a compatible OAuth bridge, then configure the two paths above. Its authorize endpoint must preserve `state`, register/validate the redirect URI, support `response_type=code` and S256 PKCE, and redirect back with a one-use code. Its token endpoint must validate `client_id`, `client_secret`, `redirect_uri`, `code`, and `code_verifier`, returning **actual PeerTube API** `access_token` and `refresh_token` values. Identity, refresh, and revoke then use PeerTube's native `/api/v1/users/me`, `/api/v1/users/token`, and `/api/v1/users/revoke-token` endpoints. These settings do not add an authorization-code grant to a stock instance, and an OIDC identity token alone is insufficient. The mock integration test proves this adapter contract; no third-party bridge is included in this change.
|
|
|
|
### Telegram
|
|
|
|
Create a bot in BotFather and enter its bot token. The connector validates it with `getMe`, then stores the token through the same encrypted vault envelope as OAuth credentials. Optionally supply a chat/channel ID (`@channel` or a numeric chat ID); add the bot to the target and grant any necessary posting rights. Identity health confirms token validity, not permission to publish to every chat. Reconnect accepts a replacement bot token for the same bot and rotates the credential row in place. Telegram's Bot API cannot revoke its own bot token: Remove clears local authorization; revoke/rotate the token through BotFather when needed.
|
|
|
|
## API and storage behavior
|
|
|
|
- `GET /connections/providers` returns available connectors, connect support, required instance/target fields, limitations, and real manifest capabilities.
|
|
- `POST /connections/authorize` accepts `{ provider, instanceUrl? }`, validates connection-management permission, returns `{ redirectUrl }`, and sets the callback binding cookie.
|
|
- `GET /connections/callback` is intentionally outside the bearer guard. It requires the random state and matching browser/workspace binding, rechecks the initiating user's current membership/epoch/permission, exchanges the code, validates identity, and redirects with a fixed success/failure result. Provider error messages never reach the redirect or API logs.
|
|
- `POST /connections/telegram/bot-token` accepts `{ token, channelId?, connectionId? }`. `connectionId` rotates the existing bot connection instead of creating another.
|
|
- `POST /connections/:connectionId/reconnect` reauthorizes the existing account and instance. A different provider identity is rejected; failed consent/exchange leaves the previous credential intact.
|
|
- `DELETE /connections/:connectionId` locks the connection, revokes supported provider credentials, and deletes the connection and cascaded credential row. It also removes local schedules/publication records required by existing restrictive foreign keys; published provider posts remain. The UI asks for confirmation with this consequence. If a provider is unavailable, local removal is retained for retry; already-invalid credentials can be removed.
|
|
- `GET /connections` probes provider identity: `healthy`, `needs_credentials`, or `unavailable`, and returns capability names without credentials. Automatic token refreshes are encrypted and persisted with a compare-and-swap fence against concurrent reconnects.
|
|
|
|
Pending authorizations are random 256-bit state keys with a ten-minute Redis TTL. The Redis value is encrypted, including app secrets, PKCE verifier, initiating session context, and redirect configuration. Atomic compare-and-delete permits only one callback across replicas. Credentials use a persistent AES-256-GCM envelope in the existing `credentials.ciphertext` column, with its DEK wrapped by the existing KEK and authenticated tenant/handle binding. No database migration or dependency addition is needed. Audit mutations and credential writes share a tenant transaction; audits contain only the provider identifier/action, never credentials. Bot request URLs and callback URLs are excluded from HTTP tracing, and API structured logs redact token fields and request bodies/query/cookie/authorization data.
|
|
|
|
## Verification
|
|
|
|
`apps/api/src/connections/connections.integration.test.ts` runs real Nest routes, real connector auth/identity/revoke code, a PKCE-checking mock authorization server, TTL-faithful pending storage, and encrypted connection storage through in-process HTTP. It needs no open TCP ports, PostgreSQL, or Redis. Persistence unit tests independently exercise the real SQL repository's parameterization, tenant context, revocation ordering, and ciphertext-only audit writes. Existing live database integration suites still require their documented database environment variables.
|