Commit Graph

141 Commits

Author SHA1 Message Date
leo 0c0ce87947 🔒️(backend) validate Room configuration with Pydantic schema
Room.configuration accepted arbitrary JSON without validation, allowing unsafe
or malformed payloads to be stored and creating a security risk. Define a
Pydantic schema to enforce structure and constraints, and add validation
at the serializer level to reject invalid inputs.
2026-05-04 18:10:44 +02:00
lebaudantoine 6f38d60a27 (backend) support add-ons authentication in external viewset
Integrate the add-ons authentication backend into the externalviewset.

This allows third-party integrations (e.g. calendar add-ins) to
obtain a JWT for a user and use it to call the API (e.g. create a
room) via a Bearer authorization header.
2026-04-29 20:32:27 +02:00
lebaudantoine 012857f8c6 🔧(backend) add setting to toggle application token exchange mechanism
Introduce a configuration flag to enable or disable the
application token exchange (service account) mechanism.

This allows activating alternative authentication backends
without requiring full application token configuration.

Required to support the upcoming add-ons authentication backend.
2026-04-29 20:32:25 +02:00
lebaudantoine e5a804f748 🩹(backend) fix scope typo addons settings
The external viewset expected a plural "rooms:x" scope instead of
the incorrect singular "room:x".

Update it to accept the proper scope and emit tokens accordingly.
2026-04-29 20:32:04 +02:00
lebaudantoine 181b97b310 ♻️(backend) align CSRF token header with Django conventions
Update the CSRF header naming to follow Django standards,
avoiding duplicated client-side logic with inconsistent
header names.
2026-04-29 11:21:10 +02:00
lebaudantoine ecb710688d (backend) introduce add-ons authentication backend
Add a new authentication backend for add-ons, with a core service
managing session state in cache and exposing three API endpoints.

Microsoft Add-ins recommend launching authentication from the
side panel via a dialog, then returning the JWT to the parent
context using postMessage. However, due to Django and SSO security
constraints (window.opener not preserved), this approach is not
viable.

Implement a three-step authentication flow:

- `/init`: create a session, return a short-lived transit token
  and CSRF token. Store session ID in a secure HttpOnly cookie.
- `/poll`: allow the add-on to poll until authentication is
  complete, then consume the session and clear the cookie.
- `/exchange`: exchange the transit token for a JWT, which is
  later retrieved via the `/poll` endpoint.

The add-on opens an authentication dialog, stores the transit
token in sessionStorage, and performs the exchange after login.

This approach works within iframe constraints and provides a
sufficiently secure v0 despite known limitations.
2026-04-24 09:27:18 +02:00
leo 8507cdd2b6 (backend) add metadata collection of VAD, connection and chat events
Introduce MetadataCollector and MetadataCollectorService classes to
centralize the collection and storage of user connections, VAD events,
and chat messages. This creates a structured foundation for future speaker
assignment logic based on voice activity detection. Add tests for this new
feature.
2026-04-17 12:17:27 +02:00
lebaudantoine 3ccb2d4dd8 ♻️(backend) fix Twirp error mocking in tests
Tests were incorrectly mocking Twirp errors using HTTP status
codes instead of the meaningful error codes returned by the
LiveKit SDK.

Update mocks to reflect actual SDK behavior.
2026-04-09 00:37:56 +02:00
lebaudantoine 5d7a54e809 ♻️(backend) use Authorization header for LiveKit token authentication
Replace passing the LiveKit JWT in the request body with the
Authorization header, following standard authentication practices.

Extend the LiveKit authentication backend usage across additional
endpoints.

This also raises questions about how clients should securely
retrieve LiveKit tokens, to be addressed later.
2026-04-09 00:37:43 +02:00
lebaudantoine 07af7a85ff 🥅(backend) refine Twirp error handling for participant operations
Avoid mapping all Twirp errors to generic 500 responses.

Explicitly handle the case where a participant is no longer in
the room, as this may indicate suspicious behavior or a client
state issue.

Improve error discrimination to provide more accurate responses.
2026-04-09 00:37:42 +02:00
lebaudantoine 6180ac4e4f 🔒️(backend) rely on backend to allow participant update their metadata
Introduce toggle-hand and rename endpoints in RoomViewSet,
secured with LiveKit token authentication.

Remove direct permission for clients to update their own metadata
via LiveKit tokens to prevent spoofing (e.g. faking admin status).

Proxy participant metadata updates through the backend to enforce
proper validation and authorization.

