Commit Graph

365 Commits

Author SHA1 Message Date
Sylvain Zimmer
117677bd14 🚀(paas) add PaaS deployment scripts, tested on Scalingo
add PaaS deployment scripts, tested on Scalingo
2026-02-10 10:44:13 +01:00
lebaudantoine
69c6e58017 🔒️(backend) add application validation when consuming external JWT
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.
2026-02-09 22:18:09 +01:00
lebaudantoine
6742f5d19d (backend) monitor throttling rate failure through sentry
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.
2026-02-09 15:50:53 +01:00
lebaudantoine
23de7e52bc ♻️(backend) extract throttling classes into a module
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.
2026-02-09 15:50:53 +01:00
lebaudantoine
3887255e9c ♻️(backend) rework permission to better align with DRF responsibilities
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.
2026-02-09 12:16:12 +01:00
lebaudantoine
5d6ad3f3f6 🔒️(backend) enhance scope manipulation
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.
2026-02-09 12:16:12 +01:00
lebaudantoine
44d68a9c80 (backend) strengthen external API viewset test coverage
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.
2026-02-09 12:16:12 +01:00
lebaudantoine
ed5c1bbd84 ♻️(backend) improve scope prefix removal logic
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.
2026-02-09 12:16:12 +01:00
lebaudantoine
f8c6da8021 🔐(backend) enforce object-level permission checks on room endpoint
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.
2026-02-09 12:16:12 +01:00
lebaudantoine
5ba1657e00 🧪(backend) add test exposing rooms permission flaw in external API
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.
2026-02-09 12:16:12 +01:00
lebaudantoine
6962367e18 🐛(backend) fix notification tests broken by renaming env var
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.
2026-02-07 00:14:49 +01:00
lebaudantoine
c34a85699b ⬆️(backend) upgrade Django to address multiple high-severity CVEs
This update fixes several SQL injection vulnerabilities, including issues in
RasterField band index handling and crafted column aliases (notably in
QuerySet.order_by()), as reported in CVE-2026-1207, CVE-2026-1287, and
CVE-2026-1312.
2026-02-05 19:16:02 +01:00
lebaudantoine
12d8c4a9db ️(admin) improve recording access select component performance
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.
2026-02-05 19:16:02 +01:00
lebaudantoine
42a05da5c0 🔒️(admin) make recording fields read-only for security and performance
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.
2026-02-05 19:16:02 +01:00
lebaudantoine
4344dd6e35 ️(admin) optimize room view queries by prefetching user access
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.
2026-02-05 19:16:02 +01:00
lebaudantoine
fe28902b2e ️(admin) optimize recording view by selecting room at the SQL level
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.
2026-02-05 19:16:02 +01:00
lebaudantoine
1e1e1a2657 ️(admin) remove list filters based on room in recording view
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.
2026-02-05 19:16:02 +01:00
lebaudantoine
88a1136dfd ♻️(backend) refactor ApplicationViewSet to use a basic ViewSet
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
2026-02-03 16:22:06 +01:00
lebaudantoine
bb64532cff 🔖(minor) bump release to 1.5.0 2026-01-28 21:28:55 +01:00
lebaudantoine
f8436d9ae2 🔖(minor) bump release to 1.4.0 2026-01-25 20:02:37 +01:00
lebaudantoine
d101459115 (frontend) add configurable external redirect for unauthenticated users
Offer a way to redirect unauthenticated users to an external home page when they
visit the app, allowing a more marketing-focused entry point with a clearer
value proposition.

In many self-hosted deployments, the default unauthenticated home page is not
accessible or already redirects elsewhere. To ensure resilience, the client
briefly checks that the target page is reachable and falls back to the default
page if not.
2026-01-25 16:49:56 +01:00
lebaudantoine
99a18b6e90 🩹(backend) use case-insensitive email matching in the external api
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.
2026-01-20 20:50:13 +01:00
lebaudantoine
a50aabeaf8 🔖(minor) bump release to 1.3.0 2026-01-13 15:44:23 +01:00
renovate[bot]
d7ad5aed05 ⬆️(dependencies) update aiohttp to v3.13.3 [SECURITY] 2026-01-06 17:00:00 +01:00
lebaudantoine
47cd3eff74 🔖(minor) bump release to 1.2.0 2026-01-05 18:10:05 +01:00
lebaudantoine
0fe8d9b681 🐛(backend) fix ignore recording webhook events
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.
2026-01-05 13:34:55 +01:00
lebaudantoine
f6cdb1125b ♻️(backend) refactor backend recording state management
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.
2026-01-05 00:14:00 +01:00
lebaudantoine
39271544d7 (summary) link transcript to their downloadable recording
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.
2026-01-04 20:22:15 +01:00
lebaudantoine
16badde82d 🚧(backend) update recording metadata alongside recording state changes
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.
2026-01-04 20:22:15 +01:00
lebaudantoine
19f8c96e9d (frontend) allow parametrization of the transcrip document destination
Not all self-hosted instances will configure this setting, so a default text is
shown when the destination is unknown.

