mirror of
https://github.com/mudler/LocalAI.git
synced 2026-05-29 11:07:18 -04:00
* feat(usage): add Source, APIKeyID, APIKeyName columns to UsageRecord Adds three additive columns plus UsageSource* constants. The columns are auto-migrated by InitDB. APIKeyID is a nullable foreign reference to UserAPIKey.ID; APIKeyName is snapshotted on each row so revoked keys keep showing their name in history. Refs: #9862 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * feat(usage): backfill Source on pre-feature usage rows InitDB now classifies any pre-existing usage_record with an empty source: 'legacy-api-key' user -> legacy, everything else -> web. The backfill is idempotent (only touches NULL/empty rows). Refs: #9862 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * feat(usage): add GetUserUsageBySource aggregator Groups by (bucket, source, api_key_id, api_key_name). Filters out legacy by default. Returns both per-bucket detail and roll-ups (by_source, by_key sorted desc and capped at 200, grand_total). The MAX(created_at) projection is iterated via Rows().Scan into a string column and parsed manually because the SQLite driver surfaces the aggregated timestamp as a string, which database/sql refuses to scan directly into time.Time. Postgres returns a real timestamp; the same string path handles its RFC3339 form too. Refs: #9862 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * fix(usage): log Rows() errors and assert LastUsed in tests Adds rows.Err() and Rows() open-failure logging in computeSourceTotals so silent data drops surface in logs. Logs on parseLastUsedString format misses for the same reason. Strengthens the snapshot-survival test to assert LastUsed is a recent timestamp, locking the SQLite time-string parser behaviour. Refs: #9862 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * feat(usage): add admin GetAllUsageBySource with filters and truncation Optional user_id and api_key_id filters (composed with AND). Legacy bucket is included for admin callers. truncated=true when more than 200 distinct keys would be in the by_key roll-up. Refs: #9862 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * feat(auth): plumb auth_source and auth_apikey through Echo context tryAuthenticate now sets auth_source on every successful branch (web for session/Bearer-session, apikey for Bearer-key/x-api-key/ token-cookie, legacy for legacy env key match). For named-key branches it also stores the resolved *UserAPIKey under auth_apikey so downstream middlewares can snapshot id+name without re-validating. Refs: #9862 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * fix(auth): expand tryAuthenticate godoc and cover Bearer-session branch Documents all three context-keys side effects (auth_source, auth_apikey, _auth_session) plus the split of responsibilities with the parent Middleware. Adds a test for the Bearer-as-session-token classification so future regressions there fail loudly. Refs: #9862 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * feat(usage): UsageMiddleware records source + snapshots key name Reads auth_source and auth_apikey from the Echo context (set by auth.Middleware in the previous task). Snapshots UserAPIKey.ID and Name onto each row so revoked keys remain readable in history. Falls back to source=web when no auth_source is set (auth disabled or unrecognised path). Refs: #9862 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * feat(usage): add /api/auth/usage/sources and admin variant Self endpoint filters legacy server-side; admin endpoint includes legacy and accepts user_id + api_key_id filters. Response includes buckets, totals.{by_source, by_key, grand_total}, and a truncated flag set when the per-key roll-up was capped at 200. Refs: #9862 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * docs(routes): mark test mirror handlers as keep-in-sync with production The newTestAuthApp helper duplicates production route handlers inline because it cannot use RegisterAuthRoutes (which requires a *application.Application). Naming the source path on each mirror makes the drift contract explicit for future maintainers. Refs: #9862 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * feat(ui): add usageApi.getMySources/getAdminSources + i18n strings Refs: #9862 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * feat(ui): add Sources tab skeleton with data fetch Adds Usage page tab that fetches /api/auth/usage/sources (or the admin variant). Renders raw totals plus a placeholder key list; real visualisations land in subsequent commits. Restructures the existing tab button block so Models and Sources are visible to non-admins (Users remains admin-only). Refs: #9862 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * feat(ui): source mix ribbon + searchable/sortable sources table Replaces the SourcesTab placeholder rendering with two reusable components: SourceMixRibbon (one segmented bar per source class) and SourcesTable (search + sort + revoked-key dim). Pulls the current API key list to detect revoked keys. Refs: #9862 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * fix(ui): skip revoked-key detection until the key list is known existingKeyIds defaulted to an empty Set, which made every live api_key row render as (revoked) during the brief window before apiKeysApi.list() resolved, and permanently after a fetch failure. Use null as the unknown state and suppress the revoked badge until the parent provides a real Set. Refs: #9862 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * feat(ui): top-N stacked time chart and drill-in chip for Sources tab Top 7 sources by total tokens get distinct colours; the rest roll up into 'Other'. Clicking a row in the SourcesTable dims everything except that series in the chart; the chip is the canonical clear. Refs: #9862 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * docs(usage): document per-API-key Sources tab and endpoints Extends features/authentication.md Usage Tracking section with: - A 'Sources' tab description and source-class taxonomy - Endpoint documentation for /api/auth/usage/sources and the admin variant - Response shape example with by_source / by_key / grand_total - Migration note about pre-feature row backfill Refs: #9862 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * fix(usage): silence errcheck on deferred rows.Close CI errcheck flagged the bare 'defer rows.Close()' in computeSourceTotals. Wrap in a closure that discards the close error explicitly; an error here is non-actionable since we have already drained the rows and logged any iteration failure. Refs: #9862 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * refactor(usage): bound batcher intake and add Shutdown/FlushNow hooks The pre-existing usage batcher had no cap on its add() path; the usageMaxPending=5000 constant only guarded the re-queue path after a failed write, leaving memory growth unbounded if the DB fell behind. This commit: - Adds the cap to add() so saturation drops new records (rate-limited warn at 1/1024) instead of growing unbounded. - Raises usageMaxPending to 50000 to absorb realistic inference bursts. - Replaces the package-level batcher global with a mutex-guarded pair plus a currentBatcher() accessor so Init / Shutdown cycles are race-free. - Adds ShutdownUsageRecorder() for graceful drain on process exit (not yet wired into app shutdown, just published). - Adds FlushNow() for deterministic tests; the middleware suite no longer needs 6s sleeps per spec and now runs in ~50ms instead of 18s. - Re-queue on failed flush is now cap-aware: prepends as much of the failed batch as fits alongside concurrent arrivals, instead of dropping the whole batch when full. Refs: #9862 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * feat(usage): drain usage batcher on graceful shutdown Registers ShutdownUsageRecorder with the existing signals.RegisterGracefulTerminationHandler so SIGINT/SIGTERM synchronously flushes any in-memory usage records before the process exits. Without this, up to one flush interval (5s) of recorded usage was lost when LocalAI restarted. Refs: #9862 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> --------- Signed-off-by: Ettore Di Giacinto <mudler@localai.io> Co-authored-by: Ettore Di Giacinto <mudler@localai.io>
427 lines
17 KiB
Markdown
427 lines
17 KiB
Markdown
+++
|
|
disableToc = false
|
|
title = "Authentication & Authorization"
|
|
weight = 26
|
|
url = '/features/authentication'
|
|
+++
|
|
|
|
LocalAI supports two authentication modes: **legacy API key authentication** (simple shared keys) and a full **user authentication system** with roles, sessions, OAuth, and per-user usage tracking.
|
|
|
|
## Legacy API Key Authentication
|
|
|
|
The simplest way to protect your LocalAI instance is with API keys. Set one or more keys via environment variable or CLI flag:
|
|
|
|
```bash
|
|
# Single key
|
|
LOCALAI_API_KEY=sk-my-secret-key localai run
|
|
|
|
# Multiple keys (comma-separated)
|
|
LOCALAI_API_KEY=key1,key2,key3 localai run
|
|
```
|
|
|
|
Clients provide the key via any of these methods:
|
|
|
|
- `Authorization: Bearer <key>` header
|
|
- `x-api-key: <key>` header
|
|
- `xi-api-key: <key>` header
|
|
- `token` cookie
|
|
|
|
Legacy API keys grant **full admin access** — there is no role separation. For multi-user deployments with role-based access, use the user authentication system instead.
|
|
|
|
API keys can also be managed at runtime through the [Runtime Settings]({{%relref "features/runtime-settings" %}}) interface.
|
|
|
|
## User Authentication System
|
|
|
|
The user authentication system provides:
|
|
|
|
- **User accounts** with email, name, and avatar
|
|
- **Role-based access control** (admin vs. user)
|
|
- **Session-based authentication** with secure cookies
|
|
- **OAuth login** (GitHub) and **OIDC single sign-on** (Keycloak, Google, Okta, Authentik, etc.)
|
|
- **Per-user API keys** for programmatic access
|
|
- **Admin route gating** — management endpoints are restricted to admins
|
|
- **Per-user usage tracking** with token consumption metrics
|
|
|
|
### Enabling Authentication
|
|
|
|
Set `LOCALAI_AUTH=true` or provide a GitHub OAuth Client ID or OIDC Client ID (which auto-enables auth):
|
|
|
|
```bash
|
|
# Enable with SQLite (default, stored at {DataPath}/database.db)
|
|
LOCALAI_AUTH=true localai run
|
|
|
|
# Enable with GitHub OAuth
|
|
GITHUB_CLIENT_ID=your-client-id \
|
|
GITHUB_CLIENT_SECRET=your-client-secret \
|
|
LOCALAI_BASE_URL=http://localhost:8080 \
|
|
localai run
|
|
|
|
# Enable with OIDC provider (e.g. Keycloak)
|
|
LOCALAI_OIDC_ISSUER=https://keycloak.example.com/realms/myrealm \
|
|
LOCALAI_OIDC_CLIENT_ID=your-client-id \
|
|
LOCALAI_OIDC_CLIENT_SECRET=your-client-secret \
|
|
LOCALAI_BASE_URL=http://localhost:8080 \
|
|
localai run
|
|
|
|
# Enable with PostgreSQL
|
|
LOCALAI_AUTH=true \
|
|
LOCALAI_AUTH_DATABASE_URL=postgres://user:pass@host/dbname \
|
|
localai run
|
|
```
|
|
|
|
### Configuration Reference
|
|
|
|
| Environment Variable | Default | Description |
|
|
|---|---|---|
|
|
| `LOCALAI_AUTH` | `false` | Enable user authentication and authorization |
|
|
| `LOCALAI_AUTH_DATABASE_URL` | `{DataPath}/database.db` | Database URL — `postgres://...` for PostgreSQL, or a file path for SQLite |
|
|
| `GITHUB_CLIENT_ID` | | GitHub OAuth App Client ID (auto-enables auth when set) |
|
|
| `GITHUB_CLIENT_SECRET` | | GitHub OAuth App Client Secret |
|
|
| `LOCALAI_OIDC_ISSUER` | | OIDC issuer URL for auto-discovery (e.g. `https://accounts.google.com`) |
|
|
| `LOCALAI_OIDC_CLIENT_ID` | | OIDC Client ID (auto-enables auth when set) |
|
|
| `LOCALAI_OIDC_CLIENT_SECRET` | | OIDC Client Secret |
|
|
| `LOCALAI_BASE_URL` | | Base URL for OAuth callbacks (e.g. `http://localhost:8080`) |
|
|
| `LOCALAI_ADMIN_EMAIL` | | Email address to auto-promote to admin role on login |
|
|
| `LOCALAI_REGISTRATION_MODE` | `approval` | Registration mode: `open`, `approval`, or `invite` |
|
|
| `LOCALAI_DISABLE_LOCAL_AUTH` | `false` | Disable local email/password registration and login (for OAuth/OIDC-only deployments) |
|
|
|
|
### Disabling Local Authentication
|
|
|
|
If you want to enforce OAuth/OIDC-only login and prevent users from registering or logging in with email/password, set `LOCALAI_DISABLE_LOCAL_AUTH=true` (or pass `--disable-local-auth`):
|
|
|
|
```bash
|
|
# OAuth-only setup (no email/password)
|
|
LOCALAI_DISABLE_LOCAL_AUTH=true \
|
|
GITHUB_CLIENT_ID=your-client-id \
|
|
GITHUB_CLIENT_SECRET=your-client-secret \
|
|
LOCALAI_BASE_URL=http://localhost:8080 \
|
|
localai run
|
|
```
|
|
|
|
When disabled:
|
|
- The login page will not show email/password forms (the UI checks the `providers` list from `/api/auth/status`)
|
|
- `POST /api/auth/register` returns `403 Forbidden`
|
|
- `POST /api/auth/login` returns `403 Forbidden`
|
|
- OAuth/OIDC login continues to work normally
|
|
|
|
### Roles
|
|
|
|
There are two roles:
|
|
|
|
- **Admin**: Full access to all endpoints, including model management, backend configuration, system settings, traces, agents, and user management.
|
|
- **User**: Access to inference endpoints only — chat completions, embeddings, image/video/audio generation, TTS, MCP chat, and their own usage statistics.
|
|
|
|
The **first user** to sign in is automatically assigned the admin role. Additional users can be promoted to admin via the admin user management API or by setting `LOCALAI_ADMIN_EMAIL` to their email address.
|
|
|
|
### Registration Modes
|
|
|
|
| Mode | Description |
|
|
|---|---|
|
|
| `open` | Anyone can register and is immediately active |
|
|
| `approval` | New users land in "pending" status until an admin approves them. If a valid invite code is provided during registration, the user is activated immediately (skipping the approval wait). **(default)** |
|
|
| `invite` | Registration requires a valid invite link generated by an admin. Without one, registration is rejected. |
|
|
|
|
### Invite Links
|
|
|
|
Admins can generate single-use, time-limited invite links from the **Users → Invites** tab in the web UI, or via the API:
|
|
|
|
```bash
|
|
# Create an invite link (default: expires in 7 days)
|
|
curl -X POST http://localhost:8080/api/auth/admin/invites \
|
|
-H "Authorization: Bearer <admin-key>" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"expiresInHours": 168}'
|
|
|
|
# List all invites
|
|
curl http://localhost:8080/api/auth/admin/invites \
|
|
-H "Authorization: Bearer <admin-key>"
|
|
|
|
# Revoke an unused invite
|
|
curl -X DELETE http://localhost:8080/api/auth/admin/invites/<invite-id> \
|
|
-H "Authorization: Bearer <admin-key>"
|
|
|
|
# Check if an invite code is valid (public, no auth required)
|
|
curl http://localhost:8080/api/auth/invite/<code>/check
|
|
```
|
|
|
|
Share the invite URL (`/invite/<code>`) with the user. When they open it, the registration form is pre-filled with the invite code. Invite codes are single-use — once consumed, they cannot be reused. Expired or used invites are rejected.
|
|
|
|
For GitHub OAuth, the invite code is passed as a query parameter to the login URL (`/api/auth/github/login?invite_code=<code>`) and stored in a cookie during the OAuth flow.
|
|
|
|
### Admin-Only Endpoints
|
|
|
|
When authentication is enabled, the following endpoints require admin role:
|
|
|
|
**Model & Backend Management:**
|
|
- `GET /api/models`, `POST /api/models/install/*`, `POST /api/models/delete/*`
|
|
- `GET /api/backends`, `POST /api/backends/install/*`, `POST /api/backends/delete/*`
|
|
- `GET /api/operations`, `POST /api/operations/*/cancel`
|
|
- `GET /models/available`, `GET /models/galleries`, `GET /models/jobs/*`
|
|
- `GET /backends`, `GET /backends/available`, `GET /backends/galleries`
|
|
|
|
**System & Monitoring:**
|
|
- `GET /api/traces`, `POST /api/traces/clear`
|
|
- `GET /api/backend-traces`, `POST /api/backend-traces/clear`
|
|
- `GET /api/backend-logs/*`, `POST /api/backend-logs/*/clear`
|
|
- `GET /api/resources`, `GET /api/settings`, `POST /api/settings`
|
|
- `GET /system`, `GET /backend/monitor`, `POST /backend/shutdown`
|
|
|
|
**P2P:**
|
|
- `GET /api/p2p/*`
|
|
|
|
**Agents & Jobs:**
|
|
- All `/api/agents/*` endpoints
|
|
- All `/api/agent/tasks/*` and `/api/agent/jobs/*` endpoints
|
|
|
|
**User-Accessible Endpoints (all authenticated users):**
|
|
- `POST /v1/chat/completions`, `POST /v1/embeddings`, `POST /v1/completions`
|
|
- `POST /v1/images/generations`, `POST /v1/audio/*`, `POST /tts`, `POST /vad`, `POST /video`
|
|
- `GET /v1/models`, `POST /v1/tokenize`, `POST /v1/detection`
|
|
- `POST /v1/mcp/chat/completions`, `POST /v1/messages`, `POST /v1/responses`
|
|
- `POST /stores/*`, `GET /api/cors-proxy`
|
|
- `GET /version`, `GET /api/features`, `GET /swagger/*`, `GET /metrics`
|
|
- `GET /api/auth/usage` (own usage data)
|
|
|
|
### Web UI Access Control
|
|
|
|
When auth is enabled, the React UI sidebar dynamically shows/hides sections based on the user's role:
|
|
|
|
- **All users see**: Home, Chat, Images, Video, TTS, Sound, Talk, Usage, API docs link
|
|
- **Admins also see**: Install Models, Agents section (Agents, Skills, Memory, MCP CI Jobs), System section (Backends, Traces, Swarm, System, Settings)
|
|
|
|
Admin-only pages are also protected at the router level — navigating directly to an admin URL redirects non-admin users to the home page.
|
|
|
|
### GitHub OAuth Setup
|
|
|
|
1. Create a GitHub OAuth App at **Settings → Developer settings → OAuth Apps → New OAuth App**
|
|
2. Set the **Authorization callback URL** to `{LOCALAI_BASE_URL}/api/auth/github/callback`
|
|
3. Set `GITHUB_CLIENT_ID` and `GITHUB_CLIENT_SECRET` environment variables
|
|
4. Set `LOCALAI_BASE_URL` to your publicly-accessible URL
|
|
|
|
### OIDC Setup
|
|
|
|
Any OIDC-compliant identity provider can be used for single sign-on. This includes Keycloak, Google, Okta, Authentik, Azure AD, and many others.
|
|
|
|
**Steps:**
|
|
|
|
1. Create a client/application in your OIDC provider
|
|
2. Set the redirect URL to `{LOCALAI_BASE_URL}/api/auth/oidc/callback`
|
|
3. Set the three environment variables: `LOCALAI_OIDC_ISSUER`, `LOCALAI_OIDC_CLIENT_ID`, `LOCALAI_OIDC_CLIENT_SECRET`
|
|
|
|
LocalAI uses OIDC auto-discovery (the `/.well-known/openid-configuration` endpoint) and requests the standard scopes: `openid`, `profile`, `email`.
|
|
|
|
**Provider examples:**
|
|
|
|
```bash
|
|
# Keycloak
|
|
LOCALAI_OIDC_ISSUER=https://keycloak.example.com/realms/myrealm
|
|
|
|
# Google
|
|
LOCALAI_OIDC_ISSUER=https://accounts.google.com
|
|
|
|
# Authentik
|
|
LOCALAI_OIDC_ISSUER=https://authentik.example.com/application/o/localai/
|
|
|
|
# Okta
|
|
LOCALAI_OIDC_ISSUER=https://your-org.okta.com
|
|
```
|
|
|
|
For OIDC, invite codes work the same way as GitHub OAuth — the invite code is passed as a query parameter to the login URL (`/api/auth/oidc/login?invite_code=<code>`) and stored in a cookie during the OAuth flow.
|
|
|
|
### User API Keys
|
|
|
|
Authenticated users can create personal API keys for programmatic access:
|
|
|
|
```bash
|
|
# Create an API key (requires session auth)
|
|
curl -X POST http://localhost:8080/api/auth/api-keys \
|
|
-H "Cookie: session=<session-id>" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"name": "My Script Key"}'
|
|
```
|
|
|
|
User API keys inherit the creating user's role. Admin keys grant admin access; user keys grant user-level access.
|
|
|
|
### Auth API Endpoints
|
|
|
|
| Method | Endpoint | Description | Auth Required |
|
|
|---|---|---|---|
|
|
| `GET` | `/api/auth/status` | Auth state, current user, providers | No |
|
|
| `POST` | `/api/auth/logout` | End session | Yes |
|
|
| `GET` | `/api/auth/me` | Current user info | Yes |
|
|
| `POST` | `/api/auth/api-keys` | Create API key | Yes |
|
|
| `GET` | `/api/auth/api-keys` | List user's API keys | Yes |
|
|
| `DELETE` | `/api/auth/api-keys/:id` | Revoke API key | Yes |
|
|
| `GET` | `/api/auth/usage` | User's own usage stats | Yes |
|
|
| `GET` | `/api/auth/usage/sources` | User's own per-API-key / per-source breakdown | Yes |
|
|
| `GET` | `/api/auth/admin/users` | List all users | Admin |
|
|
| `PUT` | `/api/auth/admin/users/:id/role` | Change user role | Admin |
|
|
| `DELETE` | `/api/auth/admin/users/:id` | Delete user | Admin |
|
|
| `GET` | `/api/auth/admin/usage` | All users' usage stats | Admin |
|
|
| `GET` | `/api/auth/admin/usage/sources` | All users' per-API-key / per-source breakdown | Admin |
|
|
| `POST` | `/api/auth/admin/invites` | Create invite link | Admin |
|
|
| `GET` | `/api/auth/admin/invites` | List all invites | Admin |
|
|
| `DELETE` | `/api/auth/admin/invites/:id` | Revoke unused invite | Admin |
|
|
| `GET` | `/api/auth/invite/:code/check` | Check if invite code is valid | No |
|
|
| `GET` | `/api/auth/github/login` | Start GitHub OAuth | No |
|
|
| `GET` | `/api/auth/github/callback` | GitHub OAuth callback (internal) | No |
|
|
| `GET` | `/api/auth/oidc/login` | Start OIDC login | No |
|
|
| `GET` | `/api/auth/oidc/callback` | OIDC callback (internal) | No |
|
|
|
|
## Usage Tracking
|
|
|
|
When authentication is enabled, LocalAI automatically tracks per-user token usage for inference endpoints. Usage data includes:
|
|
|
|
- **Prompt tokens**, **completion tokens**, and **total tokens** per request
|
|
- **Model** used and **endpoint** called
|
|
- **Request duration**
|
|
- **Timestamp** for time-series aggregation
|
|
|
|
### Viewing Usage
|
|
|
|
Usage is accessible through the **Usage** page in the web UI (visible to all authenticated users) or via the API:
|
|
|
|
```bash
|
|
# Get your own usage (default: last 30 days)
|
|
curl http://localhost:8080/api/auth/usage?period=month \
|
|
-H "Authorization: Bearer <key>"
|
|
|
|
# Admin: get all users' usage
|
|
curl http://localhost:8080/api/auth/admin/usage?period=week \
|
|
-H "Authorization: Bearer <admin-key>"
|
|
|
|
# Admin: filter by specific user
|
|
curl "http://localhost:8080/api/auth/admin/usage?period=month&user_id=<user-id>" \
|
|
-H "Authorization: Bearer <admin-key>"
|
|
```
|
|
|
|
**Period values:**
|
|
- `day` — last 24 hours, bucketed by hour
|
|
- `week` — last 7 days, bucketed by day
|
|
- `month` — last 30 days, bucketed by day (default)
|
|
- `all` — all time, bucketed by month
|
|
|
|
**Response format:**
|
|
|
|
```json
|
|
{
|
|
"usage": [
|
|
{
|
|
"bucket": "2026-03-18",
|
|
"model": "gpt-4",
|
|
"user_id": "abc-123",
|
|
"user_name": "Alice",
|
|
"prompt_tokens": 1500,
|
|
"completion_tokens": 800,
|
|
"total_tokens": 2300,
|
|
"request_count": 12
|
|
}
|
|
],
|
|
"totals": {
|
|
"prompt_tokens": 1500,
|
|
"completion_tokens": 800,
|
|
"total_tokens": 2300,
|
|
"request_count": 12
|
|
}
|
|
}
|
|
```
|
|
|
|
### Usage Dashboard
|
|
|
|
The web UI Usage page provides:
|
|
- **Period selector** - switch between day, week, month, and all-time views
|
|
- **Summary cards** - total requests, prompt tokens, completion tokens, total tokens
|
|
- **By Model table** - per-model breakdown with visual usage bars
|
|
- **By User table** (admin only) - per-user breakdown across all models
|
|
- **Sources tab** - per-API-key and per-source breakdown (described below)
|
|
|
|
### Per-API-key Breakdown
|
|
|
|
The **Sources** tab on the Usage page surfaces a third dimension of the same data: traffic broken down by API key and by request source. Three source classes are tracked:
|
|
|
|
- **API key** - request authenticated with a named user API key (`Authorization: Bearer lai-...`, `x-api-key`, or `token` cookie). Each key shows up with its label (snapshotted at write time, so revoked keys still display the original name).
|
|
- **Web UI** - request authenticated with a browser session cookie.
|
|
- **Legacy** - request authenticated with an env-configured `LOCALAI_API_KEY`. Visible to admins only.
|
|
|
|
The Sources tab is visible to every authenticated user. Non-admins see only their own keys plus their own Web UI traffic (legacy is filtered server-side). Admins see every key from every user.
|
|
|
|
The tab is laid out as:
|
|
|
|
- A **source mix ribbon** showing the percentage split across the three classes.
|
|
- A **top-N + Other stacked time chart** (top 7 sources by total tokens; the rest roll up).
|
|
- A **searchable, sortable table** of every key plus the Web UI and Legacy pseudo-rows. Click a row to filter the chart to that source.
|
|
|
|
#### Endpoints
|
|
|
|
| Method | Path | Auth | Description |
|
|
|--------|------|------|-------------|
|
|
| `GET` | `/api/auth/usage/sources` | Self | Caller's per-source breakdown. Excludes legacy. |
|
|
| `GET` | `/api/auth/admin/usage/sources` | Admin | All users' per-source breakdown. Accepts `user_id` and `api_key_id` filters. Includes legacy. |
|
|
|
|
Both endpoints accept the same `period` parameter (`day`, `week`, `month`, `all`) as `/api/auth/usage`.
|
|
|
|
```bash
|
|
# Your own per-source usage for the last week
|
|
curl "http://localhost:8080/api/auth/usage/sources?period=week" \
|
|
-H "Authorization: Bearer <key>"
|
|
|
|
# Admin: filter to a single API key across all users
|
|
curl "http://localhost:8080/api/auth/admin/usage/sources?period=month&api_key_id=<key-id>" \
|
|
-H "Authorization: Bearer <admin-key>"
|
|
```
|
|
|
|
**Response shape:**
|
|
|
|
```json
|
|
{
|
|
"buckets": [
|
|
{ "bucket": "2026-05-19", "source": "apikey",
|
|
"api_key_id": "uuid", "api_key_name": "ci-runner",
|
|
"total_tokens": 20000, "request_count": 142, "...": "..." },
|
|
{ "bucket": "2026-05-19", "source": "web",
|
|
"total_tokens": 300, "request_count": 11, "...": "..." }
|
|
],
|
|
"totals": {
|
|
"by_source": {
|
|
"apikey": { "tokens": 1234567, "requests": 8420 },
|
|
"web": { "tokens": 92000, "requests": 211 }
|
|
},
|
|
"by_key": [
|
|
{ "api_key_id": "uuid", "api_key_name": "ci-runner",
|
|
"tokens": 2100000, "requests": 8420,
|
|
"last_used": "2026-05-20T12:34:56Z" }
|
|
],
|
|
"grand_total": { "tokens": 1334777, "requests": 8645 }
|
|
},
|
|
"truncated": false
|
|
}
|
|
```
|
|
|
|
The `by_key` list is server-sorted by tokens descending and capped at 200 entries. When more keys would qualify, the response sets `"truncated": true` so the UI can show a notice.
|
|
|
|
#### Migration of pre-feature data
|
|
|
|
Usage rows recorded before this feature have no `source` column. On startup, `InitDB` backfills them as `legacy` when the synthetic `legacy-api-key` user_id was used, and `web` for everything else. The migration is idempotent; existing aggregations remain correct after the upgrade.
|
|
|
|
## Combining Auth Modes
|
|
|
|
Legacy API keys and user authentication can be used simultaneously. When both are configured:
|
|
|
|
1. User sessions and user API keys are checked first
|
|
2. Legacy API keys are checked as fallback — they grant **admin-level access**
|
|
3. This allows a gradual migration from shared API keys to per-user accounts
|
|
|
|
## Build Requirements
|
|
|
|
The user authentication system requires CGO for SQLite support. It is enabled with the `auth` build tag, which is included by default in Docker builds.
|
|
|
|
```bash
|
|
# Building from source with auth support
|
|
GO_TAGS=auth make build
|
|
|
|
# Or directly with go build
|
|
go build -tags auth ./...
|
|
```
|
|
|
|
The default Dockerfile includes `GO_TAGS="auth"`, so all Docker images ship with auth support. When building from source without the `auth` tag, setting `LOCALAI_AUTH=true` has no effect — the system operates without authentication.
|