Files
furyhawk 8351e73d39 feat: add Zustand stores for conversation, file preview, sidebar, theme, and knowledge base selection
- Implemented `conversation-store` for managing conversations and messages.
- Created `file-preview-store` to handle file preview state.
- Added `sidebar-store` for sidebar visibility management.
- Developed `theme-store` for theme persistence and management.
- Introduced `kb-selection-store` for managing active knowledge base selections with persistence.

chore: define API and chat types

- Added types for API responses, authentication, chat messages, conversations, and projects.
- Defined interfaces for various entities including users, sessions, and message ratings.

build: configure TypeScript and testing setup

- Set up `tsconfig.json` for TypeScript configuration.
- Created `vitest.config.ts` for testing configuration with Vitest.
- Added `vitest.setup.ts` for global test setup including mocks for Next.js router and media queries.
- Configured Vercel deployment settings in `vercel.json`.
2026-06-11 16:54:43 +08:00

217 lines
8.5 KiB
Markdown

# Permissions & Access Control
## Roles
Two roles are defined in `app/db/models/user.py`:
- **admin** -- Full access to all features. Can manage users, RAG collections,
sync sources, webhooks, and export data.
- **user** -- Standard access. Can chat with the AI agent, manage their own
profile, view their own conversations, upload files to chat, and search
the knowledge base.
Admins implicitly have all user permissions. The `User.has_role()` method
returns `True` for any role if the user is an admin.
## Dependency Aliases
These are defined in `app/api/deps.py` and used throughout the route layer:
| Alias | Resolves To | Access Level |
|-------|------------|--------------|
| `CurrentUser` | `Depends(get_current_user)` | Any authenticated user |
| `CurrentAdmin` | `Depends(RoleChecker(UserRole.ADMIN))` | Admin role required |
| `CurrentSuperuser` | `Depends(get_current_active_superuser)` | Admin role required (legacy alias) |
## Endpoint Access Matrix
### Authentication
| Endpoint | Method | Admin | User | Unauthenticated | Notes |
|----------|--------|-------|------|-----------------|-------|
| `/auth/login` | POST | Y | Y | Y | Returns JWT tokens |
| `/auth/register` | POST | Y | Y | Y | Creates new user account |
| `/auth/refresh` | POST | Y | Y | -- | Requires valid refresh token |
### Users
| Endpoint | Method | Admin | User | Notes |
|----------|--------|-------|------|-------|
| `/users/me` | GET | Y | Y | Own profile |
| `/users/me` | PATCH | Y | Y | Own profile; non-admins cannot change role |
| `/users/me/avatar` | POST | Y | Y | Upload own avatar image |
| `/users/avatar/{user_id}` | GET | Y | Y | Public avatar access |
| `/users` | GET | Y | -- | List all users (admin only) |
| `/users/{id}` | GET | Y | -- | View any user (admin only) |
| `/users/{id}` | PATCH | Y | -- | Update any user including role (admin only) |
| `/users/{id}` | DELETE | Y | -- | Delete any user (admin only) |
### AI Agent
| Endpoint | Method | Admin | User | Notes |
|----------|--------|-------|------|-------|
| `/agent/ws/agent` | WS | Y | Y | WebSocket chat with AI agent |
### Conversations
| Endpoint | Method | Admin | User | Notes |
|----------|--------|-------|------|-------|
| `/conversations` | GET | Y | Y | Own conversations only (filtered by user_id) |
| `/conversations` | POST | Y | Y | Create new conversation |
| `/conversations/{id}` | GET | Y | Y | Own conversations only (IDOR protection) |
| `/conversations/{id}` | PATCH | Y | Y | Update title / archived status |
| `/conversations/{id}` | DELETE | Y | Y | Delete own conversation |
| `/conversations/{id}/archive` | POST | Y | Y | Archive own conversation |
| `/conversations/{id}/messages` | GET | Y | Y | List messages in own conversation |
| `/conversations/{id}/messages` | POST | Y | Y | Add message to own conversation |
| `/conversations/export` | GET | Y | -- | Export all conversations (admin only) |
### Message Ratings
| Endpoint | Method | Admin | User | Notes |
|----------|--------|-------|------|-------|
| `/conversations/{id}/messages/{msg_id}/rate` | POST | Y | Y | Rate/update a message (like/dislike) |
| `/conversations/{id}/messages/{msg_id}/rate` | DELETE | Y | Y | Remove own rating |
| `/admin/ratings` | GET | Y | -- | List all ratings with filters (admin only) |
| `/admin/ratings/summary` | GET | Y | -- | Aggregated statistics (admin only) |
| `/admin/ratings/export` | GET | Y | -- | Export ratings JSON/CSV (admin only) |
| `/admin/conversations` | GET | Y | -- | List all conversations (admin only) |
### Files
| Endpoint | Method | Admin | User | Notes |
|----------|--------|-------|------|-------|
| `/files/upload` | POST | Y | Y | Upload file for chat |
| `/files/{id}` | GET | Y | Y | Download own files only (ownership check) |
| `/files/{id}/info` | GET | Y | Y | File metadata for own files only |
### RAG (Knowledge Base)
| Endpoint | Method | Admin | User | Notes |
|----------|--------|-------|------|-------|
| `/rag/supported-formats` | GET | Y | Y | List supported file formats |
| `/rag/search` | POST | Y | Y | Search knowledge base (all users) |
| `/rag/collections` | GET | Y | -- | List collections (admin only) |
| `/rag/collections/{name}` | POST | Y | -- | Create collection (admin only) |
| `/rag/collections/{name}` | DELETE | Y | -- | Drop collection (admin only) |
| `/rag/collections/{name}/info` | GET | Y | -- | Collection stats (admin only) |
| `/rag/collections/{name}/documents` | GET | Y | -- | List documents in collection (admin only) |
| `/rag/collections/{name}/documents/{id}` | DELETE | Y | -- | Delete document (admin only) |
| `/rag/collections/{name}/ingest` | POST | Y | -- | Upload and ingest file (admin only) |
| `/rag/documents` | GET | Y | -- | List tracked RAG documents (admin only) |
| `/rag/documents/{id}/download` | GET | Y | -- | Download original file (admin only) |
| `/rag/documents/{id}` | DELETE | Y | -- | Delete tracked document (admin only) |
| `/rag/documents/{id}/retry` | POST | Y | -- | Retry failed ingestion (admin only) |
| `/rag/sync/logs` | GET | Y | -- | List sync logs (admin only) |
| `/rag/sync/local` | POST | Y | -- | Trigger local directory sync (admin only) |
| `/rag/sync/{id}` | DELETE | Y | -- | Cancel sync operation (admin only) |
| `/rag/sync/sources` | GET | Y | -- | List sync sources (admin only) |
| `/rag/sync/sources` | POST | Y | -- | Create sync source (admin only) |
| `/rag/sync/sources/{id}` | PATCH | Y | -- | Update sync source (admin only) |
| `/rag/sync/sources/{id}` | DELETE | Y | -- | Delete sync source (admin only) |
| `/rag/sync/sources/{id}/trigger` | POST | Y | -- | Trigger manual sync (admin only) |
| `/rag/sync/connectors` | GET | Y | -- | List available connector types (admin only) |
### Health
| Endpoint | Method | Admin | User | Unauthenticated | Notes |
|----------|--------|-------|------|-----------------|-------|
| `/health` | GET | Y | Y | Y | No auth required |
## How It Works
### JWT Flow
1. User sends credentials to `POST /auth/login`.
2. Server validates credentials, returns `access_token` + `refresh_token`.
3. Client includes `Authorization: Bearer <access_token>` on subsequent requests.
4. `get_current_user` dependency extracts the JWT, verifies it, loads the user.
5. If the token is expired, the client uses `POST /auth/refresh` to get a new one.
### Role Checking
`RoleChecker` is a callable class that wraps `get_current_user`:
```python
class RoleChecker:
def __init__(self, required_role: UserRole):
self.required_role = required_role
async def __call__(self, user = Depends(get_current_user)) -> User:
if not user.has_role(self.required_role):
raise AuthorizationError(...)
return user
```
`User.has_role()` returns `True` if:
- The user's role matches the required role, OR
- The user is an admin (admin has all permissions).
### IDOR Protection
Resources owned by users (conversations, files) are protected at the service
layer. The service receives the current user's ID from the route and uses it
to filter queries:
```python
# In conversation route
items, total = await service.list_conversations(user_id=current_user.id, ...)
# In file route
chat_file = await file_upload_svc.get_user_file(file_id, current_user.id)
# Raises NotFoundError if user_id doesn't match
```
### RAG Access Model
RAG operates on a **global** access model:
- **Search** is available to all authenticated users (`CurrentUser`). All users
search the same collections and see the same results.
- **Management** (create/delete collections, upload documents, configure sync
sources) requires admin access (`CurrentAdmin`).
- There is no per-user document isolation. If you need per-user collections,
you would need to extend the service layer to scope collections by user.
### API Key Authentication
For programmatic access, clients can authenticate via API key:
```
X-API-Key: your-api-key-here
```
The `verify_api_key` dependency validates the key using constant-time comparison.
API key auth grants full access (no role distinction). Use it for trusted
server-to-server communication.
## Creating Users
### Via CLI
```bash
# Create a regular user
uv run ai_agent user create --email user@example.com --password secret
# Create an admin user
uv run ai_agent user create-admin --email admin@example.com --password secret
# Change user role
uv run ai_agent user set-role user@example.com --role admin
```
### Via Make
```bash
make create-admin # Interactive admin creation
make user-create # Interactive user creation
make user-list # List all users
```
### Via Quickstart
```bash
make quickstart # Creates admin@example.com / admin123 automatically
```