This is important to let users quickly click the link and understand which
platform is used to handle the transcription documents.
2026-01-04 20:22:15 +01:00
lebaudantoine
309c532811 (backend) submit screen recordings to the summary microservice
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.
2026-01-04 20:22:15 +01:00
lebaudantoine
4cb6320b83 (summary) add a language parameter for transcription
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.
2026-01-04 20:22:15 +01:00
lebaudantoine
587a5bc574 (frontend) allow starting both a recording and a transcription
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.
2026-01-04 20:22:15 +01:00
lebaudantoine
0d8c76cd03 (backend) add a flexible JSON field to store recording options
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.
2026-01-04 20:22:15 +01:00
lebaudantoine
d3e6af6f82 🚸(frontend) rework the meeting tools side panel UX
Explicitly explain that transcription is reserved for public servants. Remove
the temporary beta form: the feature is now available to all public servants,
with restrictions based on domain. Make white-labeling rules explicit and
clarify who to contact for access.

The beta form created frustration, with users registering and never hearing
back from the team.

Improve guidance when a user may be the meeting host but is not logged in, and
therefore cannot activate recording. Add a clear hint and a quick action to log
in. This decision is based on frequent support requests where users could not
understand why recording was unavailable while they were simply not logged in.
2026-01-04 20:22:15 +01:00
lebaudantoine
0daa6d0432 🔖(release) release 1.1.0
- enable user provisioning through the external viewset
- add LLM observability on the summary service
2025-12-22 11:23:28 +01:00
lebaudantoine
c2c478c367 🩹(backend) remove environment prefix from recently introduced settings
The prefix was unintentionally added and wasn’t caught during review.
This change corrects it.
2025-12-21 16:27:11 +01:00
lebaudantoine
10aac93c36 📝(backend) improve user provisioning documentation
try to make explicit all implicit implementation's details
2025-12-19 13:41:37 +01:00
lebaudantoine
4e6bc157b0 ♻️(backend) standardize error response format in token endpoint
Align error response with the pattern used at other places of the codebase.
2025-12-19 13:41:37 +01:00
lebaudantoine
fe83c5fa07 (backend) add unit tests for user provisioning via external API
Add test coverage for provisional user creation through the external API,
including creating users with email-only (no sub)
2025-12-19 13:41:37 +01:00
lebaudantoine
827014c952 ♻️(backend) explicitly enforce sub field immutability
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.
2025-12-19 13:41:37 +01:00
lebaudantoine
9523f52546 📝(docs) clarify sub as optional to support email-only user provisioning
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.
2025-12-19 13:41:37 +01:00
lebaudantoine
8348a55f7e (backend) enable user creation via email for external integrations
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.
2025-12-19 13:41:37 +01:00
lebaudantoine
dcdae26610 🔖(release) release 1.0.1
Patch several accessibility issues.
2025-12-17 17:36:01 +01:00
lebaudantoine
b0e27b38e2 🔒️(backend) avoid serializing rooms's pin code when restricted
Prevent anonymous users waiting in the lobby, or attacker
to discover the room pin code, that would allow them to join a room.
2025-12-17 10:05:23 +01:00
lebaudantoine
98e568d63c 🔖(major) release 1.0.0
Wouhou, finally. Important milestone, as our software is used by
thousand of users in production.
2025-12-11 00:18:59 +01:00
lebaudantoine
d241de6af1 🔖(minor) bump release to 0.1.43
- upgrade dependencies for security reason
- handle hallucination in transcription
- minor frontend fixes
- support resource server authentification
2025-12-10 23:16:22 +01:00
lebaudantoine
fba879e739 (backend) allow prefixing resource server scopes
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.
2025-12-10 19:47:36 +01:00
renovate[bot]
0241f67787 ⬆️(dependencies) update django to v5.2.9 [SECURITY] 2025-12-09 22:18:27 +01:00
lebaudantoine
c7f5dabbad (backend) integrate ResourceServerAuthentication on the external api
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.
2025-11-24 18:23:38 +01:00