Skip to content

feat(api): add Server-Sent Events (SSE) infrastructure#11556

Open
puchy22 wants to merge 12 commits into
masterfrom
PROWLER-1414-add-sse-capabilities-to-the-backend
Open

feat(api): add Server-Sent Events (SSE) infrastructure#11556
puchy22 wants to merge 12 commits into
masterfrom
PROWLER-1414-add-sse-capabilities-to-the-backend

Conversation

@puchy22

@puchy22 puchy22 commented Jun 11, 2026

Copy link
Copy Markdown
Member

Context

The API needs a reusable foundation for Server-Sent Events (SSE) so endpoints can push a one-way stream of events to clients over a single long-lived HTTP connection (live progress, token-by-token LLM output, cross-client sync). This work lands the shared SSE infrastructure and developer documentation so feature endpoints can adopt it. No existing endpoint is converted to SSE in this PR — the goal is to provide the basis.

Description

Adds the platform SSE layer that wires django-eventstream into the API, plus the runtime and auth changes needed to serve streams:

  • Dependencies: add django-eventstream and bump gunicorn to the release that ships the native asgi worker.
  • ASGI runtime: run gunicorn with the native asgi worker against config.asgi so SSE streams are parked on the event loop instead of holding a sync worker per connection (sync CRUD views keep running in the thread-sensitive executor). preload_app is disabled under DEBUG so dev reload works; dev and prod entrypoints point at the ASGI app.
  • SSEAuthentication: extends the standard JWT/API-key stack with an ?access_token=<jwt> query-parameter fallback (RFC 6750 §2.3), since browser EventSource cannot set the Authorization header. The query path accepts a JWT only; API keys remain header-only.
  • SSE infrastructure (api/sse/): BaseSSEViewSet (subclass + implement get_channels, reuses the regular DRF auth/RBAC/tenant-transaction stack), SSEChannelManager (tenant gate via the tenant id embedded in the channel name), and make_channel_name/tenant_id_from_channel (single source of truth for the <prefix>:<tenant_id>:<resource_id> channel format). Valkey Pub/Sub backend configured on a dedicated DB.
  • Docs: a Developer Guide page covering when to use SSE, the architecture/ASGI transport, a step-by-step example for adding an endpoint, the resource.verb event-naming convention, auth, the tenant-isolation model, and reconnect/state recovery.

New dependencies: django-eventstream==5.3.3, gunicorn==26.0.0 (was 23.0.0).

Steps to review

  1. Read the new developer guide (docs/developer-guide/server-sent-events.mdx) for the intended architecture and the worked example.
  2. Review the SSE package (api/src/backend/api/sse/): the base viewset, the channel manager's two-layer authorization (resource lookup in get_channels + tenant gate in can_read_channel), and the channel-name helpers.
  3. Review SSEAuthentication in api/src/backend/api/authentication.py and the header-wins / query-fallback precedence.
  4. Review the ASGI runtime changes: config/guniconf.py, docker-entrypoint.sh, and the settings wiring in config/django/base.py + config/settings/eventstream.py.

Checklist

Community Checklist
  • This feature/issue is listed in here or roadmap.prowler.com
  • Is it assigned to me, if not, request it via the issue/feature in here or Prowler Community Slack

SDK/CLI

  • Are there new checks included in this PR? No

API

  • All issue/task requirements work as expected on the API
  • Endpoint response output (if applicable) — N/A, no endpoint added; infrastructure only
  • EXPLAIN ANALYZE output for new/modified queries or indexes (if applicable) — N/A
  • Performance test results (if applicable) — N/A
  • Any other relevant evidence of the implementation (if applicable)
  • Verify if API specs need to be regenerated. — No new endpoints; specs unchanged
  • Check if version updates are required (e.g., specs, uv, etc.). — uv.lock updated
  • Ensure new entries are added to CHANGELOG.md, if applicable. — added under [1.32.0] (Prowler UNRELEASED)

License

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

