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:
@@ -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)
|
||||
Reference in New Issue
Block a user