docs: replace daedalus-service basic auth with per-user DRF tokens
This commit is contained in:
@@ -5,9 +5,12 @@ A "workspace" in Mnemosyne is a Library scoped to a Daedalus workspace UUID.
|
||||
It uses the same Library node as a global library; the difference is that
|
||||
`workspace_id` is set, and search must filter on it.
|
||||
|
||||
These endpoints are called by the Daedalus backend (HTTP Basic auth as
|
||||
the `daedalus-service` user). Daedalus owns the workspace_id; Mnemosyne
|
||||
just persists what Daedalus tells it.
|
||||
These endpoints are called by the Daedalus backend authenticated as the
|
||||
Mnemosyne user the workspace belongs to (per-user DRF token). The
|
||||
workspace's owning user is recorded on the Library node as
|
||||
``owner_username``; every read and mutation is scoped to that user.
|
||||
Non-owners receive 404 so a workspace's existence isn't disclosed
|
||||
across users.
|
||||
"""
|
||||
|
||||
import logging
|
||||
@@ -72,6 +75,17 @@ def workspace_create(request):
|
||||
existing = None
|
||||
|
||||
if existing is not None:
|
||||
if existing.owner_username and existing.owner_username != request.user.username:
|
||||
# Same workspace_id under a different owner. Don't leak the
|
||||
# collision shape; surface a generic conflict.
|
||||
logger.warning(
|
||||
"workspace_create owner_conflict workspace_id=%s caller=%s",
|
||||
data["workspace_id"], request.user.username,
|
||||
)
|
||||
return Response(
|
||||
{"detail": "Workspace id is already in use."},
|
||||
status=status.HTTP_409_CONFLICT,
|
||||
)
|
||||
if existing.library_type != data["library_type"]:
|
||||
return Response(
|
||||
{
|
||||
@@ -98,6 +112,7 @@ def workspace_create(request):
|
||||
library_type=data["library_type"],
|
||||
description=data.get("description", ""),
|
||||
workspace_id=data["workspace_id"],
|
||||
owner_username=request.user.username,
|
||||
chunking_config=defaults["chunking_config"],
|
||||
embedding_instruction=defaults["embedding_instruction"],
|
||||
reranker_instruction=defaults["reranker_instruction"],
|
||||
@@ -127,21 +142,26 @@ def workspace_detail_or_delete(request, workspace_id):
|
||||
"""
|
||||
from library.models import Library
|
||||
|
||||
try:
|
||||
lib = Library.nodes.get(workspace_id=workspace_id)
|
||||
except Library.DoesNotExist:
|
||||
lib = None
|
||||
|
||||
# Cross-user reads/writes look like "not found" — don't disclose
|
||||
# existence across users.
|
||||
if lib is not None and lib.owner_username != request.user.username:
|
||||
lib = None
|
||||
|
||||
if request.method == "GET":
|
||||
try:
|
||||
lib = Library.nodes.get(workspace_id=workspace_id)
|
||||
except Library.DoesNotExist:
|
||||
if lib is None:
|
||||
return Response(
|
||||
{"detail": "Workspace not found."},
|
||||
status=status.HTTP_404_NOT_FOUND,
|
||||
)
|
||||
|
||||
return Response(WorkspaceStatusSerializer(_serialize_workspace(lib)).data)
|
||||
|
||||
# DELETE — idempotent: a missing workspace returns 204.
|
||||
try:
|
||||
lib = Library.nodes.get(workspace_id=workspace_id)
|
||||
except Library.DoesNotExist:
|
||||
# DELETE — idempotent: a missing (or unowned) workspace returns 204.
|
||||
if lib is None:
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
library_uid = lib.uid
|
||||
|
||||
Reference in New Issue
Block a user