Improve validation of parameters accepted when starting a
recording to prevent unsupported or unexpected values.
Language validation will be further tightened to only accept
languages supported by the transcribe microservice.
Add extensive API validation tests to cover these scenarios.
Use settings directly in migrations to avoid noop
migrations. This might have undisered side effects
if we change the config over time 'invalid' data will be
in the database.
It's a simple quick fix.
Keeping some migrations that are no useless to avoid changing
too much the migration history for users.
Similar to https://github.com/suitenumerique/people/commit/
469014ac415b25be0ceed08b31a87d2d40d743cd
These fields previously triggered a suspicious operation exception
when passed to the API.
Make the list configurable so the serializer behavior can be
adjusted without requiring a new release.
During the bug bounty, attempts were made to pass unexpected hidden
fields to manipulate room behavior and join as a ghost.
Treat these parameters as suspicious. They are not sent by the
frontend, so their presence likely indicates tampering.
Explicitly allow the parameters but emit warning logs to help detect
and investigate suspicious activity.
Transcription and summarization results were always generated
using a French text structure (e.g. "Réunion du..."), regardless
of user preference or meeting language. Introduced basic localization
support to adapt generated string languages.
Refactor external API authentication classes to inherit from a
common base authentication backend.
Prepare the introduction of a new authentication class responsible
for verifying tokens provided to calendar integrations.
Move token decoding responsibility to the new token service so it
can both generate and validate tokens.
Encapsulate external exceptions and expose a clear interface by
defining custom Python exceptions raised during token validation.
Taken from #897.
Encapsulate token generation logic for authenticating to the
external API in a well-scoped service.
This service can later be reused in other parts of the codebase,
especially for providing tokens required by calendar integrations.
Commit was cherry picked from #897
Token generation already verifies that the application is active, but this
guarantee was not enforced when the token was used. This change adds a
runtime check to ensure the client_id claim matches an existing and active
application when evaluating permissions.
This also introduces an emergency revocation mechanism, allowing all previously
issued tokens for a given application to be invalidated if the application is
disabled.
Use a mixin, introduced by @lunika in the shared
backend library to monitor throttling behavior.
The mixin tracks when throttling limits are reached, sending errors to Sentry
to trigger alerts when configured. This helps detect misconfigurations,
fine-tune throttling settings, and identify suspicious operations.
This enables safely increasing API throttling limits while ensuring stability,
providing confidence that higher limits won’t break the system.
Extract throttling classes into a dedicated Python module, following the
structure of suitenumerique/docs.
This is a preparatory refactor to ease upcoming changes to the throttling
implementation. No functional behavior change is introduced in this commit.
If a viewset action is not implemented, the permission layer no longer returns
a 403. Instead, it lets DRF handle the request and return the appropriate 405
Method Not Allowed response, ensuring cleaner and more standard API error
handling.
Enhance scope manipulation by normalizing and sanitizing
scope values before processing.
Scopes are now converted to lowercase to ensure consistent behavior,
deduplicated while preserving their original order, and handled in a
deterministic way aligned with the intended authorization model.
Reinforce the test suite around the external API viewset to better
prevent regressions, permission leaks, and unexpected failures.
Adds additional scenarios covering permission enforcement, edge cases,
and error handling to ensure the external API behavior remains stable
and secure as it evolves.
The previous replace usage was too broad and could remove multiple
occurrences, which was not the original intention.
Replace the replace call with removeprefix, which more accurately
matches the expected behavior by only removing the prefix when present
at the start of the string.
Apply strict permission validation on the external API room endpoint to
enforce the principle of least privilege. Unlike the default API (which allows
unauthenticated room retrieval and filters access in the serializer), the
external API now only exposes rooms to users with explicit permissions.
This change fixes a security issue. Slug-based room retrieval, as supported
by the default API, is not introduced here but could be added later if needed.
Retrieving rooms by UUID is retained, as guessing a UUID is significantly harder
than a slug.
A dedicated permission class was created to avoid coupling permissions between
the default and external APIs. The external API enforces stricter access rules.
Access policies may be revisited based on user and integrator feedback. The
external API currently has no production usage.
Add a failing test demonstrating that a user can retrieve a room they
do not have access to when the room UUID is known.
This highlights an improper object-level permission verification in the
external API. While exploitation requires obtaining the target room
UUID, this still represents a security issue (BOLA / IDOR class
vulnerability) and must be fixed.
The test documents the expected behavior and will pass once proper
access filtering or permission checks are enforced.
SCREEN_RECORDING_BASE_URL was renamed to RECORDING_DOWNLOAD_BASE_URL.
The new variable supersedes the old one, which is temporarily kept for backward
compatibility. This test failure was missed because the local common file was
out of sync with common.dist.
Add the new variable with a default value of None to ensure a smooth
deprecation path when the old variable is removed.
Replace the basic select component that loaded thousands of options into the
DOM with a smarter component supporting dynamic loading and search.
With large user bases, linking users to recording access caused massive option
lists to render, severely impacting performance. This change dramatically
improves page loading speed.
These values should not be updated from the admin interface. Allowing changes
to a recording’s associated room could lead to data leaks (e.g., notifications
being resent to the wrong users after a malicious modification).
Also remove the room select field, which rendered a dropdown with ~150k options,
flooding the DOM and severely degrading page performance.
Use prefetch_related for the room–user access relationship to avoid N+1
queries. select_related cannot be used here since this is a many-to-many
relation. This significantly improves performance.
Use select_related on the room foreign key to avoid N+1 queries. This makes
Django perform a join between tables instead of triggering additional queries
per row, reducing complexity from O(n²) patterns to O(n) and significantly
improving performance.
This was a mistake: the filter was never used in production and caused
performance issues. It generated a list of unique room slugs, bloating the DOM
with thousands of values and slowing down view rendering. Remove this
regression.
This endpoint only exposes a custom action for token generation and does not
rely on serializers or querysets. Using ViewSet is more appropriate here, as
it provides routing without enforcing standard CRUD patterns or requiring a
serializer_class.
This removes unnecessary constraints and avoids warnings related to missing
serializer configuration, while better reflecting the actual responsibility of
this view.
I noticed this bug from Sentry issue 241308
Fix a minor issue in the external API where users were matched using
case-sensitive email comparison, while authentication treats emails as
case-insensitive. This caused inconsistencies that are now resolved.
Spotted by T. Lemeur from Centrale.
Fix an unexpected behavior where filtering LiveKit webhook events sometimes
failed because the room name was not reliably extracted from the webhook data,
causing notifications to be ignored.
Configure the same filtering logic locally to avoid missing this kind of issue
in the future.
Instead of relying on the egress_started event—which fires when egress is
starting, not actually started—I now rely on egress_updated for more accurate
status updates. This is especially important for the active status, which
triggers after egress has truly joined the room. Using this avoids prematurely
stopping client-side listening to room.isRecording updates. A further
refactoring may remove reliance on room updates entirely.
The goal is to minimize handling metadata in the mediator class. egress_starting
is still used for simplicity, but egress_started could be considered in the
future.
Note: if the API to start egress hasn’t responded yet, the webhook may fail to
find the recording because it currently matches by worker ID. This is unstable.
A better approach would be to pass the database ID in the egress metadata and
recover the recording from it in the webhook.
Link the transcription document to its related recording by adding a short
header explaining that users can download the audio file via a dedicated link.
This was a highly requested feature, as many users need to keep their audio
files.
As part of a small refactor, remove the argument length check in the metadata
analytics class. The hardcoded argument count made code evolution harder and was
easy to forget updating. Argument unwrapping remains fragile and should be
redesigned later to be more robust.
The backend is responsible for generating the download link to ensure
consistency and reliability.
I tried adding a divider, but the Markdown-to-Yjs conversion is very lossy and
almost never handles it correctly. Only about one out of ten conversions works
as expected.
Previously, this was handled manually by the client, sending notifications to
other participants and keeping the recording state only in memory. There was no
shared or persisted state, so leaving and rejoining a meeting lost this
information. Delegating this responsibility solely to the client was a poor
choice.
The backend now owns this responsibility and relies on LiveKit webhooks to keep
room metadata in sync with the egress lifecycle.
This also reveals that the room.isRecording attribute does not update as fast
as the egress stop event, which is unexpected and should be investigated
further.
This will make state management working when several room’s owner will be in
the same meeting, which is expected to arrive any time soon.
Screen recording are MP4 files containing video)
The current approach is suboptimal: the microservice will later be updated to
extract audio paths from video, which can be heavy to send to the Whisper
service.
This implementation is straightforward, but the notification service is now
handling many responsibilities through conditional logic. A refactor with a
more configurable approach (mapping attributes to processing steps via
settings) would be cleaner and easier to maintain.
For now, this works; further improvements can come later.
I follow the KISS principle, and try to make this new feature implemented
with the lesser impact on the codebase. This isn’t perfect.
Pass recording options’ language to the summary service, allowing users to
personalize the recording language.
This is important because automatic language detection often fails, causing
empty transcriptions or 5xx errors from the Whisper API. Users then do not
receive their transcriptions, which leads to frustration. For most of our
userbase, meetings are in French, and automatic detection is unreliable.
Support for language parameterization in the Whisper API has existed for some
time; only the frontend and backend integration were missing.
I did not force French as the default, since a minority of users hold English or
other European meetings. A proper settings tab to configure this value will be
introduced later.
Major user feature request: allow starting recording and transcription
simultaneously. Inspired by Google Meet UX, add a subtle checkbox letting users
start a recording alongside transcription.
The backend support for this feature is not yet implemented and will come in
upcoming commits, I can only pass the options to the API. The update of the
notification service will be handled later.
We’re half way with a functional feature.
This is not enabled by default because screen recording is resource-intensive. I
prefer users opt in rather than making it their default choice until feature
usage and performance stabilize.
Using a JSON field allows iterating on recording data without running a new
migration each time additional options or metadata need to be tracked.
This comes with trade-offs, notably weaker data validation and less clarity on
which data can be stored alongside a recording.
In the long run, this JSON field can be refactored into dedicated columns once
the feature and data model have stabilized.
Add OIDC_USER_SUB_FIELD_IMMUTABLE setting to our config and enforce
it in the user viewset. Previously relied on implicit Django
LaSuite defaults.
Makes the sub mutability constraint explicit and ensures it's enforced
at the application level, critical for provisional users where sub is
assigned on first login.
Update the sub field documentation to explicitly reflect its optional nature.
Originally intended to be mandatory, sub became optional due to a code issue.
This change acknowledges and formalizes that behavior as intentional.
The optional sub enables external API integrations to provision users with
only an email address. Full identity (sub) is assigned on first login,
allowing third-party platforms to create users before they authenticate.
Allow external platforms using the public API to create provisional users
with email-only identification when the user doesn't yet exist in our
system. This removes a key friction point blocking third-party integrations
from fully provisioning access on behalf of new users.
Provisional users are created with email as the primary identifier. Full
identity reconciliation (sub assignment) occurs on first login, ensuring
reliable user identification is eventually established.
While email-only user creation is not ideal from an identity perspective,
it provides a pragmatic path to unlock integrations and accelerate adoption
through external platforms that are increasingly driving our videoconference
tool's growth.
When declaring scopes with our OIDC provider, they require us to prefix
each scope with our application name. This is to prevent reserving generic
scopes like rooms:list for only our app, as they manage a large federation.
I’m proposing a workaround where, if a resource server prefix is detected in
the scope, it’s stripped out. This solution is simple and sufficient
in my opinion.
Since the scopes are defined in the database, I don’t want to update
them directly. Additionally, each self-hosted instance may have a different
application name, so the prefix should be configurable via a Django setting.
Upgrade django-lasuite to v0.0.19 to benefit from the latest resource server
authentication backend. Thanks @qbey for your work. For my needs, @qbey
refactored the class in #46 on django-lasuite.
Integrate ResourceServerAuthentication in the relevant viewset. The integration
is straightforward since most heavy lifting was done in the external-api viewset
when introducing the service account.
Slightly modify the existing service account authentication backend to defer to
ResourceServerAuthentication if a token is not recognized.
Override user provisioning behavior in ResourceServerBackend: now, a user is
automatically created if missing, based on the 'sub' claim (email is not yet
present in the introspection response). Note: shared/common implementation
currently only retrieves users, failing if the user does not exist.
django-lasuite 0.1.16 changed the user update mechanism from .update()
to .save(), which triggers Django's constraint validation. This causes
an additional SELECT query to verify 'sub' field uniqueness on every
user update, despite 'sub' being immutable in our auth flow.
This commit update the test to make them pass again.
Replace narrow HTTPError handling with broad RequestException
catch to prevent crashes from network failures (ConnectionError),
timeouts (30s exceeded), SSL/TLS errors, and other request failures
that previously caused unhandled exceptions.
Ensures consistent False return and proper logging for all network-related
failures instead of crashing application when summary service
communication encounters infrastructure issues beyond HTTP errors.
While helping users, it was such a pain to determine quickly which recording
was indeed a transcription or a video recording.
Added the column to help me, and support team.
The recording / transcription is the most unstable part of the project.
Enable administrators to manually retrigger external service notifications
from Django admin for failed or missed notification scenarios,
providing operational control over notification delivery.
Fixes "Invalid LiveKit token" errors caused by field mismatch between
token generation and authentication lookup.
Previously:
- generate_token() used user.sub as token identity
- LiveKitTokenAuthentication tried to retrieve user via user.id field
- This failed when sub was not a UUID (e.g., from LemonLDAP OIDC provider)
Now:
- generate_token() continues using user.sub (canonical OIDC identifier)
- LiveKitTokenAuthentication correctly looks up by sub field
- Both sides now consistently use the same field
This ensures compatibility with all RFC 7519-compliant OIDC providers,
regardless of their sub claim format.
Add configurable room name regex filtering to exclude Tchap events from shared
LiveKit server webhooks, preventing backend spam from unrelated application
events while maintaining UUID-based room processing for visio.
Those unrelated application events are spamming the sentry.
Acknowledges this is a pragmatic solution trading proper namespace
prefixing for immediate spam reduction with minimal refactoring impact
leaving prefix-based approach for future improvement.
Introduce ENABLE_EXTERNAL_API setting (defaults to False) to allow
administrators to disable external API endpoints, preventing unintended
exposure for self-hosted instances where such endpoints aren't
needed or desired.