Fix: bind memory message user_id to authenticated user for JWT auth (#14745)

### Related issues

Closes #14744

### What problem does this PR solve?

The Memory REST endpoint `POST /api/v1/messages` previously persisted
whatever `user_id` the client sent in the JSON body. Memory rows were
therefore attributed to an arbitrary string, even when the caller
authenticated as a normal workspace user via JWT (browser/session-style
bearer token decoded into an access token). That broke attribution and
audit semantics for shared memories (team visibility): any authorized
writer could spoof another subject id.

The Python SDK already sends an optional `user_id` for integrations
using **API keys** (`APIToken`) to tag an external subject distinct from
the tenant owner user.

### Solution

- Record **`g.auth_via_api_token`** in `_load_user`
(`api/apps/__init__.py`): set `True` only when authentication resolves
via `APIToken`, otherwise `False` after JWT-based login succeeds.
- In **`POST /messages`** (`memory_api.add_message`): if the request was
authenticated with an API key, keep accepting optional `user_id` from
the body (default empty string). For JWT-authenticated users, **always**
set stored `user_id` to **`current_user.id`** and ignore the client
field.
- Guard reads of `g` with **`RuntimeError`** handling so isolated
imports or tests without a Quart application context do not fail when
resolving `user_id`.
- Document on **`RAGFlow.add_message`** that `user_id` is only
meaningful for API-key authentication.

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
- [ ] New Feature (non-breaking change which adds functionality)
- [ ] Documentation Update
- [ ] Refactoring
- [ ] Performance Improvement
- [ ] Other (please describe):

### Testing

- `python -m py_compile` on modified modules (`api/apps/__init__.py`,
`api/apps/restful_apis/memory_api.py`).
- Recommended: run web/SDK memory message tests (`test_add_message`,
`test_message_routes_unit`) against a full environment with `quart` and
configured services.

### Notes for reviewers

- Behavior change **only** for callers using JWT-style authorization on
`POST /messages`; API-key callers keep prior optional `user_id`
semantics.

Co-authored-by: jony376 <jony376@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
jony376
2026-05-10 22:26:05 -07:00
committed by GitHub
parent 16354f4e14
commit 46897d6fa4
3 changed files with 15 additions and 2 deletions

View File

@@ -130,6 +130,7 @@ def _load_user():
jwt = Serializer(secret_key=settings.get_secret_key())
authorization = request.headers.get("Authorization")
g.user = None
g.auth_via_api_token = False
if not authorization:
return _load_user_from_session()
@@ -175,6 +176,7 @@ def _load_user():
if not user[0].access_token or not user[0].access_token.strip():
logging.warning(f"User {user[0].email} has empty access_token in database")
return _load_user_from_session()
g.auth_via_api_token = True
g.user = user[0]
return user[0]
logging.warning(f"load_user: No user found for tenant_id={objs[0].tenant_id} from APIToken")

View File

@@ -17,7 +17,7 @@ import logging
import os
import time
from quart import request
from quart import request, g
from common.constants import LLMType, RetCode
from common.exceptions import ArgumentException, NotFoundException
from api.apps import login_required, current_user
@@ -188,8 +188,18 @@ async def add_message():
req = await get_request_json()
memory_ids = req["memory_id"]
# JWT / session users cannot spoof attribution; API-key callers may supply an external subject id.
try:
trust_client_subject = bool(getattr(g, "auth_via_api_token", False))
except RuntimeError:
trust_client_subject = False
if trust_client_subject:
effective_user_id = req.get("user_id", "")
else:
effective_user_id = current_user.id
message_dict = {
"user_id": req.get("user_id"),
"user_id": effective_user_id,
"agent_id": req["agent_id"],
"session_id": req["session_id"],
"user_input": req["user_input"],

View File

@@ -334,6 +334,7 @@ class RAGFlow:
raise Exception(res["message"])
def add_message(self, memory_id: list[str], agent_id: str, session_id: str, user_input: str, agent_response: str, user_id: str = "") -> str:
"""Append messages to memories; ``user_id`` is forwarded only for API-key auth (external subject)."""
payload = {
"memory_id": memory_id,
"agent_id": agent_id,