Summary by CodeRabbit

  • New Features

    • Introduced Server-Sent Events (SSE) infrastructure for tenant-aware real-time streams and EventSource-friendly auth fallback (query token).
    • Switched dev and prod to ASGI/Gunicorn for long-lived streaming endpoints.
  • Chores

    • Upgraded Gunicorn to 26.0.0 and added django-eventstream dependency.
  • Documentation

    • Added developer guide and changelog entry describing SSE usage and operational guidance.

@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b410f41d-cd62-449e-80cb-7bf2aba47bd6

📥 Commits

Reviewing files that changed from the base of the PR and between 7820d68 and 247bb4e.

📒 Files selected for processing (4)
  • api/src/backend/api/authentication.py
  • api/src/backend/api/sse/channelmanager.py
  • api/src/backend/api/tests/test_authentication.py
  • api/src/backend/api/tests/test_sse.py

📝 Walkthrough

Walkthrough

Adds Server-Sent Events (SSE) platform: ASGI/Gunicorn runtime changes, django-eventstream dependency and settings, SSE authentication fallback, channel-name utilities, BaseSSEViewSet and tenant-aware SSEChannelManager, tests, developer guide, and changelog entry.

Changes

Server-Sent Events Platform

Layer / File(s) Summary
Runtime dependencies and ASGI server configuration
api/pyproject.toml, api/docker-entrypoint.sh, api/src/backend/config/guniconf.py, api/src/backend/config/django/base.py
Django-eventstream dependency added and gunicorn upgraded to v26 for ASGI. Container entrypoint now starts Gunicorn ASGI in both dev and prod modes. Gunicorn configured with ASGI worker class and Django ASGI_APPLICATION setting points to config.asgi:application for long-lived streaming support.
Channel naming and tenant extraction utilities
api/src/backend/api/sse/utils.py, api/src/backend/api/tests/test_sse.py, api/src/backend/api/sse/__init__.py
Colon-separated channel naming format (prefix:tenant_id:resource_id) with helpers make_channel_name() and tenant_id_from_channel(). Tests verify round-tripping, string tenant handling, hyphenated prefixes, and graceful handling of malformed channels.
SSE-aware authentication for header-less clients
api/src/backend/api/authentication.py, api/src/backend/api/tests/test_authentication.py
SSEAuthentication extends existing JWT/API-key auth to support EventSource clients that cannot set headers, falling back to access_token query parameter validation. Tests confirm standard header auth remains unchanged and query-only fallback validates via JWT.
SSE ViewSet base and tenant-scoped channel manager
api/src/backend/api/sse/base_views.py, api/src/backend/api/sse/channelmanager.py, api/src/backend/api/tests/test_sse.py
BaseSSEViewSet overrides list() to populate channels from get_channels() and streams via django_eventstream. SSEChannelManager enforces tenant isolation: authenticates users, extracts tenant from channel name, and only allows reads for tenant members. Tests validate authorization, malformed channel rejection, and channel reliability status.
Eventstream configuration and package wiring
api/src/backend/config/settings/eventstream.py, api/src/backend/api/sse/__init__.py
Dedicated Valkey DB configuration for event streaming with optional authentication and TLS. Channel manager class and allowed header constraints configured. SSE package exports BaseSSEViewSet and make_channel_name as public API.
Documentation and changelog
api/CHANGELOG.md, docs/developer-guide/server-sent-events.mdx, docs/docs.json
Changelog entry for SSE infrastructure. Developer guide explains use cases, platform components, endpoint implementation workflow, event conventions, authentication patterns, tenant isolation mechanics, reconnect/recovery model, local testing with curl, and test coverage expectations. Navigation updated.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.51% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding Server-Sent Events infrastructure to the API.
Description check ✅ Passed The description covers all required template sections: Context explains the motivation, Description details the changes and dependencies, Steps to review guides reviewers, and the Checklist is completed appropriately.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch PROWLER-1414-add-sse-capabilities-to-the-backend

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

