Adding mkdocs and privileged tools

This commit is contained in:
2026-01-31 16:20:35 -05:00
parent f68a79bdb7
commit 7cfad5baba
10 changed files with 476 additions and 14 deletions

274
docs/authentication.md Normal file
View File

@@ -0,0 +1,274 @@
# Authentication Architecture
This document describes the authentication stack for SimbaRAG: LLDAP → Authelia → OAuth2/OIDC.
## Overview
```
┌─────────┐ ┌──────────┐ ┌──────────────┐ ┌──────────┐
│ LLDAP │────▶│ Authelia │────▶│ OAuth2/OIDC │────▶│ SimbaRAG │
│ (Users) │ │ (IdP) │ │ (Flow) │ │ (App) │
└─────────┘ └──────────┘ └──────────────┘ └──────────┘
```
| Component | Role |
|-----------|------|
| **LLDAP** | Lightweight LDAP server storing users and groups |
| **Authelia** | Identity provider that authenticates against LLDAP and issues OIDC tokens |
| **SimbaRAG** | Relying party that consumes OIDC tokens and manages sessions |
## OIDC Configuration
### Environment Variables
| Variable | Description | Default |
|----------|-------------|---------|
| `OIDC_ISSUER` | Authelia server URL | Required |
| `OIDC_CLIENT_ID` | Client ID registered in Authelia | Required |
| `OIDC_CLIENT_SECRET` | Client secret for token exchange | Required |
| `OIDC_REDIRECT_URI` | Callback URL after authentication | Required |
| `OIDC_USE_DISCOVERY` | Enable automatic discovery | `true` |
| `JWT_SECRET_KEY` | Secret for signing backend JWTs | Required |
### Discovery
When `OIDC_USE_DISCOVERY=true`, the application fetches endpoints from:
```
{OIDC_ISSUER}/.well-known/openid-configuration
```
This provides:
- Authorization endpoint
- Token endpoint
- JWKS URI for signature verification
- Supported scopes and claims
## Authentication Flow
### 1. Login Initiation
```
GET /api/user/oidc/login
```
1. Generate PKCE code verifier and challenge (S256)
2. Generate CSRF state token
3. Store state in session storage
4. Return authorization URL for frontend redirect
### 2. Authorization
User is redirected to Authelia where they:
1. Enter LDAP credentials
2. Complete MFA if configured
3. Consent to requested scopes
### 3. Callback
```
GET /api/user/oidc/callback?code=...&state=...
```
1. Validate state matches stored value (CSRF protection)
2. Exchange authorization code for tokens using PKCE verifier
3. Verify ID token signature using JWKS
4. Validate claims (issuer, audience, expiration)
5. Create or update user in database
6. Issue backend JWT tokens (access + refresh)
### 4. Token Refresh
```
POST /api/user/refresh
Authorization: Bearer <refresh_token>
```
Issues a new access token without re-authentication.
## User Model
```python
class User(Model):
id = UUIDField(primary_key=True)
username = CharField(max_length=255)
password = BinaryField(null=True) # Nullable for OIDC-only users
email = CharField(max_length=100, unique=True)
# OIDC fields
oidc_subject = CharField(max_length=255, unique=True, null=True)
auth_provider = CharField(max_length=50, default="local") # "local" or "oidc"
ldap_groups = JSONField(default=[]) # LDAP groups from OIDC claims
created_at = DatetimeField(auto_now_add=True)
updated_at = DatetimeField(auto_now=True)
def has_group(self, group: str) -> bool:
"""Check if user belongs to a specific LDAP group."""
return group in (self.ldap_groups or [])
def is_admin(self) -> bool:
"""Check if user is an admin (member of lldap_admin group)."""
return self.has_group("lldap_admin")
```
### User Provisioning
The `OIDCUserService` handles automatic user creation:
1. Extract claims from ID token (`sub`, `email`, `preferred_username`)
2. Check if user exists by `oidc_subject`
3. If not, check by email for migration from local auth
4. Create new user or update existing
## JWT Tokens
Backend issues its own JWTs after OIDC authentication:
| Token Type | Purpose | Typical Lifetime |
|------------|---------|------------------|
| Access Token | API authorization | 15 minutes |
| Refresh Token | Obtain new access tokens | 7 days |
### Claims
```json
{
"identity": "<user-uuid>",
"type": "access|refresh",
"exp": 1234567890,
"iat": 1234567890
}
```
## Protected Endpoints
All API endpoints use the `@jwt_refresh_token_required` decorator for basic authentication:
```python
@blueprint.route("/example")
@jwt_refresh_token_required
async def protected_endpoint():
user_id = get_jwt_identity()
# ...
```
---
## Role-Based Access Control (RBAC)
RBAC is implemented using LDAP groups passed through Authelia as OIDC claims. Users in the `lldap_admin` group have admin privileges.
### Architecture
```
┌─────────────────────────────────────────────────────────────┐
│ LLDAP │
│ Groups: lldap_admin, lldap_user │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Authelia │
│ Scope: groups → Claim: groups = ["lldap_admin"] │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ SimbaRAG │
│ 1. Extract groups from ID token │
│ 2. Store in User.ldap_groups │
│ 3. Check membership with @admin_required decorator │
└─────────────────────────────────────────────────────────────┘
```
### Authelia Configuration
Ensure Authelia is configured to pass the `groups` claim:
```yaml
identity_providers:
oidc:
clients:
- client_id: simbarag
scopes:
- openid
- profile
- email
- groups # Required for RBAC
```
### Admin-Only Endpoints
The `@admin_required` decorator protects privileged endpoints:
```python
from blueprints.users.decorators import admin_required
@blueprint.post("/admin-action")
@admin_required
async def admin_only_endpoint():
# Only users in lldap_admin group can access
...
```
**Protected endpoints:**
| Endpoint | Access | Description |
|----------|--------|-------------|
| `POST /api/rag/index` | Admin | Trigger document indexing |
| `POST /api/rag/reindex` | Admin | Clear and reindex all documents |
| `GET /api/rag/stats` | All users | View vector store statistics |
### User Response
The OIDC callback returns group information:
```json
{
"access_token": "...",
"refresh_token": "...",
"user": {
"id": "uuid",
"username": "john",
"email": "john@example.com",
"groups": ["lldap_admin", "lldap_user"],
"is_admin": true
}
}
```
---
## Security Considerations
### Current Gaps
| Issue | Risk | Mitigation |
|-------|------|------------|
| In-memory session storage | State lost on restart, not scalable | Use Redis for production |
| No token revocation | Tokens valid until expiry | Implement blacklist or short expiry |
| No audit logging | Cannot track auth events | Add event logging |
| Single JWT secret | Compromise affects all tokens | Rotate secrets, use asymmetric keys |
### Recommendations
1. **Use Redis** for OIDC state storage in production
2. **Implement logout** with token blacklisting
3. **Add audit logging** for authentication events
4. **Rotate JWT secrets** regularly
5. **Use short-lived access tokens** (15 min) with refresh
---
## File Reference
| File | Purpose |
|------|---------|
| `services/raggr/oidc_config.py` | OIDC client configuration and discovery |
| `services/raggr/blueprints/users/models.py` | User model definition with group helpers |
| `services/raggr/blueprints/users/oidc_service.py` | User provisioning from OIDC claims |
| `services/raggr/blueprints/users/__init__.py` | Auth endpoints and flow |
| `services/raggr/blueprints/users/decorators.py` | Auth decorators (`@admin_required`) |

14
docs/index.md Normal file
View File

@@ -0,0 +1,14 @@
# SimbaRAG Documentation
SimbaRAG is a RAG-powered conversational AI system with enterprise authentication.
## Architecture
- **Backend**: Quart (async Python) with Tortoise ORM
- **Vector Store**: LangChain with configurable embeddings
- **Auth Stack**: LLDAP → Authelia → OAuth2/OIDC
- **Database**: PostgreSQL
## Sections
- [Authentication](authentication.md) - OIDC flow, user management, and RBAC planning