fix(server): harden self-hosted server — pgvector upgrade, admin auth, endpoint security (#5360)
This commit is contained in:
+2
-1
@@ -302,7 +302,8 @@
|
|||||||
"group": "Migration",
|
"group": "Migration",
|
||||||
"icon": "arrow-right",
|
"icon": "arrow-right",
|
||||||
"pages": [
|
"pages": [
|
||||||
"migration/oss-v2-to-v3"
|
"migration/oss-v2-to-v3",
|
||||||
|
"migration/server-pgvector-upgrade"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -228,6 +228,7 @@ If the user is on a pre-current major (Python < 2, TS < 3, or Platform `output_f
|
|||||||
- [OSS v2 to v3 Migration](https://docs.mem0.ai/migration/oss-v2-to-v3) [OSS]: Use when upgrading a self-hosted deployment across major versions.
|
- [OSS v2 to v3 Migration](https://docs.mem0.ai/migration/oss-v2-to-v3) [OSS]: Use when upgrading a self-hosted deployment across major versions.
|
||||||
- [Platform v2 to v3 Migration](https://docs.mem0.ai/migration/platform-v2-to-v3) [Platform]: Use when upgrading a Platform integration across major versions.
|
- [Platform v2 to v3 Migration](https://docs.mem0.ai/migration/platform-v2-to-v3) [Platform]: Use when upgrading a Platform integration across major versions.
|
||||||
- [API Changes](https://docs.mem0.ai/migration/api-changes) [Both]: Use when the upgrade involves API surface changes.
|
- [API Changes](https://docs.mem0.ai/migration/api-changes) [Both]: Use when the upgrade involves API surface changes.
|
||||||
|
- [Server pgvector Image Upgrade](https://docs.mem0.ai/migration/server-pgvector-upgrade) [OSS]: Use when upgrading the self-hosted server Docker image from ankane/pgvector to pgvector/pgvector.
|
||||||
- [Changelog](https://docs.mem0.ai/changelog/highlights) [Both]: Use when the user asks what shipped recently.
|
- [Changelog](https://docs.mem0.ai/changelog/highlights) [Both]: Use when the user asks what shipped recently.
|
||||||
|
|
||||||
## Open Source
|
## Open Source
|
||||||
|
|||||||
@@ -0,0 +1,158 @@
|
|||||||
|
---
|
||||||
|
title: "Server: Upgrading the pgvector Docker Image"
|
||||||
|
description: "Migrate your self-hosted Mem0 server from the archived ankane/pgvector image to the official pgvector/pgvector image."
|
||||||
|
icon: "arrow-right"
|
||||||
|
iconType: "solid"
|
||||||
|
---
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The self-hosted Mem0 server has upgraded its PostgreSQL Docker image:
|
||||||
|
|
||||||
|
| | Before | After |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Docker image | `ankane/pgvector:v0.5.1` | `pgvector/pgvector:pg17` |
|
||||||
|
| PostgreSQL | 15 | 17 |
|
||||||
|
| pgvector | 0.5.1 | 0.8.0 |
|
||||||
|
| Credentials | Hardcoded `postgres` / `postgres` | Set via `POSTGRES_USER` / `POSTGRES_PASSWORD` env vars |
|
||||||
|
|
||||||
|
<Warning>
|
||||||
|
The `ankane/pgvector` image is **archived and no longer maintained**. The new `pgvector/pgvector` image is the official distribution maintained by the pgvector project.
|
||||||
|
</Warning>
|
||||||
|
|
||||||
|
<Info>
|
||||||
|
**Should you migrate?**
|
||||||
|
- You are running the Mem0 server via `docker-compose.yaml` in the `server/` directory.
|
||||||
|
- You want to stay on a maintained, actively-patched PostgreSQL + pgvector image.
|
||||||
|
- You want pgvector 0.8.0 features (improved HNSW performance, parallel index builds).
|
||||||
|
</Info>
|
||||||
|
|
||||||
|
## Fresh Installs
|
||||||
|
|
||||||
|
No migration is needed. Copy the example env file, set your password, and start the stack:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd server
|
||||||
|
cp .env.example .env
|
||||||
|
# Edit .env — set POSTGRES_PASSWORD (required) and OPENAI_API_KEY at minimum
|
||||||
|
make up
|
||||||
|
```
|
||||||
|
|
||||||
|
## Migrating an Existing Install
|
||||||
|
|
||||||
|
PostgreSQL 17 cannot read data files created by PostgreSQL 15 directly. You need to export your data from the old container and import it into the new one.
|
||||||
|
|
||||||
|
### 1. Back Up Your Data
|
||||||
|
|
||||||
|
With the **old** stack still running:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd server
|
||||||
|
docker compose exec -T postgres pg_dumpall -U postgres > mem0_backup.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
Verify the dump is non-empty:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ls -lh mem0_backup.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
<Warning>
|
||||||
|
Do not skip this step. The next step permanently deletes your Postgres data volume.
|
||||||
|
</Warning>
|
||||||
|
|
||||||
|
### 2. Stop the Old Stack and Remove the Volume
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose down
|
||||||
|
docker compose down -v
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Update Your `.env`
|
||||||
|
|
||||||
|
Postgres credentials are no longer hardcoded in `docker-compose.yaml`. Add them to your `.env`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
POSTGRES_HOST=postgres
|
||||||
|
POSTGRES_PORT=5432
|
||||||
|
POSTGRES_DB=postgres
|
||||||
|
POSTGRES_USER=postgres
|
||||||
|
POSTGRES_PASSWORD=<your-password> # required — compose will refuse to start without it
|
||||||
|
POSTGRES_COLLECTION_NAME=memories
|
||||||
|
```
|
||||||
|
|
||||||
|
<Info>
|
||||||
|
`POSTGRES_PASSWORD` is **required** — `docker compose up` will refuse to start without it. If you previously relied on the hardcoded default, set `POSTGRES_PASSWORD=postgres`.
|
||||||
|
</Info>
|
||||||
|
|
||||||
|
### 4. Start Only Postgres
|
||||||
|
|
||||||
|
Start **only** the Postgres container first — do **not** start the mem0 API yet.
|
||||||
|
The API runs `alembic upgrade head` on startup, which creates empty tables that
|
||||||
|
would conflict with the restore.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up -d postgres
|
||||||
|
```
|
||||||
|
|
||||||
|
Wait for Postgres to become healthy:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose exec -T postgres pg_isready -q && echo "ready" || echo "not ready"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Restore Your Data
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose exec -T postgres psql -U postgres < mem0_backup.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
You may see notices like `role "postgres" already exists` — these are safe to ignore.
|
||||||
|
|
||||||
|
<Warning>
|
||||||
|
You must restore **before** starting the mem0 API container. The API runs
|
||||||
|
database migrations on startup which create empty tables — restoring after
|
||||||
|
that would fail with duplicate-key errors and lose your API keys and settings.
|
||||||
|
</Warning>
|
||||||
|
|
||||||
|
### 6. Start the API
|
||||||
|
|
||||||
|
Now start the mem0 API container. Alembic will detect the existing tables and
|
||||||
|
only apply any new migrations:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up -d mem0
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. Verify
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check service health
|
||||||
|
cd server && make health
|
||||||
|
|
||||||
|
# Confirm memories are accessible
|
||||||
|
curl -s http://localhost:8888/memories?user_id=<your-user-id> \
|
||||||
|
-H "X-API-Key: <your-api-key>"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Rollback
|
||||||
|
|
||||||
|
If something goes wrong, revert the image tag in `docker-compose.yaml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
postgres:
|
||||||
|
image: ankane/pgvector:v0.5.1
|
||||||
|
```
|
||||||
|
|
||||||
|
Then destroy the new volume, start the old image, and restore from your backup:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose down -v
|
||||||
|
docker compose up -d --build
|
||||||
|
docker compose exec -T postgres psql -U postgres < mem0_backup.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
## Need Help?
|
||||||
|
|
||||||
|
- Join our [Discord community](https://mem0.ai/discord) for real-time support
|
||||||
|
- Open an issue on [GitHub](https://github.com/mem0ai/mem0/issues)
|
||||||
+6
-5
@@ -4,12 +4,13 @@ OPENAI_API_KEY=
|
|||||||
# ANTHROPIC_API_KEY=
|
# ANTHROPIC_API_KEY=
|
||||||
# GOOGLE_API_KEY=
|
# GOOGLE_API_KEY=
|
||||||
|
|
||||||
POSTGRES_HOST=
|
# Postgres — POSTGRES_PASSWORD is required; docker-compose will not start without it.
|
||||||
POSTGRES_PORT=
|
POSTGRES_HOST=postgres
|
||||||
POSTGRES_DB=
|
POSTGRES_PORT=5432
|
||||||
POSTGRES_USER=
|
POSTGRES_DB=postgres
|
||||||
|
POSTGRES_USER=postgres
|
||||||
POSTGRES_PASSWORD=
|
POSTGRES_PASSWORD=
|
||||||
POSTGRES_COLLECTION_NAME=
|
POSTGRES_COLLECTION_NAME=memories
|
||||||
|
|
||||||
ADMIN_API_KEY=
|
ADMIN_API_KEY=
|
||||||
JWT_SECRET=
|
JWT_SECRET=
|
||||||
|
|||||||
@@ -2,8 +2,24 @@
|
|||||||
|
|
||||||
Mem0 ships a self-hosted FastAPI server plus a local dashboard. It is secure by default, supports dashboard login and API keys, and exposes OpenAPI docs at `/docs`.
|
Mem0 ships a self-hosted FastAPI server plus a local dashboard. It is secure by default, supports dashboard login and API keys, and exposes OpenAPI docs at `/docs`.
|
||||||
|
|
||||||
|
> **Upgrading?** The Postgres image changed from the archived `ankane/pgvector:v0.5.1`
|
||||||
|
> to the official `pgvector/pgvector:pg17`, and `POSTGRES_PASSWORD` is now a required
|
||||||
|
> env var. If you have an existing install, see
|
||||||
|
> [Migrating from ankane/pgvector to pgvector/pgvector](#migrating-from-ankanepgvector-to-pgvectorpgvector)
|
||||||
|
> before running `docker compose up`.
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
Copy the example env file and set a Postgres password (required):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd server
|
||||||
|
cp .env.example .env
|
||||||
|
# Edit .env — at minimum set POSTGRES_PASSWORD and OPENAI_API_KEY
|
||||||
|
```
|
||||||
|
|
||||||
### Agent-first
|
### Agent-first
|
||||||
|
|
||||||
Run one command; the terminal prints the admin email, password, and first API key.
|
Run one command; the terminal prints the admin email, password, and first API key.
|
||||||
@@ -121,6 +137,140 @@ The dashboard sets the following response headers on every path (see `server/das
|
|||||||
|
|
||||||
Together these prevent iframe embedding, sniffing of mislabelled MIME types, and cross-origin referrer leaks. Harden further behind your own reverse proxy if needed.
|
Together these prevent iframe embedding, sniffing of mislabelled MIME types, and cross-origin referrer leaks. Harden further behind your own reverse proxy if needed.
|
||||||
|
|
||||||
|
## Migrating from `ankane/pgvector` to `pgvector/pgvector`
|
||||||
|
|
||||||
|
The `ankane/pgvector` Docker image is archived and no longer maintained. This release
|
||||||
|
replaces it with the official `pgvector/pgvector:pg17` image (PostgreSQL 17, pgvector 0.8.0).
|
||||||
|
|
||||||
|
**What changed:**
|
||||||
|
|
||||||
|
| | Before | After |
|
||||||
|
|---|---|---|
|
||||||
|
| Docker image | `ankane/pgvector:v0.5.1` | `pgvector/pgvector:pg17` |
|
||||||
|
| PostgreSQL version | 15 | 17 |
|
||||||
|
| pgvector version | 0.5.1 | 0.8.0 |
|
||||||
|
| Credentials | Hardcoded `postgres`/`postgres` | Driven by `POSTGRES_USER` / `POSTGRES_PASSWORD` env vars |
|
||||||
|
|
||||||
|
### Fresh installs (no existing data)
|
||||||
|
|
||||||
|
No migration needed. Copy `.env.example` to `.env`, set `POSTGRES_PASSWORD`, and run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd server
|
||||||
|
make up
|
||||||
|
```
|
||||||
|
|
||||||
|
### Existing installs (preserving data)
|
||||||
|
|
||||||
|
PostgreSQL 17 cannot read data files written by PostgreSQL 15 directly.
|
||||||
|
You must export your data first, then import it into the new container.
|
||||||
|
|
||||||
|
**1. Export your data from the old container**
|
||||||
|
|
||||||
|
With the old stack still running:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd server
|
||||||
|
|
||||||
|
# Dump all databases (mem0 memories + mem0_app auth/config data)
|
||||||
|
docker compose exec -T postgres pg_dumpall -U postgres > mem0_backup.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
Verify the dump file is non-empty:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ls -lh mem0_backup.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
**2. Stop the old stack and remove the old volume**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Stop containers
|
||||||
|
docker compose down
|
||||||
|
|
||||||
|
# Remove the old Postgres data volume
|
||||||
|
docker compose down -v
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Warning:** `docker compose down -v` deletes the `postgres_db` volume permanently.
|
||||||
|
> Only run this after you have verified your backup.
|
||||||
|
|
||||||
|
**3. Update your `.env`**
|
||||||
|
|
||||||
|
The Postgres credentials are no longer hardcoded in `docker-compose.yaml`.
|
||||||
|
Add them to your `.env` file (or verify they match your old setup):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
POSTGRES_HOST=postgres
|
||||||
|
POSTGRES_PORT=5432
|
||||||
|
POSTGRES_DB=postgres
|
||||||
|
POSTGRES_USER=postgres
|
||||||
|
POSTGRES_PASSWORD=<your-password> # required — compose will refuse to start without it
|
||||||
|
POSTGRES_COLLECTION_NAME=memories
|
||||||
|
```
|
||||||
|
|
||||||
|
If you previously relied on the hardcoded defaults (`postgres`/`postgres`), set
|
||||||
|
`POSTGRES_PASSWORD=postgres` to keep the same credentials.
|
||||||
|
|
||||||
|
**4. Start only Postgres**
|
||||||
|
|
||||||
|
Start **only** the Postgres container first — do not start the mem0 API yet.
|
||||||
|
The API runs `alembic upgrade head` on startup, which creates empty tables that
|
||||||
|
would conflict with the restore.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up -d postgres
|
||||||
|
```
|
||||||
|
|
||||||
|
Wait for the Postgres healthcheck to pass:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose exec -T postgres pg_isready -q && echo "ready" || echo "not ready"
|
||||||
|
```
|
||||||
|
|
||||||
|
**5. Restore your data**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose exec -T postgres psql -U postgres < mem0_backup.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
You may see notices like `role "postgres" already exists` — these are harmless.
|
||||||
|
|
||||||
|
> **Important:** You must restore before starting the mem0 API container. The API
|
||||||
|
> runs database migrations on startup which create empty tables — restoring after
|
||||||
|
> that would fail with duplicate-key errors and lose your API keys and settings.
|
||||||
|
|
||||||
|
**6. Start the API**
|
||||||
|
|
||||||
|
Now start the mem0 API container. Alembic will detect the existing tables and
|
||||||
|
only apply any new migrations:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up -d mem0
|
||||||
|
```
|
||||||
|
|
||||||
|
**7. Verify**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check the API is healthy
|
||||||
|
make health
|
||||||
|
|
||||||
|
# Confirm your memories are present
|
||||||
|
curl -s http://localhost:8888/memories?user_id=<your-user-id> -H "X-API-Key: <your-api-key>"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Rollback
|
||||||
|
|
||||||
|
If you need to revert, restore the old image tag in `docker-compose.yaml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
postgres:
|
||||||
|
image: ankane/pgvector:v0.5.1
|
||||||
|
```
|
||||||
|
|
||||||
|
Then `docker compose down -v`, `docker compose up -d --build`, and restore from
|
||||||
|
`mem0_backup.sql` into the old container the same way.
|
||||||
|
|
||||||
## Reference
|
## Reference
|
||||||
|
|
||||||
Additional product and API documentation lives at [docs.mem0.ai](https://docs.mem0.ai/open-source/overview).
|
Additional product and API documentation lives at [docs.mem0.ai](https://docs.mem0.ai/open-source/overview).
|
||||||
|
|||||||
+32
-3
@@ -3,16 +3,15 @@ import secrets
|
|||||||
import uuid
|
import uuid
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
|
|
||||||
|
from db import get_db
|
||||||
from fastapi import Depends, HTTPException, Request
|
from fastapi import Depends, HTTPException, Request
|
||||||
from fastapi.security import APIKeyHeader, HTTPAuthorizationCredentials, HTTPBearer
|
from fastapi.security import APIKeyHeader, HTTPAuthorizationCredentials, HTTPBearer
|
||||||
from jose import JWTError, jwt
|
from jose import JWTError, jwt
|
||||||
|
from models import APIKey, RefreshTokenJti, User
|
||||||
from passlib.context import CryptContext
|
from passlib.context import CryptContext
|
||||||
from sqlalchemy import select, update
|
from sqlalchemy import select, update
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from db import get_db
|
|
||||||
from models import APIKey, RefreshTokenJti, User
|
|
||||||
|
|
||||||
JWT_SECRET = os.environ.get("JWT_SECRET", "")
|
JWT_SECRET = os.environ.get("JWT_SECRET", "")
|
||||||
JWT_ALGORITHM = "HS256"
|
JWT_ALGORITHM = "HS256"
|
||||||
ACCESS_TOKEN_EXPIRE_MINUTES = 30
|
ACCESS_TOKEN_EXPIRE_MINUTES = 30
|
||||||
@@ -184,3 +183,33 @@ async def require_auth(
|
|||||||
return default_user
|
return default_user
|
||||||
raise HTTPException(status_code=401, detail="Authentication required.")
|
raise HTTPException(status_code=401, detail="Authentication required.")
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
_BOOTSTRAP_ADMIN = User(
|
||||||
|
id=uuid.UUID(int=0), name="admin_api_key", email="", password_hash="", role="admin", created_at=datetime.min.replace(tzinfo=timezone.utc),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def require_admin(
|
||||||
|
request: Request,
|
||||||
|
user: User | None = Depends(verify_auth),
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
) -> User:
|
||||||
|
"""Like require_auth but also enforces admin role.
|
||||||
|
|
||||||
|
ADMIN_API_KEY and AUTH_DISABLED callers are treated as admin even when
|
||||||
|
the users table is empty (fresh-deploy bootstrap).
|
||||||
|
"""
|
||||||
|
auth_type = getattr(request.state, "auth_type", "none")
|
||||||
|
if user is None:
|
||||||
|
if auth_type in {"admin_api_key", "disabled"}:
|
||||||
|
default_user = _get_default_user(db)
|
||||||
|
if default_user is not None:
|
||||||
|
if default_user.role != "admin":
|
||||||
|
raise HTTPException(status_code=403, detail="Admin role required.")
|
||||||
|
return default_user
|
||||||
|
return _BOOTSTRAP_ADMIN
|
||||||
|
raise HTTPException(status_code=401, detail="Authentication required.")
|
||||||
|
if user.role != "admin":
|
||||||
|
raise HTTPException(status_code=403, detail="Admin role required.")
|
||||||
|
return user
|
||||||
|
|||||||
@@ -30,16 +30,16 @@ services:
|
|||||||
- MEM0_TELEMETRY=${MEM0_TELEMETRY:-true}
|
- MEM0_TELEMETRY=${MEM0_TELEMETRY:-true}
|
||||||
|
|
||||||
postgres:
|
postgres:
|
||||||
image: ankane/pgvector:v0.5.1
|
image: pgvector/pgvector:pg17
|
||||||
restart: on-failure
|
restart: on-failure
|
||||||
shm_size: "128mb"
|
shm_size: "128mb"
|
||||||
networks:
|
networks:
|
||||||
- mem0_network
|
- mem0_network
|
||||||
environment:
|
environment:
|
||||||
- POSTGRES_USER=postgres
|
- POSTGRES_USER=${POSTGRES_USER:-postgres}
|
||||||
- POSTGRES_PASSWORD=postgres
|
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD in .env}
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "pg_isready", "-q", "-d", "postgres", "-U", "postgres"]
|
test: ["CMD-SHELL", "pg_isready -q -d postgres -U ${POSTGRES_USER:-postgres}"]
|
||||||
interval: 5s
|
interval: 5s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
|
|||||||
+14
-8
@@ -5,7 +5,7 @@ import time
|
|||||||
from typing import Any, Dict, List, Optional
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
import telemetry
|
import telemetry
|
||||||
from auth import ADMIN_API_KEY, AUTH_DISABLED, JWT_SECRET, verify_auth
|
from auth import ADMIN_API_KEY, AUTH_DISABLED, JWT_SECRET, require_admin, verify_auth
|
||||||
from db import SessionLocal
|
from db import SessionLocal
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
from errors import (
|
from errors import (
|
||||||
@@ -316,8 +316,8 @@ def list_bundled_providers(_auth=Depends(verify_auth)):
|
|||||||
|
|
||||||
|
|
||||||
@app.post("/configure", summary="Configure Mem0")
|
@app.post("/configure", summary="Configure Mem0")
|
||||||
def set_config(config: Dict[str, Any], _auth=Depends(verify_auth)):
|
def set_config(config: Dict[str, Any], _auth=Depends(require_admin)):
|
||||||
"""Set memory configuration."""
|
"""Set memory configuration. Requires admin role."""
|
||||||
_validate_bundled_providers(config)
|
_validate_bundled_providers(config)
|
||||||
update_config(config)
|
update_config(config)
|
||||||
return {"message": "Configuration set successfully"}
|
return {"message": "Configuration set successfully"}
|
||||||
@@ -391,19 +391,25 @@ def _list_all_memories(limit: int = ALL_MEMORIES_LIMIT) -> Dict[str, Any]:
|
|||||||
|
|
||||||
@app.get("/memories", summary="Get memories")
|
@app.get("/memories", summary="Get memories")
|
||||||
def get_all_memories(
|
def get_all_memories(
|
||||||
|
request: Request,
|
||||||
user_id: Optional[str] = None,
|
user_id: Optional[str] = None,
|
||||||
run_id: Optional[str] = None,
|
run_id: Optional[str] = None,
|
||||||
agent_id: Optional[str] = None,
|
agent_id: Optional[str] = None,
|
||||||
_auth=Depends(verify_auth),
|
_auth=Depends(verify_auth),
|
||||||
):
|
):
|
||||||
"""Retrieve stored memories. Lists all memories when no identifier is provided."""
|
"""Retrieve stored memories. Lists all memories when no identifier is provided (admin only)."""
|
||||||
try:
|
try:
|
||||||
if not any([user_id, run_id, agent_id]):
|
if not any([user_id, run_id, agent_id]):
|
||||||
|
auth_type = getattr(request.state, "auth_type", "none")
|
||||||
|
if _auth is not None and _auth.role != "admin" and auth_type not in {"admin_api_key", "disabled"}:
|
||||||
|
raise HTTPException(status_code=403, detail="Admin role required to list all memories.")
|
||||||
return _list_all_memories()
|
return _list_all_memories()
|
||||||
filters = {
|
filters = {
|
||||||
k: v for k, v in {"user_id": user_id, "run_id": run_id, "agent_id": agent_id}.items() if v is not None
|
k: v for k, v in {"user_id": user_id, "run_id": run_id, "agent_id": agent_id}.items() if v is not None
|
||||||
}
|
}
|
||||||
return get_memory_instance().get_all(filters=filters)
|
return get_memory_instance().get_all(filters=filters)
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
except Exception:
|
except Exception:
|
||||||
raise upstream_error()
|
raise upstream_error()
|
||||||
|
|
||||||
@@ -479,9 +485,9 @@ def delete_all_memories(
|
|||||||
user_id: Optional[str] = None,
|
user_id: Optional[str] = None,
|
||||||
run_id: Optional[str] = None,
|
run_id: Optional[str] = None,
|
||||||
agent_id: Optional[str] = None,
|
agent_id: Optional[str] = None,
|
||||||
_auth=Depends(verify_auth),
|
_auth=Depends(require_admin),
|
||||||
):
|
):
|
||||||
"""Delete all memories for a given identifier."""
|
"""Delete all memories for a given identifier. Requires admin role."""
|
||||||
if not any([user_id, run_id, agent_id]):
|
if not any([user_id, run_id, agent_id]):
|
||||||
raise HTTPException(status_code=400, detail="At least one identifier is required.")
|
raise HTTPException(status_code=400, detail="At least one identifier is required.")
|
||||||
try:
|
try:
|
||||||
@@ -495,8 +501,8 @@ def delete_all_memories(
|
|||||||
|
|
||||||
|
|
||||||
@app.post("/reset", summary="Reset all memories")
|
@app.post("/reset", summary="Reset all memories")
|
||||||
def reset_memory(_auth=Depends(verify_auth)):
|
def reset_memory(_auth=Depends(require_admin)):
|
||||||
"""Completely reset stored memories."""
|
"""Completely reset stored memories. Requires admin role."""
|
||||||
try:
|
try:
|
||||||
get_memory_instance().reset()
|
get_memory_instance().reset()
|
||||||
return {"message": "All memories reset"}
|
return {"message": "All memories reset"}
|
||||||
|
|||||||
@@ -2,11 +2,10 @@ from collections import defaultdict
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Any, Literal, Optional
|
from typing import Any, Literal, Optional
|
||||||
|
|
||||||
|
from auth import require_admin, verify_auth
|
||||||
|
from errors import upstream_error
|
||||||
from fastapi import APIRouter, Depends
|
from fastapi import APIRouter, Depends
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from auth import verify_auth
|
|
||||||
from errors import upstream_error
|
|
||||||
from schemas import MessageResponse
|
from schemas import MessageResponse
|
||||||
from server_state import get_memory_instance
|
from server_state import get_memory_instance
|
||||||
|
|
||||||
@@ -69,7 +68,7 @@ def list_entities(_auth=Depends(verify_auth)):
|
|||||||
|
|
||||||
|
|
||||||
@router.delete("/{entity_type}/{entity_id}", response_model=MessageResponse)
|
@router.delete("/{entity_type}/{entity_id}", response_model=MessageResponse)
|
||||||
def delete_entity(entity_type: EntityType, entity_id: str, _auth=Depends(verify_auth)):
|
def delete_entity(entity_type: EntityType, entity_id: str, _auth=Depends(require_admin)):
|
||||||
try:
|
try:
|
||||||
get_memory_instance().delete_all(**{TYPE_TO_FIELD[entity_type]: entity_id})
|
get_memory_instance().delete_all(**{TYPE_TO_FIELD[entity_type]: entity_id})
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
from datetime import datetime
|
|
||||||
import uuid
|
import uuid
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from auth import require_admin
|
||||||
|
from db import get_db
|
||||||
from fastapi import APIRouter, Depends, Query
|
from fastapi import APIRouter, Depends, Query
|
||||||
|
from models import RequestLog
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from auth import require_auth
|
|
||||||
from db import get_db
|
|
||||||
from models import RequestLog, User
|
|
||||||
|
|
||||||
router = APIRouter(prefix="/requests", tags=["requests"])
|
router = APIRouter(prefix="/requests", tags=["requests"])
|
||||||
|
|
||||||
|
|
||||||
@@ -30,7 +29,7 @@ API_KEY_AUTH_TYPES = ("api_key", "admin_api_key")
|
|||||||
|
|
||||||
@router.get("", response_model=list[RequestLogItem])
|
@router.get("", response_model=list[RequestLogItem])
|
||||||
def list_requests(
|
def list_requests(
|
||||||
user: User = Depends(require_auth),
|
_auth=Depends(require_admin),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
limit: int = Query(default=50, ge=1, le=200),
|
limit: int = Query(default=50, ge=1, le=200),
|
||||||
):
|
):
|
||||||
|
|||||||
Reference in New Issue
Block a user