Overview
The Tokens module manages popout tokens for overlay and popout page access. Popout tokens are non-expiring access tokens designed for use in OBS browser sources and other embedded contexts where cookie-based authentication is not practical. Each token has configurable permissions, an optional user assignment, and a label for identification. The full token is only shown once at creation time; afterward, only a prefix is stored for identification. Tokens are hashed before storage for security.
Architecture
Backend
- GraphQL (
apps/api/src/graphql/tokens.rs) -- Queries for listing tokens. Mutations for creating, updating, and deleting tokens.
- Token Generation (
crates/lo-auth/src/popout_token.rs) -- generate_popout_token() produces a cryptographically random token, its hash (for storage), and a prefix (for display).
- Authentication -- Popout tokens are one of the 5 auth types in Lumio. When a request includes a
token query parameter, the auth middleware hashes it and looks it up in the database. The token's permissions are used for authorization.
Frontend
- Token management page listing all tokens with their labels, permissions, and prefixes.
- Token creation form with permission selector and optional user/label fields.
- Copy-to-clipboard for the full token at creation (shown only once).
- Overlay popout URLs use format:
/overlay/[key]?token=xxx.
API
GraphQL Queries
| Query | Permission | Description |
|---|
popoutTokens | tokens:read | List all popout tokens for the account. Shows id, account_id, user_id, token_prefix, label, permissions, created_at. Never exposes the token hash. |
GraphQL Mutations
| Mutation | Permission | Description |
|---|
createPopoutToken(input: CreatePopoutTokenInput) | tokens:create | Create a new popout token. Returns the full token string (shown only once) and the token metadata. If no user_id is provided, binds to the authenticated user. |
updatePopoutToken(input: UpdatePopoutTokenInput) | tokens:edit | Update a token's label, permissions, or user binding. Uses double-option semantics: absent = don't change, null = clear, value = set. |
deletePopoutToken(id: UUID) | tokens:delete | Delete/revoke a popout token. Verifies account ownership. |
REST Endpoints
All paths live under /v1/tokens. Bodies are snake_case and mirror the GraphQL inputs. The full token string is returned only from POST /v1/tokens and cannot be retrieved later.
| Method | Path | Permission | Description |
|---|
GET | /v1/tokens | tokens:read | List popout tokens for the account (hash never exposed) |
POST | /v1/tokens | tokens:create | Create a popout token; returns full token once |
PATCH | /v1/tokens/{id} | tokens:edit | Update label, permissions, or user binding |
DELETE | /v1/tokens/{id} | tokens:delete | Revoke a popout token |
GET | /v1/tokens/me | Auth | Return the permissions granted to the current token (self-introspection) |
CreatePopoutTokenInput:
| Field | Type | Description |
|---|
label | String? | Human-readable label for the token |
permissions | [String] | List of permission strings the token grants |
userId | UUID? | User to bind the token to (defaults to authenticated user) |
UpdatePopoutTokenInput:
| Field | Type | Description |
|---|
id | UUID | Token ID to update |
label | Option<Option<String>> | Double-option: absent = no change, null = clear, value = set |
permissions | Option<[String]> | Replace permissions list (absent = no change) |
userId | Option<Option<UUID>> | Double-option: absent = no change, null = unassign, value = assign |
Permissions
| Permission | Description |
|---|
tokens:read | View popout tokens (prefix, label, permissions) |
tokens:create | Create new popout tokens |
tokens:edit | Update token label, permissions, user binding |
tokens:delete | Delete/revoke popout tokens |
Database
| Table | Database | Description |
|---|
popout_tokens | PostgreSQL | id, account_id, user_id (nullable), token_hash (bcrypt/sha256 hash), token_prefix (first N chars for display), label, permissions (text array), created_at |
Security
- The full token is generated using
generate_popout_token() from lo-auth.
- Only the hash is stored in the database; the full token cannot be recovered.
- The full token is returned exactly once at creation time.
- The
token_prefix (first few characters) is stored for identification in the UI.
- Tokens are non-expiring by design (for OBS browser sources that run unattended).
Data Flow
- User creates a popout token, selecting which permissions it should grant.
generate_popout_token() creates a random token, computes its hash, and extracts a prefix.
- The hash, prefix, permissions, and metadata are stored in
popout_tokens.
- The full token is returned to the user (shown once, must be copied).
- User adds the token to an OBS browser source URL:
/overlay/[key]?token=xxx.
- When the overlay loads, the auth middleware:
- Extracts the
token query parameter.
- Hashes it and looks up the hash in
popout_tokens.
- If found, authenticates the request with the token's account_id, user_id, and permissions.
Key Files
| Path | Description |
|---|
apps/api/src/graphql/tokens.rs | GraphQL queries and mutations |
apps/api/src/db/tokens.rs | Database operations for token CRUD |
crates/lo-auth/src/popout_token.rs | Token generation (random token, hash, prefix) |
crates/lo-auth/src/ | Popout token authentication type |