✅ All necessary CHANGELOG.md files have been updated.

@github-actions

github-actions Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Conflict Markers Resolved

All conflict markers have been successfully resolved in this pull request.

@mintlify

mintlify Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
prowler 🟢 Ready View Preview Jun 11, 2026, 2:32 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@github-actions

github-actions Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

🔒 Container Security Scan

Image: prowler-api:1b3dc9e
Last scan: 2026-06-12 11:33:42 UTC

📊 Vulnerability Summary

Severity Count
🔴 Critical 22
Total 22

16 package(s) affected

⚠️ Action Required

Critical severity vulnerabilities detected. These should be addressed before merging:

  • Review the detailed scan results
  • Update affected packages to patched versions
  • Consider using a different base image if updates are unavailable

📋 Resources:

@codecov

codecov Bot commented Jun 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.42857% with 8 lines in your changes missing coverage. Please review.
✅ Project coverage is 94.04%. Comparing base (a394c0f) to head (247bb4e).
⚠️ Report is 6 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master   #11556      +/-   ##
==========================================
+ Coverage   94.02%   94.04%   +0.01%     
==========================================
  Files         241      247       +6     
  Lines       35705    35927     +222     
==========================================
+ Hits        33573    33787     +214     
- Misses       2132     2140       +8     
Flag Coverage Δ
api 94.04% <96.42%> (+0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
prowler ∅ <ø> (∅)
api 94.04% <96.42%> (+0.01%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@puchy22 puchy22 marked this pull request as ready for review June 11, 2026 15:53
@puchy22 puchy22 requested review from a team as code owners June 11, 2026 15:53

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@api/src/backend/api/sse/utils.py`:
- Around line 34-36: Change the channel-shape check to require exactly three
segments and fail otherwise: replace the current len(segments) < 3 check with
len(segments) != 3 so channels with more than three segments are rejected;
additionally, after splitting, validate the first segment equals the expected
prefix constant (e.g., CHANNEL_PREFIX or the literal used elsewhere) and
validate the tenant_id segment is a UUID (use uuid.UUID(...) in a try/except) to
prevent separator-injection and non-canonical names when extracting tenant_id
from segments.

In `@api/src/backend/api/tests/test_authentication.py`:
- Around line 413-427: Add a test case in test_authentication.py that verifies
SSEAuthentication.authenticate raises an authentication error when an
access_token query param is present but invalid: create a MagicMock request with
request.query_params = {"access_token": "bad-token"}, patch
"api.authentication.JWTAuthentication" to return a jwt_instance whose
get_validated_token raises rest_framework.exceptions.AuthenticationFailed, call
SSEAuthentication().authenticate(request) and assert that AuthenticationFailed
is raised, and also assert get_validated_token was called with "bad-token" to
ensure the query-token path is exercised.

In `@docs/developer-guide/server-sent-events.mdx`:
- Around line 60-66: Standardize the prose to use "tenant ID" (uppercase)
everywhere while keeping code identifiers like `<tenant_id>` and CHANNEL_PREFIX
(and the example channel format `<prefix>:<tenant_id>:<resource_id>`) unchanged;
update occurrences such as "The tenant id is baked into every channel name" and
"parsing the tenant id embedded in the channel name" to "The tenant ID..." so
all narrative references use the uppercase form, but do not modify code snippets
or symbol names like make_channel_name, CHANNEL_PREFIX, or `<tenant_id>`.
- Line 60: Reword the phrase "owned by your feature" to remove the second-person
possessive in the sentence describing channel format; for example change it to
"provided by the feature", "controlled by the feature", or "assigned by the
feature" so the sentence reads like "The prefix is provided by the feature and
may contain hyphens but never colons (the parser splits on `:`)"; update the doc
text that describes channels and the `make_channel_name` usage accordingly.
- Around line 1-3: Add a Version Badge immediately after the section header
"Server-Sent Events (SSE)" to indicate this is new in Prowler API v1.32.0;
update docs/developer-guide/server-sent-events.mdx by inserting the standard
Version Badge element (matching project badge style) right below the top-level
title line so the page clearly shows "Prowler API v1.32.0" for the new SSE
infrastructure feature.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 3e28d44a-07ed-482b-80d9-7f77bd3f1cc7

📥 Commits

Reviewing files that changed from the base of the PR and between 610febb and 64d6255.

⛔ Files ignored due to path filters (1)
  • api/uv.lock is excluded by !**/*.lock, !**/uv.lock
📒 Files selected for processing (15)
  • api/CHANGELOG.md
  • api/docker-entrypoint.sh
  • api/pyproject.toml
  • api/src/backend/api/authentication.py
  • api/src/backend/api/sse/__init__.py
  • api/src/backend/api/sse/base_views.py
  • api/src/backend/api/sse/channelmanager.py
  • api/src/backend/api/sse/utils.py
  • api/src/backend/api/tests/test_authentication.py
  • api/src/backend/api/tests/test_sse.py
  • api/src/backend/config/django/base.py
  • api/src/backend/config/guniconf.py
  • api/src/backend/config/settings/eventstream.py
  • docs/developer-guide/server-sent-events.mdx
  • docs/docs.json

Comment thread api/src/backend/api/sse/utils.py
Comment thread api/src/backend/api/tests/test_authentication.py
Comment thread docs/developer-guide/server-sent-events.mdx

<Step title="Pick a channel prefix">

Channels follow the format `<prefix>:<tenant_id>:<resource_id>`, built only through `make_channel_name`. The prefix is owned by your feature and may contain hyphens but **never colons** (the parser splits on `:`).

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Minimize second-person possessive in descriptive text.

The phrase "owned by your feature" uses a second-person possessive in descriptive (non-imperative) text. As per coding guidelines, minimize explicit use of second-person pronouns and possessives except for imperative instructions.

♻️ Proposed alternative
-Channels follow the format `<prefix>:<tenant_id>:<resource_id>`, built only through `make_channel_name`. The prefix is owned by your feature and may contain hyphens but **never colons** (the parser splits on `:`).
+Channels follow the format `<prefix>:<tenant_id>:<resource_id>`, built only through `make_channel_name`. Feature implementations own the prefix, which may contain hyphens but **never colons** (the parser splits on `:`).
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Channels follow the format `<prefix>:<tenant_id>:<resource_id>`, built only through `make_channel_name`. The prefix is owned by your feature and may contain hyphens but **never colons** (the parser splits on `:`).
Channels follow the format `<prefix>:<tenant_id>:<resource_id>`, built only through `make_channel_name`. Feature implementations own the prefix, which may contain hyphens but **never colons** (the parser splits on `:`).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/developer-guide/server-sent-events.mdx` at line 60, Reword the phrase
"owned by your feature" to remove the second-person possessive in the sentence
describing channel format; for example change it to "provided by the feature",
"controlled by the feature", or "assigned by the feature" so the sentence reads
like "The prefix is provided by the feature and may contain hyphens but never
colons (the parser splits on `:`)"; update the doc text that describes channels
and the `make_channel_name` usage accordingly.

Source: Coding guidelines

Comment on lines +60 to +66
Channels follow the format `<prefix>:<tenant_id>:<resource_id>`, built only through `make_channel_name`. The prefix is owned by your feature and may contain hyphens but **never colons** (the parser splits on `:`).

```python
CHANNEL_PREFIX = "scan-progress"
```

The tenant id is baked into every channel name. That is what lets the platform enforce cross-tenant isolation without knowing anything about your feature.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | 💤 Low value

Inconsistent capitalization of "tenant id" versus "tenant ID".

The document uses both "tenant id" (lines 60, 66, 201) and "tenant ID" (line 209, embedded in code/channel names as tenant_id). For consistency and clarity, standardize on one form throughout the prose. "tenant ID" (uppercase) is preferred when referring to identifiers in technical documentation.

Examples:

  • Line 60: "format <prefix>:<tenant_id>:<resource_id>" → code identifier (keep lowercase in code)
  • Line 66: "The tenant id is baked into every channel name" → prose (should be "tenant ID")
  • Line 201: "by parsing the tenant id embedded in the channel name" → prose (should be "tenant ID")

Also applies to: 201-201, 209-209

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/developer-guide/server-sent-events.mdx` around lines 60 - 66,
Standardize the prose to use "tenant ID" (uppercase) everywhere while keeping
code identifiers like `<tenant_id>` and CHANNEL_PREFIX (and the example channel
format `<prefix>:<tenant_id>:<resource_id>`) unchanged; update occurrences such
as "The tenant id is baked into every channel name" and "parsing the tenant id
embedded in the channel name" to "The tenant ID..." so all narrative references
use the uppercase form, but do not modify code snippets or symbol names like
make_channel_name, CHANNEL_PREFIX, or `<tenant_id>`.

Source: Coding guidelines

Comment thread api/src/backend/api/sse/base_views.py
Comment thread api/src/backend/api/tests/test_authentication.py
puchy22 added 10 commits June 12, 2026 12:32
Add the django-eventstream dependency that backs Server-Sent Events and
bump gunicorn to a release that ships the native asgi worker class, so
SSE streams can run on the event loop.
Run gunicorn with the native asgi worker against config.asgi so SSE
streams are parked on the event loop instead of holding a sync worker
per open connection; sync CRUD views keep running in the thread-sensitive
executor. Disable preload under DEBUG so dev reload picks up edited code,
and point the dev and prod entrypoints at the ASGI application.
Browser EventSource cannot set the Authorization header, so add an
SSEAuthentication class that extends the standard JWT/API-key stack with
an ?access_token=<jwt> query-parameter fallback (RFC 6750 section 2.3),
consulted only when no Authorization header is present. The query path
accepts a JWT only; API keys remain header-only.
Add the platform SSE layer that wires django-eventstream into the API:

- BaseSSEViewSet: a base viewset features subclass to expose an SSE
  endpoint, reusing the regular DRF stack (auth, RBAC permissions, tenant
  transaction) and delegating the stream to django-eventstream.
- SSEChannelManager: resolves the channel set off the request and enforces
  a tenant gate by parsing the tenant id embedded in the channel name.
- make_channel_name/tenant_id_from_channel: the single source of truth for
  the <prefix>:<tenant_id>:<resource_id> channel format.
- eventstream settings: Valkey Pub/Sub backend on a dedicated DB, the
  channel manager, and allowed headers; registered in Django settings.

No endpoint streams over SSE yet; this is the reusable base.
Document the SSE infrastructure for backend developers: when to use SSE,
the architecture and ASGI transport, a step-by-step worked example for
adding an endpoint to a feature, the resource.verb event-naming
convention, authentication, the tenant-isolation model, and reconnect/
state-recovery. Register the page in the Developer Guide navigation.
Enforce the canonical <prefix>:<tenant_id>:<resource_id> contract: make_channel_name now raises ValueError when any segment contains the ':' separator, and tenant_id_from_channel requires exactly three segments so a crafted name cannot slip a valid tenant UUID into position 1 while carrying extra segments.
Add an error-path test asserting SSEAuthentication.authenticate raises AuthenticationFailed when the access_token query param is present but invalid, complementing the existing valid-token fallback test.
Drive a real DRF request through the full viewset stack (auth, RLS, content negotiation, channel manager) and assert events() returns an SSE StreamingHttpResponse, guarding the DRF-request-into-django-eventstream path from silent regressions.
Mark the Server-Sent Events guide as new in Prowler API v1.32.0 with the standard VersionBadge component.
@puchy22 puchy22 force-pushed the PROWLER-1414-add-sse-capabilities-to-the-backend branch from 64d6255 to 7820d68 Compare June 12, 2026 10:38

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@api/src/backend/api/authentication.py`:
- Around line 120-123: The code creates a new JWTAuthentication instance instead
of using the shared backend, causing divergence from
CombinedJWTOrAPIKeyAuthentication.jwt_auth; replace the direct instantiation
(JWTAuthentication()) with a reference to the shared backend
(CombinedJWTOrAPIKeyAuthentication.jwt_auth) when validating the raw_token and
retrieving the user so SSE fallback uses the same configured JWT backend; update
calls to get_validated_token and get_user to use that shared jwt_auth instance.

In `@api/src/backend/api/sse/channelmanager.py`:
- Around line 13-29: get_channels_for_request currently returns
request.sse_channels without checking the active JWT tenant; change it to
filter/validate every channel in request.sse_channels against request.tenant_id
(the tenant set by BaseRLSViewSet / request.auth["tenant_id"]) by parsing
tenant_id_from_channel(channel) and only returning channels whose embedded
tenant equals request.tenant_id (fail-closed by excluding mismatches or
malformed channels); keep can_read_channel as the secondary membership backstop
but ensure the primary authorization uses request.tenant_id before handing
channels to django-eventstream.

In `@api/src/backend/api/tests/test_authentication.py`:
- Around line 392-400: Add a new assertion to the existing
test_header_present_delegates_to_super that sets both an Authorization header
and a query access_token (e.g., request.query_params = {"access_token":
"query-token"}) and verifies SSEAuthentication().authenticate(request) delegates
to the superclass authenticate (patch
SSEAuthentication.__bases__[0].authenticate as in the test) and returns the
super result; this ensures the header takes precedence over the ?access_token=
fallback.

In `@api/src/backend/config/guniconf.py`:
- Around line 28-35: The preload/reload and logging decisions are using the
config variable DEBUG instead of the actual runtime Django settings; update the
module to read settings.DEBUG and settings.LOGGING from the active settings
module (via django.conf.settings or by loading DJANGO_SETTINGS_MODULE) and use
those values when computing preload_app, reload and any logging configuration;
specifically change references that set preload_app = not DEBUG and any LOGGING
usage to use settings.DEBUG and settings.LOGGING (ensure settings is
imported/initialized before use) so Gunicorn behavior matches the active
settings module.

In `@docs/developer-guide/server-sent-events.mdx`:
- Around line 27-35: Update the documentation to use consistent
repository-relative paths (prefer the existing repo convention like
api/src/backend/api/...) for all referenced files: replace instances of
api/sse/base_views.py, api/sse/channelmanager.py, api/authentication.py,
api/sse/utils.py, config/settings/eventstream.py and api/tests/test_sse.py with
their repository-relative equivalents (e.g.,
api/src/backend/api/sse/base_views.py,
api/src/backend/api/sse/channelmanager.py,
api/src/backend/api/authentication.py, api/src/backend/api/sse/utils.py,
api/src/backend/config/settings/eventstream.py,
api/src/backend/api/tests/test_sse.py) and ensure all other mentions on the page
use the same convention so paths are uniform throughout the guide.
- Line 15: The documentation uses sentence case for several section headers;
update each listed header to Title Case to match the docs standard — e.g.,
change "When to use SSE", "How it works", "Local development" and the other
occurrences (lines referenced: the headers with texts at 25, 37, 41, 56, 164,
181, 200, 209, 218, 235) to Title Case (e.g., "When to Use SSE", "How It Works",
"Local Development"); ensure every header in
docs/developer-guide/server-sent-events.mdx follows Title-Case capitalization
consistently.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 3e3c1b2a-d789-4931-ac69-b50dae15e9b6

📥 Commits

Reviewing files that changed from the base of the PR and between 64d6255 and 7820d68.

⛔ Files ignored due to path filters (1)
  • api/uv.lock is excluded by !**/*.lock, !**/uv.lock
📒 Files selected for processing (15)
  • api/CHANGELOG.md
  • api/docker-entrypoint.sh
  • api/pyproject.toml
  • api/src/backend/api/authentication.py
  • api/src/backend/api/sse/__init__.py
  • api/src/backend/api/sse/base_views.py
  • api/src/backend/api/sse/channelmanager.py
  • api/src/backend/api/sse/utils.py
  • api/src/backend/api/tests/test_authentication.py
  • api/src/backend/api/tests/test_sse.py
  • api/src/backend/config/django/base.py
  • api/src/backend/config/guniconf.py
  • api/src/backend/config/settings/eventstream.py
  • docs/developer-guide/server-sent-events.mdx
  • docs/docs.json

Comment thread api/src/backend/api/authentication.py Outdated
Comment thread api/src/backend/api/sse/channelmanager.py Outdated
Comment on lines +392 to +400
def test_header_present_delegates_to_super(self):
request = MagicMock()
request.headers = {"Authorization": "Bearer header-token"}
with patch.object(
SSEAuthentication.__bases__[0], "authenticate", return_value=("user", "tok")
) as super_auth:
result = SSEAuthentication().authenticate(request)
super_auth.assert_called_once_with(request)
assert result == ("user", "tok")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Add an explicit "header beats query token" test.

The suite covers header delegation and query fallback separately, but it never exercises the advertised case where both are present. Please add one assertion with Authorization plus ?access_token= so this precedence rule stays locked down.

Suggested test shape
     def test_header_present_delegates_to_super(self):
         request = MagicMock()
         request.headers = {"Authorization": "Bearer header-token"}
+        request.query_params = {"access_token": "query-jwt"}
         with patch.object(
             SSEAuthentication.__bases__[0], "authenticate", return_value=("user", "tok")
-        ) as super_auth:
+        ) as super_auth, patch("api.authentication.JWTAuthentication") as jwt_auth:
             result = SSEAuthentication().authenticate(request)
         super_auth.assert_called_once_with(request)
+        jwt_auth.assert_not_called()
         assert result == ("user", "tok")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def test_header_present_delegates_to_super(self):
request = MagicMock()
request.headers = {"Authorization": "Bearer header-token"}
with patch.object(
SSEAuthentication.__bases__[0], "authenticate", return_value=("user", "tok")
) as super_auth:
result = SSEAuthentication().authenticate(request)
super_auth.assert_called_once_with(request)
assert result == ("user", "tok")
def test_header_present_delegates_to_super(self):
request = MagicMock()
request.headers = {"Authorization": "Bearer header-token"}
request.query_params = {"access_token": "query-jwt"}
with patch.object(
SSEAuthentication.__bases__[0], "authenticate", return_value=("user", "tok")
) as super_auth, patch("api.authentication.JWTAuthentication") as jwt_auth:
result = SSEAuthentication().authenticate(request)
super_auth.assert_called_once_with(request)
jwt_auth.assert_not_called()
assert result == ("user", "tok")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/src/backend/api/tests/test_authentication.py` around lines 392 - 400, Add
a new assertion to the existing test_header_present_delegates_to_super that sets
both an Authorization header and a query access_token (e.g.,
request.query_params = {"access_token": "query-token"}) and verifies
SSEAuthentication().authenticate(request) delegates to the superclass
authenticate (patch SSEAuthentication.__bases__[0].authenticate as in the test)
and returns the super result; this ensures the header takes precedence over the
?access_token= fallback.

Source: Coding guidelines

Comment thread api/src/backend/config/guniconf.py
The platform ships the SSE **infrastructure** (`api.sse`) and wiring. No feature endpoint streams over SSE out of the box — this guide shows how to build one on top of the shared base.
</Info>

## When to use SSE

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Standardize headers to Title Case across the page.

Several section headers are sentence case (for example, “When to use SSE”, “How it works”, “Local development”). Apply one capitalization style consistently to match the documentation standard.

As per coding guidelines, "Use Title-Case capitalization for all titles and headers in documentation."

Also applies to: 25-25, 37-37, 41-41, 56-56, 164-164, 181-181, 200-200, 209-209, 218-218, 235-235

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/developer-guide/server-sent-events.mdx` at line 15, The documentation
uses sentence case for several section headers; update each listed header to
Title Case to match the docs standard — e.g., change "When to use SSE", "How it
works", "Local development" and the other occurrences (lines referenced: the
headers with texts at 25, 37, 41, 56, 164, 181, 200, 209, 218, 235) to Title
Case (e.g., "When to Use SSE", "How It Works", "Local Development"); ensure
every header in docs/developer-guide/server-sent-events.mdx follows Title-Case
capitalization consistently.

