claude-memory-mcp/src/claude_memory/api/permissions.py
Viktor Barzin f45e8ce2b3
add multi-user memory sharing with r/w permissions
- New migration 004: memory_shares and tag_shares tables with indexes
- Share individual memories or entire tags with other users (read/write)
- Tag shares are live rules: future memories with shared tags auto-visible
- Recall query merges own + shared memories via UNION, returns shared_by field
- Owner-only delete enforcement (403 for non-owners, even with write access)
- PUT /api/memories/{id} update endpoint with permission checks
- 5 new MCP SSE tools: memory_share, memory_unshare, memory_share_tag,
  memory_unshare_tag, memory_update
- Permission helper checks ownership, individual shares, and tag shares
2026-03-22 15:34:01 +02:00

60 lines
1.8 KiB
Python

"""Permission checking for shared memories."""
import asyncpg
async def check_memory_permission(
conn: asyncpg.Connection, memory_id: int, user_id: str, required: str
) -> tuple[bool, str | None]:
"""Check if user_id has the required permission on memory_id.
Returns (allowed, owner_id).
- Owner always has full access.
- Shared users checked via memory_shares and tag_shares.
- required: "read" or "write". "read" is satisfied by either permission.
"""
row = await conn.fetchrow(
"SELECT user_id FROM memories WHERE id = $1 AND deleted_at IS NULL",
memory_id,
)
if not row:
return False, None
owner_id = row["user_id"]
# Owner always has access
if owner_id == user_id:
return True, owner_id
# Check individual memory share
share = await conn.fetchrow(
"SELECT permission FROM memory_shares WHERE memory_id = $1 AND shared_with = $2",
memory_id, user_id,
)
if share:
if required == "read" or share["permission"] == "write":
return True, owner_id
return False, owner_id
# Check tag-based shares
tag_share = await conn.fetchrow(
"""
SELECT ts.permission
FROM tag_shares ts
JOIN memories m ON m.user_id = ts.owner_id
WHERE m.id = $1 AND ts.shared_with = $2
AND EXISTS (
SELECT 1 FROM unnest(string_to_array(m.tags, ',')) t
WHERE trim(t) = ts.tag
)
ORDER BY CASE WHEN ts.permission = 'write' THEN 0 ELSE 1 END
LIMIT 1
""",
memory_id, user_id,
)
if tag_share:
if required == "read" or tag_share["permission"] == "write":
return True, owner_id
return False, owner_id
return False, owner_id