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/.
7.9 KiB
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 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.
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/providersreturns available connectors, connect support, required instance/target fields, limitations, and real manifest capabilities.POST /connections/authorizeaccepts{ provider, instanceUrl? }, validates connection-management permission, returns{ redirectUrl }, and sets the callback binding cookie.GET /connections/callbackis 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-tokenaccepts{ token, channelId?, connectionId? }.connectionIdrotates the existing bot connection instead of creating another.POST /connections/:connectionId/reconnectreauthorizes the existing account and instance. A different provider identity is rejected; failed consent/exchange leaves the previous credential intact.DELETE /connections/:connectionIdlocks 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 /connectionsprobes provider identity:healthy,needs_credentials, orunavailable, 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.