Skip to content

🔐 Add user-level access control via OIDC#153

Open
StephanMeijer wants to merge 1 commit into
feat/demo-v1from
feat/bring-everything-up
Open

🔐 Add user-level access control via OIDC#153
StephanMeijer wants to merge 1 commit into
feat/demo-v1from
feat/bring-everything-up

Conversation

@StephanMeijer
Copy link
Copy Markdown
Collaborator

Summary

  • Add SearchAuthMiddleware supporting both service tokens and OIDC tokens
  • Extract user_sub from authenticated OIDC user for access control
  • Services build where clause for user-level filtering (reach != restricted OR user_sub IN users OR id IN visited)
  • Add comprehensive documentation in docs/SEARCH_QUERY_SCHEMA.md

Changes

  • core/middleware.py: New SearchAuthMiddleware with dual auth support
  • core/services/search.py: combine_with_system_scope() for where clause merging
  • core/handlers.py: Extract user from request.state["user"].sub
  • find/settings.py: Add dummy DATABASES for pytest-django compatibility
  • Regenerated VCR cassettes for updated query format

Testing

  • All 107 tests pass
  • Pre-existing lint errors in core/apps.py and core/bolt_auth.py (not from this PR)

@StephanMeijer StephanMeijer force-pushed the feat/bring-everything-up branch 3 times, most recently from 26cac48 to e7ac7ce Compare May 16, 2026 12:25
@StephanMeijer StephanMeijer requested a review from Copilot May 16, 2026 12:29
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces user-level access control for the search endpoint by supporting both service-token and OIDC authentication, extracting the authenticated user subject for filtering, and documenting the query DSL and access-control model.

Changes:

  • Added Bolt route-level auth middleware to support service token auth, OIDC auth, and a combined “search auth” mode.
  • Updated search query construction (explicit language fields + updated trigram settings) and adjusted handlers to build responses from localized _source fields.
  • Added/updated operational config and documentation (django-bolt integration, pytest-django DB config, and expanded query schema docs), plus regenerated VCR cassettes.

Reviewed changes

Copilot reviewed 18 out of 19 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/backend/uv.lock Adds uvicorn[standard] and related transitive deps to the lockfile (dev extra).
src/backend/pyproject.toml Adds uvicorn[standard] to dev dependencies.
src/backend/find/settings.py Registers django_bolt, configures BOLT_API, adds a DB config, and adjusts trigram defaults.
src/backend/find/asgi.py Switches ASGI entrypoint import to core.api.
src/backend/demo/tests/cassettes/test_commands_create_demo.yaml Regenerates demo cassette to match updated indexing/query behavior.
src/backend/core/tests/conftest.py Updates test fixtures to patch new middleware-based auth paths.
src/backend/core/tests/cassettes/TestSearchDocumentsHandler.test_search_with_where_clause_returns_200.yaml Updates cassette to match new query format and _source selection.
src/backend/core/tests/cassettes/TestSearchDocumentsHandler.test_search_with_sort_and_limit_returns_200.yaml Updates cassette to match new query format and _source selection.
src/backend/core/tests/cassettes/TestSearchDocumentsHandler.test_search_simple_query_returns_200.yaml Updates cassette to match new query format and _source selection.
src/backend/core/services/search.py Adds logging and revises full-text query field selection (explicit language fields + trigram query structure).
src/backend/core/middleware.py Introduces ServiceAuthMiddleware, OIDCAuthMiddleware, and SearchAuthMiddleware.
src/backend/core/handlers.py Migrates endpoints to middleware-driven auth and updates result mapping for localized fields.
src/backend/core/enums.py Adjusts _source field selection to use title.* / content.*.
src/backend/core/bolt_auth.py Removes service-token auth backend/guards and narrows module scope to OIDC auth.
src/backend/core/authentication.py Reworks OIDC resource-server backend init to avoid requiring Django auth user model.
src/backend/core/api.py Adds a django-bolt autodiscovery entrypoint that re-exports handlers.api.
docs/SEARCH_QUERY_SCHEMA.md Adds comprehensive documentation for the search query schema, operators, fields, and access control.
docs/architecture.md Documents system-scope vs user-scope filtering and the dual-auth search flow.
compose.yml Runs the backend via manage.py runbolt in docker-compose for development.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/backend/core/services/search.py Outdated
Comment thread src/backend/core/services/search.py Outdated
Comment thread src/backend/find/settings.py Outdated
Comment thread src/backend/core/handlers.py Outdated
Comment thread src/backend/core/api.py Outdated
Comment thread compose.yml
@StephanMeijer StephanMeijer force-pushed the feat/bring-everything-up branch from e7ac7ce to fa603bc Compare May 16, 2026 13:13
Signed-off-by: Stephan Meijer <me@stephanmeijer.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 19 out of 20 changed files in this pull request and generated 6 comments.

Comment on lines +74 to +75
must=[multi_match_standard],
should=[multi_match_trigram],
Comment on lines 200 to 208
# Trigrams search settings
TRIGRAMS_BOOST = values.Value(
default=0.25, environ_name="TRIGRAMS_BOOST", environ_prefix=None
default=0.01, environ_name="TRIGRAMS_BOOST", environ_prefix=None
)
TRIGRAMS_MINIMUM_SHOULD_MATCH = values.Value(
default="75%", environ_name="TRIGRAMS_MINIMUM_SHOULD_MATCH", environ_prefix=None
default="100%",
environ_name="TRIGRAMS_MINIMUM_SHOULD_MATCH",
environ_prefix=None,
)
]

# Django-Bolt API autodiscovery
BOLT_API = ["core.handlers:api"]
Comment on lines +245 to +251
These fields can be used in `where` clauses by API clients:

| Field | Type | Description |
|-------|------|-------------|
| `id` | `string` | Document UUID (mapped to `_id` in OpenSearch) |
| `title` | `string` | Document title |
| `content` | `string` | Document content |
Comment on lines +261 to +270
### System Fields (Server-Side Only)

These fields are used internally for access control:

| Field | Type | Description |
|-------|------|-------------|
| `is_active` | `bool` | Document is active (not deleted) |
| `users` | `list[string]` | User subs with explicit access |
| `groups` | `list[string]` | Group slugs with access |
| `service` | `string` | Service that owns the document |
Comment thread Dockerfile
Comment on lines 101 to +108
# ---- Production image ----
FROM core AS backend-production

# Gunicorn
RUN mkdir -p /usr/local/etc/gunicorn
COPY docker/files/usr/local/etc/gunicorn/find.py /usr/local/etc/gunicorn/find.py

# Un-privileged user running the application
ARG DOCKER_USER
USER ${DOCKER_USER}

# The default command runs gunicorn WSGI server in find's main module
CMD ["gunicorn", "-c", "/usr/local/etc/gunicorn/find.py", "find.wsgi:application"]
CMD ["python", "manage.py", "runbolt", "--host", "0.0.0.0", "--port", "8000"]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants