feat(multi-user): add multi-user feature flag, owner_id model field, and user-scoped queries

Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-03-05 11:26:24 +00:00
parent af4cd966e9
commit 71f437e43a
9 changed files with 196 additions and 14 deletions
+22
View File
@@ -78,6 +78,28 @@ SETTING_METADATA = {
"required": False,
"restart_required": True,
},
"multi_user_enabled": {
"category": "Authentication",
"description": (
"Enable multi-user mode with individual document spaces per user. "
"Each user sees only their own documents, uploads, and search results. "
"Requires auth_enabled=True."
),
"type": "boolean",
"sensitive": False,
"required": False,
"restart_required": True,
},
"default_daily_upload_limit": {
"category": "Authentication",
"description": (
"Default maximum document uploads allowed per user per day in multi-user mode. Set to 0 for unlimited."
),
"type": "integer",
"sensitive": False,
"required": False,
"restart_required": False,
},
"session_secret": {
"category": "Authentication",
"description": "Secret key for session encryption (min 32 characters)",
+72
View File
@@ -0,0 +1,72 @@
"""
User-scoping utilities for multi-user document isolation.
When ``multi_user_enabled`` is ``True`` in settings, every document query
is filtered by the authenticated user's identifier so that each user sees
only their own documents. When the flag is ``False`` (default), all
documents are visible to all users (single-user / shared mode).
"""
import logging
from fastapi import Request
from sqlalchemy.orm import Query
from app.models import FileRecord
logger = logging.getLogger(__name__)
def get_current_owner_id(request: Request) -> str | None:
"""Extract the owner identifier for the current authenticated user.
The owner ID is derived from the user's session data. It uses the
``sub`` claim (OAuth subject) when available, falling back to
``preferred_username`` or ``email``. Returns ``None`` when no user
is authenticated.
Args:
request: The current FastAPI request with session data.
Returns:
A stable string identifier for the user, or ``None``.
"""
user = request.session.get("user")
if not user or not isinstance(user, dict):
return None
# Prefer 'sub' (OAuth subject), then 'preferred_username', then 'email', then 'id'
return user.get("sub") or user.get("preferred_username") or user.get("email") or user.get("id")
def apply_owner_filter(query: Query, request: Request) -> Query:
"""Conditionally filter a ``FileRecord`` query by the current user.
When multi-user mode is enabled, only files whose ``owner_id``
matches the authenticated user are returned. Admin users bypass
the filter and see all documents.
When multi-user mode is disabled the query is returned unchanged.
Args:
query: A SQLAlchemy query selecting ``FileRecord`` rows.
request: The current FastAPI request (for session inspection).
Returns:
The (possibly filtered) query.
"""
from app.config import settings
if not settings.multi_user_enabled:
return query
user = request.session.get("user")
if isinstance(user, dict) and user.get("is_admin"):
# Admins see all documents in multi-user mode
return query
owner_id = get_current_owner_id(request)
if owner_id is None:
# No authenticated user — return empty result set
return query.filter(FileRecord.id < 0)
return query.filter(FileRecord.owner_id == owner_id)