Signed-off-by: lebaudantoine <lebaud.antoine131@gmail.com>
2026-04-09 00:37:42 +02:00
lebaudantoine 6b656eefd7 (backend) add unit tests for JwtTokenService
Introduce minimal unit test coverage for the JwtTokenService
to ensure its core behavior is validated.
2026-04-02 23:18:39 +02:00
lebaudantoine d915b93caa 🚨(backend) fix InsecureKeyLengthWarning in test suite
Resolve warnings raised in jwt/api_jwt.py:365 by ensuring test
keys meet the required security length.

Align test configuration with expected cryptographic standards.
2026-03-28 16:18:16 +01:00
lebaudantoine d5a614d2b5 🐛(backend) fix regression in update-participant endpoint
Serialization hardening introduced a breaking change between the
frontend and backend. Adjust the Pydantic model to restore
compatibility.

Reinstate support for can_subscribe_metric, which is passed by
default from the frontend.
2026-03-25 12:20:45 +01:00
lebaudantoine e9f90e95b1 🔒️(backend) fix email disclosure in room invitation endpoint
Prevent invited participants from seeing each other's email
addresses when sending room invitations.

Ensure invitations are sent with proper isolation to avoid
mass PII disclosure.

This mitigates risks of email harvesting, spam, and phishing
through the platform.
2026-03-25 09:52:43 +01:00
lebaudantoine dcb788b57b 🔒️(backend) avoid information exposure through exception messages
Sanitize error handling to prevent leaking internal details when
invalid or malicious requests are sent to the API.

Return generic error responses to reduce the risk of information
disclosure during probing attempts.
2026-03-13 17:33:55 +01:00
lebaudantoine f3e90c3999 ♻️(backend) clarify storage hook message for intentionally ignored req
Update the response message to explicitly state that certain
notifications are ignored on purpose, avoiding confusion
during debugging or log inspection.
2026-03-13 15:42:30 +01:00
lebaudantoine cb4ed3c9d7 🩹(backend) ignore non-recording uploads in storage webhook handler
With the introduction of background file uploads, a misconfigured
MinIO webhook could trigger the storage hook endpoint for unrelated
files.

While the dev setup now filters events via the MinIO lifecycle
configuration, add a safeguard at the application level.

Enforce a stricter filepath regex when parsing storage hook events
and ignore files outside the recording output directory.

Return a clean 200 response to acknowledge the webhook while
avoiding unnecessary processing.
2026-03-13 15:34:02 +01:00
Florent Chehab 3b719ab9ba (settings) disable file upload by default & max count
Add FILE_UPLOAD_ENABLED setting (default to False, to avoid
a breaking change). Also adds a max_count_by_user sub setting
to restrict the number of uploaded files per user.
2026-03-12 14:55:33 +01:00
lebaudantoine b0af5e7f35 🧪(backend) add failing test for client_id enumeration issue
Currently the inactive status is revealed before verifying the
secret, creating an authentication state oracle.

Introduce a failing test to capture the issue before
applying the fix.
2026-03-12 13:39:44 +01:00
lebaudantoine ca4494c09e ♻️(backend) align Application model field with is_active convention
Most models in the project use `is_active` rather than `active`.
The Application model was not aligned with this convention.

Rename the field and add a migration to standardize the naming.
Update related tests accordingly.

This change should not introduce breaking changes for external
applications.
2026-03-12 13:26:37 +01:00
lebaudantoine f57db0acc8 ♻️(backend) refactor uploaded file's key format
Remove the user-provided filename while preserving the extension,
and switch to the format `uuid.extension`.

This is more natural than the previous `uuid/.extension` format
and avoids confusion.

Update related tests accordingly.
2026-03-12 10:39:59 +01:00
lebaudantoine 051f33bb1e (backend) add tests for request-entry throttling in the lobby system
Cover throttling behavior for both authenticated users and
anonymous participants identified via the lobby cookie.
2026-03-11 18:07:39 +01:00
lebaudantoine c3fd1a89ef 🩹(backend) add page_size to pagination for room endpoints
Align room endpoints with the pagination behavior used across
other API endpoints and resolve issue #1055.
2026-03-11 16:27:02 +01:00
Florent Chehab dc278a6064 (backend) add file upload feature & tests
For the coming features we will need to store files on the meet side.
(for instance user backgrounds).

This commits adds a new Model to manage files, and the associated
serializers & viewsets. All are tested.

This work was heavily inspired by the work done by our friends at
https://github.com/suitenumerique/drive
It build on the same architecture design (upload directly to S3 but
download goes through our proxy), but model is much much simplier
(no folders, no file sharing, etc.).
2026-03-06 11:31:39 +01:00
Florent Chehab 047da94494 (backend) add mimetype detection logic from drive
Reused the logic developed by the team working on drive.
This is usefull for our own upload file backend (that will
come in later commits).

