Skip to main content

Channel Connections & OAuth Scopes

Channel connections are the mechanism by which Lumio accesses platform APIs on behalf of a user's channel. Each connection stores encrypted OAuth tokens and the scopes that were granted during authorization.

One Connection Per Platform

Each account can have one channel connection per platform. This is enforced by a UNIQUE (account_id, platform) constraint on the channel_connections table.

Supported platforms: Twitch, YouTube, Kick, Trovo, Spotify.

OAuth Scopes by Platform

Scopes are defined statically in apps/api/src/platforms.rs (get_oauth_config()). They are requested during the authorization flow and cannot be changed after the connection is established. To change scopes, the user must disconnect and reconnect.

Twitch (27 scopes)

ScopePurpose
channel:read:subscriptionsRead subscriber list and events
channel:read:redemptionsRead channel point redemptions
channel:manage:redemptionsManage channel point redemptions
channel:read:hype_trainRead hype train events
channel:read:pollsRead poll data
channel:manage:pollsCreate and manage polls
channel:read:predictionsRead prediction data
channel:manage:predictionsCreate and manage predictions
channel:read:goalsRead channel goals
bits:readRead cheer/bits events
moderator:read:followersRead follower list (used by ProfileService)
moderator:read:suspicious_usersRead suspicious user flags
moderator:manage:suspicious_usersUpdate suspicious user treatment
moderator:manage:banned_usersBan and unban users
channel:botSend chat messages as the channel bot
user:read:chatRead chat messages
channel:read:adsRead ad schedule
channel:manage:raidsStart and cancel raids
channel:moderateModerate chat (delete, timeout)
moderator:read:blocked_termsRead blocked terms list
moderator:read:chat_settingsRead chat settings
moderator:read:unban_requestsRead unban requests
moderator:read:banned_usersRead banned users list
moderator:read:chat_messagesRead chat messages as moderator
moderator:read:warningsRead user warnings
moderator:read:moderatorsRead moderator list
moderator:read:vipsRead VIP list

Extra params: force_verify=true (forces re-authorization even if already granted)

YouTube (2 scopes)

ScopePurpose
youtube.readonlyRead channel, video, and live chat data
youtube.force-sslFull YouTube Data API access (chat, comments)

Extra params: access_type=offline (enables refresh tokens), prompt=consent (forces consent screen)

Kick (6 scopes)

ScopePurpose
user:readRead user profile information
channel:readRead channel information
events:readRead channel events
events:subscribeSubscribe to real-time events
chat:writeSend chat messages
moderation:chat_message:manageDelete chat messages

Trovo (2 scopes)

ScopePurpose
channel_details_selfRead own channel details
channel_subscriptionsRead subscriber data

Spotify (7 scopes)

ScopePurpose
user-read-playback-stateRead current playback state
user-modify-playback-stateControl playback (play, pause, skip)
user-read-currently-playingRead currently playing track
playlist-read-privateRead private playlists
playlist-read-collaborativeRead collaborative playlists
playlist-modify-publicModify public playlists
playlist-modify-privateModify private playlists

OAuth Connection Flow

The complete flow from credentials to active worker:

sequenceDiagram
participant User
participant Dashboard as Dashboard (Next.js)
participant API as API Server (Rust)
participant DB as PostgreSQL
participant Platform as Platform OAuth

Note over User,Dashboard: Step 1: Save App Credentials
User->>Dashboard: Enter client_id + client_secret
Dashboard->>API: saveAppCredentials mutation
API->>API: Encrypt with AES-256-GCM
API->>DB: Upsert into app_credentials
API-->>Dashboard: Return client_id_hint (last 4 chars)

Note over User,Platform: Step 2: Authorize Channel
User->>Dashboard: Click "Connect"
Dashboard->>API: Get authorize URL
API->>API: Build URL with scopes from get_oauth_config()
API-->>Dashboard: Redirect URL
Dashboard->>Platform: Redirect to OAuth authorize
Platform->>User: Show consent screen
User->>Platform: Approve
Platform-->>API: Callback with authorization code

Note over API,DB: Step 3: Token Exchange
API->>API: Decrypt client_id + client_secret from app_credentials
API->>Platform: Exchange code for access_token + refresh_token
Platform-->>API: Token response
API->>Platform: Fetch channel info (name, ID)
Platform-->>API: Channel details
API->>API: Encrypt tokens with AES-256-GCM
API->>DB: Upsert into channel_connections

Note over API: Step 4: Start Worker
API->>API: Start platform worker (EventSub, chat listener, etc.)

Scope Behavior

Static Scopes

Scopes are defined at compile time in get_oauth_config(). When the authorize URL is built, all scopes for the platform are requested. There is no mechanism for requesting partial scopes.

Changing Scopes

If the required scopes change (e.g., after a Lumio update adds new features), users must:

  1. Disconnect the existing channel connection
  2. Reconnect to trigger a new OAuth flow with the updated scope list

The old tokens continue to work for their originally granted scopes, but new features requiring additional scopes will fail until reconnection.

Scope Storage

Granted scopes are stored in the scopes column of channel_connections as a VARCHAR[] array. This records what was actually granted (which may differ from what was requested if the user denied specific scopes on platforms that support partial grants).

Credential Requirements

Both tables are required for a fully functional connection:

TableRequired For
app_credentialsToken refresh, API client headers (client_id in requests)
channel_connectionsAll API calls (access_token as bearer token)

If app_credentials are deleted, the connection's access_token continues to work until it expires, but refresh will fail.

If channel_connections are deleted, no API calls can be made for that platform.

Deleting app_credentials via the API also deletes the associated channel_connections entry.

Key Files

FilePurpose
apps/api/src/platforms.rsget_oauth_config() -- scopes, URLs, extra params per platform
apps/api/src/db/connections.rsCRUD for app_credentials and channel_connections
apps/api/src/graphql/connections.rsGraphQL mutations for save/delete credentials, disconnect
apps/api/src/oauth.rsOAuthTokens -- token refresh logic
apps/api/src/crypto.rsAES-256-GCM encryption/decryption