Auth
Overview
Lumio implements a multi-type authentication system with five distinct auth types, each serving a different use case. Authentication is handled by the lo-auth crate (token generation, validation, RBAC) and the API layer (OAuth flows, session management, middleware).
Auth Types
| Type | Prefix | Use Case | Rate Limit |
|---|---|---|---|
| System Key | lm_sys_ | Internal service-to-service communication | Unlimited |
| User API Key | lm_usr_ | External API access for users | 1200 req/min |
| JWT | lm_ + eyJ... | Session-based auth after OAuth login | 600 req/min |
| Popout Token | lm_pop_ | Non-expiring overlay/OBS browser source access | 600 req/min |
| Anonymous | (none) | Unauthenticated public access | 120 req/min |
Token type is automatically identified from the prefix via identify_token().
Architecture
Client Request
|
v
Auth Middleware (identify_token -> resolve AuthContext)
|
+-- lm_sys_* --> System Key lookup (config file)
+-- lm_usr_* --> User API Key lookup (DB, hash match)
+-- lm_eyJ* --> JWT validation (decode + verify)
+-- lm_pop_* --> Popout Token lookup (DB, hash match)
+-- (none) --> Anonymous
|
v
AuthContext (enum: System | ApiKey | User | PopoutToken | Anonymous)
|
v
Permission checks (PermissionGuard / require_permission)
|
v
Handler / Resolver
OAuth Login Flow
- User initiates login in the ID App (NextAuth-based).
- ID App handles OAuth with the provider (Twitch, YouTube/Google, Discord, Kick, Trovo).
- ID App calls the
exchangeTokenGraphQL mutation with:- Provider name and provider-specific user ID
- OAuth access token
- User profile (display_name, username, avatar_url, email)
- The API finds or creates the user via
find_or_create_user_by_provider. - A session is created in the database with a SHA-256 hash of the refresh token's
jti. - The session is cached in Redis.
- A JWT (short-lived) and refresh token (long-lived) are returned.
Token Refresh Flow
- Client sends expired JWT's refresh token to the
refreshTokenmutation. - Refresh token is validated and the session is looked up by token hash.
- A new JWT is issued with the same session ID.
- The original refresh token is returned unchanged.
Logout Flow
- Client sends the refresh token to the
logoutmutation. - Session is deleted from PostgreSQL and Redis.
- Returns success even if the token was already expired.
API
GraphQL Queries
| Query | Args | Returns | Guard |
|---|---|---|---|
me | -- | MeResult! | AuthGuard |
myPermissions | -- | [String!]! | AuthGuard |
MeResult includes:
- User profile (
id,displayName,email,avatarUrl,createdAt) activeAccountId(nullable)accounts: [AccountMembership!]!(role, plan, owner status, owner avatar)permissions: [String!]!-- resolved account-scoped permissionsadminPermissions: [String!]!-- resolved admin-scope permissionsloginConnections: [LoginConnection!]!enabledFeatures: [String!]!token: String-- present only whenupdateMeswitched the active account
GraphQL Mutations
| Mutation | Args | Returns | Guard |
|---|---|---|---|
exchangeToken | input: ExchangeTokenInput! | TokenResult! | None (public) |
refreshToken | refreshToken: String! | TokenResult! | None (public) |
logout | refreshToken: String! | LogoutResult! | None (public) |
logoutSession | -- | LogoutResult! | AuthGuard |
disconnectLoginConnection | provider: String! | LogoutResult! | AuthGuard |
updateMe | input: UpdateMeInput! | MeResult! | AuthGuard |
ExchangeTokenInput
input ExchangeTokenInput {
provider: String! # "twitch", "discord", "google"
providerId: String! # Provider-specific user ID
accessToken: String! # OAuth access token from provider
profile: ProviderProfileInput!
}
input ProviderProfileInput {
displayName: String!
username: String
avatarUrl: String
email: String
}
TokenResult
type TokenResult {
token: String! # Lumio JWT (short-lived, lm_ prefix)
refreshToken: String! # Refresh token (long-lived)
expiresAt: String! # JWT expiration (ISO 8601)
isNewUser: Boolean! # First login ever
hasAccount: Boolean! # User has at least one account
}
REST Endpoints
Auth REST endpoints are public (no permission guard) unless noted. They live under /v1/auth.
| Method | Path | Description |
|---|---|---|
POST | /v1/auth/token | Issue a JWT for a dashboard login (ID App -> API handshake). |
POST | /v1/auth/refresh | Exchange a refresh token for a new JWT. |
POST | /v1/auth/logout | Invalidate the current refresh token/session. |
POST | /v1/auth/authorize | OAuth 2.0 authorize handshake for downstream clients. |
POST | /v1/auth/token/exchange | Exchange a provider OAuth token for a Lumio JWT. |
POST | /v1/auth/link | Link an additional provider identity to the authenticated user (JWT required). |
Bodies are application/json with snake_case fields. Response shapes match the GraphQL TokenResult / LogoutResult types.
User / Session Endpoints
These live under /v1/users/me and are covered in detail in Sessions and the Users resource.
| Method | Path | Permission | Description |
|---|---|---|---|
GET | /v1/users/me | Auth | Current user profile + permissions |
PATCH | /v1/users/me | Auth | Update display name / avatar |
GET | /v1/users/me/login-connections | Auth | List provider identities |
GET | /v1/users/me/sessions | Auth | List active sessions |
DELETE | /v1/users/me/sessions/{id} | Auth | Revoke one session (ownership enforced in handler) |
DELETE | /v1/users/me/sessions | Auth | Revoke all other sessions for the current user |
AuthContext
The AuthContext enum is the core of the auth system, resolved by middleware and available in every handler/resolver:
enum AuthContext {
System { name, permissions },
ApiKey { user_id, account_id, label, permissions, rate_tier },
User { user_id, account_id, session_id, global_permissions, account_permissions },
PopoutToken { account_id, user_id, label, permissions },
Anonymous,
}
Permission Resolution
- System -- Checked against the system key's configured permissions.
- API Key -- Checked against the key's assigned permissions.
- User (JWT) --
admin:*in global permissions grants everything. Otherwise, global permissions and account permissions are checked in order. - Popout Token -- Checked against the token's custom permission list.
- Anonymous -- Always denied.
Wildcard support: events:* matches events:read, events:create, etc.
JWT Structure
Claims
| Field | Type | Description |
|---|---|---|
sub | UUID | User ID |
account_id | UUID? | Active account (can be switched) |
session_id | UUID? | References sessions table (JWT auth only) |
iat | i64 | Issued at (Unix timestamp) |
exp | i64 | Expiration (Unix timestamp) |
jti | UUID | Unique token identifier (UUID v7) |
All JWTs are prefixed with lm_ for type identification.
API Keys
System Keys (lm_sys_)
- Generated via
generate_system_api_key() - Configured in TOML config files
- Used by internal services and bots
- 32 random bytes, hex-encoded
- Stored as SHA-256 hash for verification
User API Keys (lm_usr_)
- Generated via
generate_user_api_key() - Created by users in the dashboard
- Bound to a user + account
- Display prefix shows first 2 chars of the random part (e.g.,
lm_usr_ab) - Full key shown once, only hash stored in DB
Popout Tokens (lm_pop_)
- Generated via
generate_popout_token() - Non-expiring, bound to user + account
- Used for OBS browser sources and dashboard popout windows
- Limited, configurable permissions
- 32 random bytes, hex-encoded, SHA-256 hashed for storage
Login Connections
Login connections link OAuth provider identities to Lumio users. Each connection stores:
- Provider name and provider account ID
- Username, display name, avatar URL
- Encrypted OAuth tokens (access_token, refresh_token)
- Scopes and token expiry
Supported providers: Twitch, YouTube (via Google), Discord, Kick, Trovo.
Key Files
| File | Purpose |
|---|---|
crates/lo-auth/src/lib.rs | Public API re-exports |
crates/lo-auth/src/jwt.rs | JWT creation, validation, refresh token generation |
crates/lo-auth/src/context.rs | AuthContext enum with permission checks |
crates/lo-auth/src/api_key.rs | API key generation, hashing, type identification |
crates/lo-auth/src/popout_token.rs | Popout token generation and validation |
crates/lo-auth/src/rbac.rs | Permission constants, default roles, plan limits |
crates/lo-auth/src/error.rs | Auth error types |
apps/api/src/graphql/auth.rs | GraphQL mutations (exchange, refresh, logout) and me query |
apps/api/src/db/auth.rs | User, session, and login connection DB operations |