Dockefile was updated to add a required system dependency.

We might want to put this shared logic in a lib.
2026-03-06 11:31:39 +01:00
lebaudantoine 42647d6d25 🦺(backend) strengthen API validation for recording options
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.
2026-03-03 19:05:15 +01:00
lebaudantoine bfbf253033 🔒️(backend) enhance API input validation to strengthen security
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.
2026-03-03 13:30:10 +01:00
lebaudantoine f625df6508 ♻️(backend) refactor external API tests
Refactor tests to avoid duplicating JWT secret key configuration.

Introduce configuration of the JWT audience, which previously had no
default value.
2026-02-24 16:07:23 +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 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 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 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 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 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 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 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 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 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 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
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
lebaudantoine 0d09d1df08 (backend) fix auth unit test with django-lasuite 0.1.16 update
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.
2025-11-13 16:26:17 +01:00
lebaudantoine 5c74ace0d8 🐛(backend) filter LiveKit events by room name regex to exclude Tchap
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.
2025-10-12 16:57:44 +02:00
lebaudantoine c9fcc2ed60 (backend) draft initial Room viewset for external applications
From a security perspective, the list endpoint should be limited to return only
rooms created by the external application. Currently, there is a risk of
exposing public rooms through this endpoint.

I will address this in upcoming commits by updating the room model to track
the source of generation. This will also provide useful information
for analytics.

The API viewset was largely copied and adapted. The serializer was heavily
restricted to return a response more appropriate for external applications,
providing ready-to-use information for their users
(for example, a clickable link).

I plan to extend the room information further, potentially aligning it with the
Google Meet API format. This first draft serves as a solid foundation.

Although scopes for delete and update exist, these methods have not yet been
implemented in the viewset. They will be added in future commits.
2025-10-06 19:34:24 +02:00
lebaudantoine 1f3d0f9239 (backend) add delegation mechanism to external app /token endpoint
This endpoint does not strictly follow the OAuth2 Machine-to-Machine
specification, as we introduce the concept of user delegation (instead of
using the term impersonation).

Typically, OAuth2 M2M is used only to authenticate a machine in server-to-server
exchanges. In our case, we require external applications to act on behalf of a
user in order to assign room ownership and access.

Since these external applications are not integrated with our authorization
server, a workaround was necessary. We treat the delegated user’s email as a
form of scope and issue a JWT to the application if it is authorized to request
it.

Using the term scope for an email may be confusing, but it remains consistent
with OAuth2 vocabulary and allows for future extension, such as supporting a
proper M2M process without any user delegation.

It is important not to confuse the scope in the request body with the scope in
the generated JWT. The request scope refers to the delegated email, while the
JWT scope defines what actions the external application can perform on our
viewset, matching Django’s viewset method naming.

The viewset currently contains a significant amount of logic. I did not find
a clean way to split it without reducing maintainability, but this can be
reconsidered in the future.

Error messages are intentionally vague to avoid exposing sensitive
information to attackers.
2025-10-06 19:34:24 +02:00
lebaudantoine 3fd5a4404c (backend) add application model with secure secret handling
We need to integrate with external applications. Objective: enable them to
securely generate room links with proper ownership attribution.

Proposed solution: Following the OAuth2 Machine-to-Machine specification,
we expose an endpoint allowing external applications to exchange a client_id
and client_secret pair for a JWT. This JWT is valid only within a well-scoped,
isolated external API, served through a dedicated viewset.

This commit introduces a model to persist application records in the database.
The main challenge lies in generating a secure client_secret and ensuring
it is properly stored.

The restframework-apikey dependency was discarded, as its approach diverges
significantly from OAuth2. Instead, inspiration was taken from oauthlib and
django-oauth-toolkit. However, their implementations proved either too heavy or
not entirely suitable for the intended use case. To avoid pulling in large
dependencies for minimal utility, the necessary components were selectively
copied, adapted, and improved.

A generic SecretField was introduced, designed for reuse and potentially
suitable for upstream contribution to Django.

Secrets are exposed only once at object creation time in the Django admin.
Once the object is saved, the secret is immediately hashed, ensuring it can
never be retrieved again.

One limitation remains: enforcing client_id and client_secret as read-only
during edits. At object creation, marking them read-only excluded them from
the Django form, which unintentionally regenerated new values.
This area requires further refinement.

The design prioritizes configurability while adhering to the principle of least
privilege. By default, new applications are created without any assigned scopes,
preventing them from performing actions on the API until explicitly configured.

If no domain is specified, domain delegation is not applied, allowing tokens
to be issued for any email domain.
2025-10-06 19:34:24 +02:00