Initial: Tibi-Doku auf Starlight-Basis (Nova-Theme, Docker-Stack, Sync-Skripte)
This commit is contained in:
commit
58502aa3c8
78 files changed
+22860
No files matched your search
@@ -0,0 +1,606 @@
|
||||
---
|
||||
title: 'Authentication & Authorization'
|
||||
---
|
||||
|
||||
# Authentication & Authorization
|
||||
|
||||
## Overview
|
||||
|
||||
Tibi-Server uses JWT-based authentication with a role and permission system. Access control is configured per collection via permission sets that support public access, role-based access, token-based access, and custom permissions.
|
||||
|
||||
## Login Flow
|
||||
|
||||
### Username/Password Login
|
||||
|
||||
```
|
||||
POST /api/v1/login
|
||||
Content-Type: application/json
|
||||
|
||||
{"username": "admin", "password": "admin"}
|
||||
```
|
||||
|
||||
1. Password is hashed with MD5 and compared against the stored hash
|
||||
2. If matched, a JWT token and refresh token are generated
|
||||
3. JWT token is returned in the response body
|
||||
4. Refresh token is set as an HttpOnly cookie
|
||||
|
||||
### Refresh Token Login
|
||||
|
||||
```
|
||||
POST /api/v1/login
|
||||
Cookie: tibi-refresh-token=eyJ...
|
||||
```
|
||||
|
||||
When no username/password is provided but a refresh token cookie exists:
|
||||
1. The refresh token JWT is validated (HMAC-SHA256, expiration required)
|
||||
2. User ID is extracted from the token claims
|
||||
3. User is looked up by ID
|
||||
4. New JWT token is issued
|
||||
|
||||
### Logout
|
||||
|
||||
```
|
||||
POST /api/v1/login?logout=1
|
||||
```
|
||||
|
||||
Clears the refresh token cookie (sets expiry to -1). Any non-empty `logout` query parameter value triggers logout.
|
||||
|
||||
### Impersonation (Admin/OrgAdmin → User)
|
||||
|
||||
```
|
||||
POST /api/v1/login/impersonate
|
||||
X-Auth-Token: <admin-jwt>
|
||||
Content-Type: application/json
|
||||
|
||||
{"userId": "<target-user-id>"}
|
||||
```
|
||||
|
||||
Allows a SystemAdmin (role=0) or OrgAdmin to obtain a JWT token acting as another user.
|
||||
This is useful for debugging permission issues ("why can User X not see document Y?").
|
||||
|
||||
**Security rules:**
|
||||
| Role | Can impersonate | Reason |
|
||||
|---|---|---|
|
||||
| SystemAdmin (0) | Any user | Admin already has full access |
|
||||
| OrgAdmin | Users in same org(s) | OrgAdmin can already grant themselves the same team permissions |
|
||||
| User (1) | Nobody | Would break the permission model |
|
||||
|
||||
**Response (200):**
|
||||
```json
|
||||
{
|
||||
"token": "<jwt-acting-as-target-user>",
|
||||
"user": { "id": "...", "username": "targetuser", "role": 1, ... },
|
||||
"impersonatedBy": {
|
||||
"id": "<original-admin-id>",
|
||||
"username": "admin",
|
||||
"role": 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Important:** The JWT token contains an `impersonatedBy` field in the `object` claim,
|
||||
which is used by the audit system to record who actually performed each action.
|
||||
Every audit entry during an impersonated session will include:
|
||||
- `userId`/`username`/`userRole` → the impersonated (target) user
|
||||
- `impersonatedBy` → the original admin who initiated the impersonation
|
||||
- `authMethod` → `"impersonation"`
|
||||
|
||||
**Logout from impersonation:** Same as normal logout (`POST /api/v1/login?logout=1`).
|
||||
The refresh token cookie is cleared, ending the impersonated session. The admin
|
||||
must re-authenticate with their own credentials.
|
||||
|
||||
**Chained impersonation:** If an admin impersonates an org-admin, and that
|
||||
org-admin then impersonates a user, the `impersonatedBy` field always points to
|
||||
the **original admin** who started the chain — not the intermediate impersonator.
|
||||
|
||||
**Errors:**
|
||||
| Status | Description |
|
||||
|---|---|
|
||||
| 400 | `userId is required` or invalid userId format |
|
||||
| 403 | `impersonation not allowed: insufficient role` |
|
||||
| 403 | `impersonation not allowed: not admin of target user's org` |
|
||||
| 404 | target user not found |
|
||||
|
||||
## JWT Token Structure
|
||||
|
||||
### Access Token
|
||||
|
||||
| Field | Value | Description |
|
||||
|-------|-------|-------------|
|
||||
| Algorithm | HS256 | HMAC-SHA256 |
|
||||
| `object` | User data | `{id, username, role, permissions, orgs, teams, primaryOrgId, meta, ...}` |
|
||||
| `type` | `"User"` | Token type identifier |
|
||||
| `exp` | now + 60min | Expiration timestamp |
|
||||
|
||||
The `password` field is explicitly excluded from the token (via custom JSON marshaling).
|
||||
|
||||
The `object` includes org and team membership if the user has any:
|
||||
|
||||
```json
|
||||
{
|
||||
"object": {
|
||||
"id": "60a1b2c3d4...",
|
||||
"username": "alice",
|
||||
"role": 1,
|
||||
"permissions": [],
|
||||
"orgs": [{"orgId": "...", "role": "admin"}],
|
||||
"teams": [{"teamId": "...", "role": "member"}],
|
||||
"primaryOrgId": "..."
|
||||
},
|
||||
"type": "User",
|
||||
"exp": 1700000000
|
||||
}
|
||||
```
|
||||
|
||||
See [Orgs & Teams](18-orgs-teams.md) for details on org/team-based access control.
|
||||
|
||||
**Auth Header:**
|
||||
|
||||
```
|
||||
X-Auth-Token: eyJhbGciOiJIUzI1NiIs...
|
||||
```
|
||||
|
||||
### Refresh Token
|
||||
|
||||
| Field | Value | Description |
|
||||
|-------|-------|-------------|
|
||||
| Algorithm | HS256 | HMAC-SHA256 |
|
||||
| `object` | `{id: "userId"}` | Minimal user reference |
|
||||
| `type` | `"RefreshTokenData"` | Token type identifier |
|
||||
| `exp` | now + 10h | Expiration timestamp |
|
||||
|
||||
**Cookie:**
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Name | `tibi-refresh-token` |
|
||||
| MaxAge | 36000 (10 hours) |
|
||||
| Path | `/` |
|
||||
| HttpOnly | `true` |
|
||||
| SameSite | `Strict` |
|
||||
| Secure | `false` |
|
||||
|
||||
### Token Validation
|
||||
|
||||
Token validation includes caching — subsequent calls within the same request reuse the validated result:
|
||||
|
||||
```go
|
||||
// Cached in gin.Context:
|
||||
// "validate-auth-token-object" → user data map
|
||||
// "validate-auth-token-type" → token type string
|
||||
// "validate-auth-token-err" → validation error
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Admin Tokens (Server-Level)
|
||||
|
||||
Admin tokens provide token-based access for agents, CI/CD pipelines, and other automated systems without requiring the JWT login flow.
|
||||
|
||||
### Configuration
|
||||
|
||||
```yaml
|
||||
api:
|
||||
adminTokens:
|
||||
- token: "agent-token-xyz"
|
||||
label: "ci-deploy" # Optional: human-readable label for audit logs
|
||||
permissions:
|
||||
- project # Shorthand: all project CRUD operations
|
||||
- project.reload # Reload project config
|
||||
- user # Shorthand: all user CRUD operations
|
||||
- server.shutdown
|
||||
- namespace.myproject # All collections in myproject
|
||||
- token: "readonly-token"
|
||||
label: "monitoring" # Appears as tokenLabel in audit entries
|
||||
permissions:
|
||||
- project.read
|
||||
- user.read
|
||||
- namespace.*.*.read # Read-only across all namespaces and collections
|
||||
- token: "importer-token"
|
||||
label: "data-import"
|
||||
permissions:
|
||||
- namespace.myproject.articles.create # Only create in myproject/articles
|
||||
- namespace.myproject.articles.update # Only update in myproject/articles
|
||||
```
|
||||
|
||||
### Available Permissions
|
||||
|
||||
| Permission | Description |
|
||||
|-----------|-------------|
|
||||
| `project` | Shorthand for all project operations (`project.*`) |
|
||||
| `project.read` | GET projects |
|
||||
| `project.create` | POST (create) projects |
|
||||
| `project.update` | PUT (update) projects |
|
||||
| `project.delete` | DELETE projects |
|
||||
| `project.reload` | Reload project configuration |
|
||||
| `user` | Shorthand for all user operations (`user.*`) |
|
||||
| `user.read` | GET users |
|
||||
| `user.create` | POST (create) users |
|
||||
| `user.update` | PUT (update) users |
|
||||
| `user.delete` | DELETE users |
|
||||
| `server.shutdown` | Shutdown the server |
|
||||
| `namespace` | Shorthand: all collections in all projects |
|
||||
| `namespace.{project}` | Shorthand: all collections in a project |
|
||||
| `namespace.{project}.{collection}` | Shorthand: all actions on a collection |
|
||||
| `namespace.{project}.{collection}.read` | GET documents |
|
||||
| `namespace.{project}.{collection}.create` | POST (create) documents |
|
||||
| `namespace.{project}.{collection}.update` | PUT (update) documents |
|
||||
| `namespace.{project}.{collection}.delete` | DELETE documents |
|
||||
|
||||
### Wildcards
|
||||
|
||||
Use `*` as a wildcard for any single segment in a permission string:
|
||||
|
||||
| Permission | Description |
|
||||
|-----------|-------------|
|
||||
| `namespace.*` | All collections in any project |
|
||||
| `namespace.*.articles.read` | Read articles across all projects |
|
||||
| `namespace.myproject.*.read` | Read all collections in myproject |
|
||||
| `namespace.*.*.read` | Read-only across everything |
|
||||
|
||||
Shorthand works at any level — a permission with fewer segments implies all descendants:
|
||||
- `namespace` = `namespace.*.*.*` (everything)
|
||||
- `namespace.myproject` = `namespace.myproject.*.*` (all collections, all actions)
|
||||
- `namespace.myproject.articles` = `namespace.myproject.articles.*` (all actions)
|
||||
|
||||
### Usage
|
||||
|
||||
Send the token via `X-Admin-Token` header or `Authorization: Bearer` header:
|
||||
|
||||
```
|
||||
# Via X-Admin-Token header — project management
|
||||
POST /api/v1/project
|
||||
X-Admin-Token: agent-token-xyz
|
||||
Content-Type: application/json
|
||||
|
||||
{"name": "my-project", "description": "A new project", "configFile": "/path/to/config.yml"}
|
||||
```
|
||||
|
||||
```
|
||||
# Via Authorization: Bearer header — collection access
|
||||
GET /api/v1/_/myproject/articles
|
||||
Authorization: Bearer readonly-token
|
||||
```
|
||||
|
||||
```
|
||||
# Create document in a collection
|
||||
POST /api/v1/_/myproject/articles
|
||||
X-Admin-Token: importer-token
|
||||
Content-Type: application/json
|
||||
|
||||
{"title": "New Article", "body": "Content..."}
|
||||
```
|
||||
|
||||
### Token Priority
|
||||
|
||||
For project/user endpoints:
|
||||
1. **Admin Token** (`X-Admin-Token` / `Authorization: Bearer`): Checked first. If present and valid, access is granted immediately. If present but invalid, access is denied (no fallthrough).
|
||||
2. **JWT Auth** (`X-Auth-Token`): Checked as fallback if no admin token is provided.
|
||||
|
||||
For collection endpoints (`/_/:project/:collection`):
|
||||
1. **Admin Token**: If present, checked for `namespace.{project}.{collection}.{action}` permission. If present but lacking permission, access is denied (no fallthrough to JWT or public permissions).
|
||||
2. **Collection Token Permissions**: Header/query-based tokens defined in collection config.
|
||||
3. **Public / User / JWT**: Normal permission chain if no admin token is present.
|
||||
|
||||
### Project Admin Endpoints
|
||||
|
||||
For project admin endpoints (`reload`, `regenerate-search`, `backup`, `restore`), three auth methods are supported (checked in order):
|
||||
|
||||
1. Server-level admin tokens with the corresponding permission (e.g. `project.reload`, `project.regenerate-search`, `project.backup`)
|
||||
2. Project-level tokens configured in the project's `config.yml` (`admin.tokens[].allowReload`, `admin.tokens[].allowRegenerateSearch`, `admin.tokens[].allowBackup`)
|
||||
3. JWT login with admin role (`X-Auth-Token` header)
|
||||
|
||||
### Audit Trail
|
||||
|
||||
All admin token actions are logged in the audit log with additional context:
|
||||
|
||||
| Field | Description | Example |
|
||||
|-------|-------------|---------|
|
||||
| `authMethod` | Authentication method used | `admin-token`, `jwt` |
|
||||
| `tokenLabel` | Label from token config (if set) | `ci-deploy` |
|
||||
| `tokenPrefix` | First 8 characters of the token | `agent-to` |
|
||||
| `ip` | Client IP address | `192.168.1.100` |
|
||||
|
||||
The full token is **never** logged for security reasons. Use descriptive `label` values to identify tokens in audit logs.
|
||||
|
||||
---
|
||||
|
||||
## Roles
|
||||
|
||||
| Role | Value | Description |
|
||||
|------|-------|-------------|
|
||||
| Admin | `0` | Full access to all system and collection endpoints |
|
||||
| User | `1` | Access limited to assigned projects and permitted methods |
|
||||
|
||||
### Admin Role Behavior
|
||||
|
||||
- Can access all projects regardless of assignment
|
||||
- Can manage all users (create, update, delete)
|
||||
- Can manage all projects
|
||||
- Bypasses all collection permission checks
|
||||
- No projection restrictions
|
||||
- No pre-filter application
|
||||
- Only admins can change `role`, `permissions`, and `llmBudgets` on users
|
||||
|
||||
### User Role Behavior
|
||||
|
||||
- Can only see projects belonging to their orgs (via `project.orgId` ∈ user's org IDs)
|
||||
- Cannot create/update/delete projects (OrgAdmins can modify `teams[]`)
|
||||
- Can only GET and PUT their own user profile (OrgAdmins have broader scope)
|
||||
- Cannot change `username`, `role`, `permissions`, or `llmBudgets`
|
||||
- Subject to collection permission checks (via team permissions)
|
||||
- See [Orgs & Teams](18-orgs-teams.md) for the full role hierarchy
|
||||
|
||||
---
|
||||
|
||||
## Permission System
|
||||
|
||||
Each collection defines a `permissions` map with named permission sets:
|
||||
|
||||
```yaml
|
||||
permissions:
|
||||
public:
|
||||
methods:
|
||||
get: true
|
||||
filter:
|
||||
published: true
|
||||
user:
|
||||
methods:
|
||||
get: true
|
||||
post: true
|
||||
put:
|
||||
allow: true
|
||||
bulk: true # allow bulk PUT
|
||||
delete:
|
||||
allow: true
|
||||
bulk: true # allow bulk DELETE
|
||||
"token:apikey":
|
||||
methods:
|
||||
get: true
|
||||
post: true
|
||||
editor:
|
||||
methods:
|
||||
get: true
|
||||
put: true # shorthand for {allow: true}
|
||||
```
|
||||
|
||||
### PermissionSet Structure
|
||||
|
||||
```go
|
||||
type PermissionSet struct {
|
||||
Methods map[string]MethodPermission // Allowed methods (get, post, put, delete)
|
||||
Filter dbcrud.M // Auto-applied filter for queries
|
||||
ValidProjections []string // Allowed projection names
|
||||
CompiledFilterSubs []*CompiledSubst // Pre-compiled $subst expressions
|
||||
}
|
||||
|
||||
// MethodPermission represents a method permission.
|
||||
// Can be unmarshalled from a boolean (true/false) or an object {allow: bool, bulk: bool}.
|
||||
type MethodPermission struct {
|
||||
Allow bool // Allow the method
|
||||
Bulk bool // Allow bulk operations (POST array, PUT/DELETE without ID)
|
||||
}
|
||||
```
|
||||
|
||||
### Permission Check Priority
|
||||
|
||||
When a request comes in, permissions are checked in this order:
|
||||
|
||||
```
|
||||
1. Token-based permissions (header/query parameter match)
|
||||
│
|
||||
├── Token name found, value matches → Use token's permission set
|
||||
├── Token name found, value WRONG → ACCESS DENIED (no fallback!)
|
||||
└── No token name found → Continue
|
||||
│
|
||||
2. Public permissions
|
||||
│
|
||||
├── Method allowed → ACCESS GRANTED (no auth required)
|
||||
└── Method not allowed → Continue
|
||||
│
|
||||
3. Auth token validation (X-Auth-Token header)
|
||||
│
|
||||
├── No token / invalid → ACCESS DENIED
|
||||
└── Valid token → Continue
|
||||
│
|
||||
4. Admin role check (role = 0)
|
||||
│
|
||||
├── Is admin → ACCESS GRANTED
|
||||
└── Not admin → Continue
|
||||
│
|
||||
5. Team "user" permissions (role = 1, team-assigned to project)
|
||||
│
|
||||
├── User is member of a team assigned to this project
|
||||
│ AND collection has "user" permission set → Use "user" set
|
||||
└── Otherwise → Continue
|
||||
│
|
||||
6. Custom user permissions
|
||||
│
|
||||
├── Auth permissions contain "namespace:permName" → Use that permission set
|
||||
└── No match → Continue
|
||||
│
|
||||
7. Custom team permissions
|
||||
│
|
||||
├── User's teams (assigned to project) have permissions matching a collection key
|
||||
│ → Use that team permission set (union of all matching teams)
|
||||
└── No match → ACCESS DENIED
|
||||
```
|
||||
|
||||
See [Orgs & Teams — Permission System](18-orgs-teams.md#permission-system) for details on team-based permission resolution.
|
||||
|
||||
### Token-Based Permissions
|
||||
|
||||
Token permissions are matched against HTTP headers or query parameters:
|
||||
|
||||
```yaml
|
||||
permissions:
|
||||
"token:secretkey123": # Matches ?token=secretkey123 OR header token: secretkey123
|
||||
methods:
|
||||
get: true
|
||||
"x-api-key:my-api-key": # Matches ?x-api-key=my-api-key OR header x-api-key: my-api-key
|
||||
methods:
|
||||
get: true
|
||||
post: true
|
||||
```
|
||||
|
||||
**Important:** The permission key format is `name:value`. The `name` part is checked against both query parameters and headers. If the `name` is found but the `value` doesn't match, access is **denied immediately** with no fallback to public/user permissions.
|
||||
|
||||
### Custom Permissions
|
||||
|
||||
Users can have custom permission strings in their `permissions` array:
|
||||
|
||||
```json
|
||||
{
|
||||
"username": "editor1",
|
||||
"role": 1,
|
||||
"permissions": ["myblog:editor", "myblog:moderator"]
|
||||
}
|
||||
```
|
||||
|
||||
These are matched against collection permission keys using the format `{namespace}:{permissionKey}`:
|
||||
|
||||
```yaml
|
||||
# In project config (namespace: myblog)
|
||||
permissions:
|
||||
editor: # Matched by "myblog:editor"
|
||||
methods:
|
||||
get: true
|
||||
put: true
|
||||
moderator: # Matched by "myblog:moderator"
|
||||
methods:
|
||||
get: true
|
||||
delete: true
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Pre-Filters
|
||||
|
||||
Permission sets can define filters that are automatically applied to all queries:
|
||||
|
||||
```yaml
|
||||
permissions:
|
||||
public:
|
||||
methods:
|
||||
get: true
|
||||
filter:
|
||||
published: true
|
||||
deletedAt: null
|
||||
user:
|
||||
methods:
|
||||
get: true
|
||||
post: true
|
||||
put: true
|
||||
filter:
|
||||
author:
|
||||
$subst:
|
||||
expr: "auth.id" # Only see own documents
|
||||
```
|
||||
|
||||
### How Pre-Filters Work
|
||||
|
||||
1. The permission set's filter is deep-copied (to avoid mutation)
|
||||
2. `$subst` expressions are resolved with the current request context
|
||||
3. The filter is combined with any user-provided filter query via `$and`
|
||||
4. Admin role (0) never has pre-filters applied
|
||||
|
||||
### Filter Combination
|
||||
|
||||
```
|
||||
Final filter = $and: [
|
||||
{ _id: <from URL if single document> },
|
||||
{ <pre-filter from permissions> },
|
||||
{ <user-provided filter query> }
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Projection Restrictions
|
||||
|
||||
Permission sets can restrict which projections are available:
|
||||
|
||||
```yaml
|
||||
permissions:
|
||||
public:
|
||||
methods:
|
||||
get: true
|
||||
validProjections:
|
||||
- summary # Public can only use "summary" projection
|
||||
user:
|
||||
methods:
|
||||
get: true
|
||||
validProjections:
|
||||
- summary
|
||||
- detailed # Users can also use "detailed"
|
||||
```
|
||||
|
||||
**Behavior:**
|
||||
|
||||
- If `validProjections` is empty/not set → no restrictions (any projection or none)
|
||||
- If `validProjections` is set and no `projection` parameter → first entry is used as default
|
||||
- If `validProjections` is set and invalid projection requested → `400 Bad Request`
|
||||
- Admin role (0) is never restricted
|
||||
|
||||
---
|
||||
|
||||
## AuthData Model
|
||||
|
||||
Extracted from the JWT token on each authenticated request:
|
||||
|
||||
```go
|
||||
type AuthData struct {
|
||||
ID string // User ObjectID (hex)
|
||||
Username string // Username
|
||||
Role int // 0=Admin, 1=User
|
||||
Permissions []string // Custom permission strings
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## User Model
|
||||
|
||||
```go
|
||||
type User struct {
|
||||
Model // ID, InsertTime, UpdateTime
|
||||
Username *string // Required, alphanumeric, unique
|
||||
Password *string // Required, min 3 chars (stored as MD5 hash)
|
||||
Role *int // 0=Admin, 1=User (validated min=0, max=2)
|
||||
Permissions []string // Custom permission strings
|
||||
Meta map[string]interface{} // Arbitrary user metadata
|
||||
LLMBudgets []LLMBudget // Per-provider LLM token budgets
|
||||
}
|
||||
```
|
||||
|
||||
### User Search
|
||||
|
||||
The `_search` parameter on `/api/v1/user` searches across:
|
||||
- `username`
|
||||
- `meta.company`
|
||||
- `meta.prename`
|
||||
- `meta.surname`
|
||||
- `meta.email`
|
||||
- `meta.phone`
|
||||
|
||||
---
|
||||
|
||||
## Server-Level Admin Authentication
|
||||
|
||||
Server-level admin endpoints (e.g. shutdown) are protected by admin tokens with the appropriate permission:
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/api/v1/admin/shutdown \
|
||||
-H "X-Admin-Token: your-admin-token"
|
||||
```
|
||||
|
||||
Admin tokens are configured via `api.adminTokens` in `config.yml`. See [Configuration](02-configuration.md) for details.
|
||||
|
||||
## Personal Access Tokens (PAT)
|
||||
|
||||
Users can create and manage their own API tokens. A PAT uses the same
|
||||
`X-Auth-Token` header as the JWT and is always checked live against the owner's
|
||||
current rights. Tokens are a core feature and always enabled.
|
||||
|
||||
See [22-personal-access-tokens.md](22-personal-access-tokens.md) for format,
|
||||
scope grammar, endpoints, examples and security properties.
|
||||
Reference in new issue
Block a user