Source: Coding guidelines

Comment on lines +27 to +35
SSE is wired through [`django-eventstream`](https://github.com/fanout/django_eventstream) and a small platform layer in `api/src/backend/api/sse/`:

| Piece | File | Responsibility |
|-------|------|----------------|
| `BaseSSEViewSet` | `api/sse/base_views.py` | Base DRF viewset a feature subclasses. The feature implements `get_channels`; the base handles auth, the tenant transaction, and delegates streaming to `django-eventstream`. |
| `SSEChannelManager` | `api/sse/channelmanager.py` | Registered in `settings.EVENTSTREAM_CHANNELMANAGER_CLASS`. Reads the channel set off the request and enforces the platform-wide tenant gate. |
| `SSEAuthentication` | `api/authentication.py` | Same JWT/API-key stack as the rest of the API, plus an `?access_token=<jwt>` fallback for browser `EventSource` clients. Lives with the other authentication classes, not in the `sse` package. |
| `make_channel_name` / `tenant_id_from_channel` | `api/sse/utils.py` | Single source of truth for the channel-name format, so publishers and the channel manager agree byte-for-byte. |
| Settings | `config/settings/eventstream.py` | Valkey Pub/Sub backend (dedicated DB), channel manager, allowed headers. |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use consistent repository-relative paths for referenced files.

This page mixes api/src/backend/api/sse/... with shorter api/sse/... and later references api/tests/test_sse.py, which can mislead contributors about actual file locations. Standardize all path references to one convention (prefer repository-relative paths used elsewhere in this guide).

Also applies to: 237-237

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/developer-guide/server-sent-events.mdx` around lines 27 - 35, Update the
documentation to use consistent repository-relative paths (prefer the existing
repo convention like api/src/backend/api/...) for all referenced files: replace
instances of api/sse/base_views.py, api/sse/channelmanager.py,
api/authentication.py, api/sse/utils.py, config/settings/eventstream.py and
api/tests/test_sse.py with their repository-relative equivalents (e.g.,
api/src/backend/api/sse/base_views.py,
api/src/backend/api/sse/channelmanager.py,
api/src/backend/api/authentication.py, api/src/backend/api/sse/utils.py,
api/src/backend/config/settings/eventstream.py,
api/src/backend/api/tests/test_sse.py) and ensure all other mentions on the page
use the same convention so paths are uniform throughout the guide.

josema-xyz
josema-xyz previously approved these changes Jun 12, 2026
The ?access_token= fallback in SSEAuthentication created a fresh JWTAuthentication() instead of the shared CombinedJWTOrAPIKeyAuthentication.jwt_auth instance used by the header path. Reuse self.jwt_auth so the query-token fallback stays on the same configured backend if the parent auth stack is ever customized.

Patch the shared instance instead of the constructor in the SSE auth tests.
SSEChannelManager.get_channels_for_request returned every channel stashed on the request, leaving can_read_channel (a membership check) as the only tenant gate. A user belonging to multiple tenants could then read another tenant's stream if a viewset ever returned the wrong channel set.

Filter request.sse_channels against the active JWT tenant (request.tenant_id, set by BaseRLSViewSet) before handing channels to django-eventstream, keeping can_read_channel as the membership backstop. Fail-closed: a missing or unparseable request tenant, or a malformed channel, yields no channels.

Add type hints and docstrings to the manager methods and cover the cross-tenant, malformed, and missing-tenant cases in the tests.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants