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.
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.
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.
Refactor feature flag mechanism from Django permission classes to custom
decorator that returns 404 Not Found when features are disabled instead
of exposing API structure through permission errors.
Improves security by preventing information disclosure about disabled
features and provides more appropriate response semantics. Custom
decorator approach is better suited for feature toggling than Django's
permission system which is designed for authorization.
Refactor client-side LiveKit API calls to server-side endpoints
following LiveKit documentation recommendations for participant
management operations.
Replaces hacky direct client calls with proper backend-mediated
requests, improving security and following official LiveKit
Introduce new method on lobby system to clear lobby cache for specific
room and participant combinations.
Enables targeted cleanup of lobby state when participants leave or are
removed, improving cache management and preventing stale lobby entries.
Refactor lobby system to use consistent UUID v4 across lobby
registration and LiveKit token participant identity instead of
generating separate UUIDs.
Maintains synchronized identifiers between lobby cache and LiveKit
participants, simplifying future participant removal operations by
using the same UUID reference across both systems.
Extend LiveKit token creation utility with additional room configuration
and user role parameters to properly adapt room_admin grants and
publish sources based on permission levels.
This creates technical debt in utility function design that should be
refactored into proper service architecture for token
generation operations in future iterations.
Allow any user, anonymous or authenticated, to start subtitling
in a room only if they are an active participant of it.
Subtitling a room consists of starting the multi-user transcriber agent.
This agent forwards all participants' audio to an STT server and returns
transcription segments for any active voice to the room.
User roles in the backend room system cannot be used
to determine subtitle permissions.
The transcriber agent can be triggered multiple times but will only join a
room once. Unicity is managed by the agent itself.
Any user with a valid LiveKit token can initiate subtitles. Feature flag
logic is implemented on the frontend. The frontend ensures the "start
subtitle" action is only available to users who should see it. The backend
does not enforce feature flags in this version.
Authentication in our system does not imply access to a room. The only
valid proof of access is the LiveKit API token issued by the backend.
Security consideration: A LiveKit API token is valid for 6 hours and
cannot be revoked at the end of a meeting. It is important to verify
that the token was issued for the correct room.
Calls to the agent dispatch endpoint must be server-initiated. The backend
proxies these calls, as clients cannot securely contact the agent dispatch
endpoint directly (per LiveKit documentation).
Room ID is passed as a query parameter. There is currently no validation
ensuring that the room exists prior to agent dispatch.
TODO: implement validation or error handling for non-existent rooms.
The backend does not forward LiveKit tokens to the agent. Default API
rate limiting is applied to prevent abuse.
Implement method to process egress limit reached events from LiveKit
webhooks for better recording duration management.
Livekit by default is not notifying the participant of a room when
an egress reached its limit. I needed to proxy it through the back.
Create new service to handle recording-related webhooks, starting with
limit reached events. Will expand to enhance UX by notifying backend
of other LiveKit events.
Doesn't fit cleanly with existing recording package - may need broader
redesign. Chose dedicated service over mixing responsibilities.
Move from lobby service to utils for reuse across services. Method is
generic enough for utility status. Future: create dedicated LiveKit
service to encapsulate all LiveKit-related utilities.
Implemented a service that automatically creates a SIP dispatch rule when
the first WebRTC participant joins a room and removes it when the room
becomes empty.
Why? I don’t want a SIP participant to join an empty room.
The PIN code could be easily leaked, and there is currently no lobby
mechanism available for SIP participants.
A WebRTC participant is still required to create a room.
This behavior is inspired by a proprietary tool. The service uses LiveKit’s
webhook notification system to react to room lifecycle events. This is
a naive implementation that currently supports only a single SIP trunk and
will require refactoring to support multiple trunks. When no trunk is
specified, rules are created by default on a fallback trunk.
@rouja wrote a minimal Helm chart for LiveKit SIP with Asterisk, which
couldn’t be versioned yet due to embedded credentials. I deployed it
locally and successfully tested the integration with a remote
OVH SIP trunk.
One point to note: LiveKit lacks advanced filtering capabilities when
listing dispatch rules. Their recommendation is to fetch all rules and
filter them within your backend logic. I’ve opened a feature request asking
for at least the ability to filter dispatch rules by room, since filtering
by trunk is already supported, room-based filtering feels like a natural
addition.
Until there's an update, I prefer to keep the implementation simple.
It works well at our current scale, and can be refactored when higher load
or multi-trunk support becomes necessary.
While caching dispatch rule IDs could be a performance optimization,
I feel it would be premature and potentially error-prone due to the complexity
of invalidation. If performance becomes an issue, I’ll consider introducing
caching at that point. To handle the edge case where multiple dispatch rules
with different PIN codes are present, the service performs an extensive
cleanup during room creation to ensure SIP routing remains clean and
predictable. This edge case should not happen.
In the 'delete_dispatch_rule' if deleting one rule fails, method would exit
without deleting the other rules. It's okay IMO for a first iteration.
If multiple dispatch rules are often found for room, I would enhance this part.
Remove assertion statement that was placed after code expected to raise an
exception. The assertion was never evaluated due to the exception flow,
making the test ineffective.
Rework regex pattern to exclude empty string matches since
url_encoded_folder_path is optional.
Add additional test cases covering edge cases and failure
scenarios to improve validation coverage
and prevent false positives.
Handle unhandled exceptions to prevent UX impact. Marketing email operations
are optional and should not disrupt core functionality.
My first implementation was imperfect, raising error in sentry.
Fix test cases for room PIN code generation that were not updated when
max retry limit was increased during code review. Aligns test expectations
with actual implementation to prevent false failures.
Enable users to join rooms via SIP telephony by:
- Dialing the SIP trunk number
- Entering the room's PIN followed by '#'
The PIN code needs to be generated before the LiveKit room is created,
allowing the owner to send invites to participants in advance.
With 10-digit PINs (10^10 combinations) and a large number of rooms
(e.g., 1M), collisions become statistically inevitable. A retry mechanism
helps reduce the chance of repeated collisions but doesn't eliminate
the overall risk.
With 100K generated PINs, the probability of at least one collision exceeds
39%, due to the birthday paradox.
To scale safely, we’ll later propose using multiple trunks. Each trunk
will handle a separate PIN namespace, and the combination of trunk_id and PIN
will ensure uniqueness. Room assignment will be evenly distributed across
trunks to balance load and minimize collisions.
Following XP principles, we’ll ship the simplest working version of this
feature. The goal is to deliver value quickly without over-engineering.
We’re not solving scaling challenges we don’t currently face.
Our production load is around 10,000 rooms — well within safe limits for
the initial implementation.
Discussion points:
- The `while` loop should be reviewed. Should we add rate limiting
for failed attempts?
- A systematic existence check before `INSERT` is more costly for a rare
event and doesn't prevent race conditions, whereas retrying on integrity
errors is more efficient overall.
- Should we add logging or monitoring to track and analyze collisions?
I tried to balance performance and simplicity while ensuring the
robustness of the PIN generation process.
Restrict access to room user permissions data by excluding this information
from room serializer response for non-admin/owner users. Previously all
members could see complete access lists. Change enforces stricter information
access control based on user role.
Spotted in #YWH-PGM14336-5.
Fix container networking issue where app-dev container couldn't resolve
localhost address when calling LiveKit API. Update configuration to use
proper container network addressing for backchannel communication between
services.
Create dedicated utility function for livekit API client initialization.
Centralizes configuration logic including custom session handling for SSL
verification. Improves code reuse across backend components that interact
with LiveKit.
Refactor BaseEgress class to leverage latest livekit-api client's custom
session support. Simplifies code by using built-in capability to disable SSL
verification in development environments instead of previous workaround.
Remove BaseEgress tests that were overly complicated and had excessive
mocking, making them unrealistic and difficult to maintain. Will replace with
more straightforward tests in future commits that better reflect actual code
behavior.
Update livekit-api dependency to most recent release, enabling custom session
configuration. New version allows disabling SSL verification in local
development environment through session parameter support.
Add expiration system for recordings.
Include option for users to set recordings as permanent (no expiration)
which is the default behavior.
System only calculates expiration dates and tracks status - actual deletion
is handled by Minio bucket lifecycle policies, not by application code.
Customize email notifications for recording availability based on each user's
language and timezone settings. Improves user experience through localized
communications.
Prioritize simple, maintainable implementation over complex code that would
form subgroups based on user preferences. Note: Changes individual email
sending instead of batch processing, which may impact performance for large
groups but is acceptable for typical recording access patterns.
Fix inconsistent test naming resulting from copy-pasted examples. Rename
tests to properly reflect their actual testing purpose and improve code
maintainability.
Add user language and timezone to serialized user data to enable frontend
customization. Allows backend email notifications to respect user's
localization preferences for improved communication relevance.
Modify media auth endpoint to properly handle recordings with "Notification
succeeded" status alongside "Saved" status. Previous code incorrectly
expected only "Saved" status, causing access issues after email notifications
were sent and status was updated.
Add recording key to serialized API response to enable frontend to generate
proper download links without additional backend calls. Simplifies media
access workflow across the application.
Implement new endpoint allowing admin/owner to invite participants via email.
Provides explicit way to search users and send meeting invitations with
direct links.
In upcoming commits, frontend will call ResourceAccess endpoint to add
invited people as members if they exist in visio, bypassing waiting room
for a smoother experience.
Fix code that accidentally exposed personal email addresses in logs during
email sending failures. Modify logging to remove identifying information
to protect user privacy while still providing useful debugging context.
Original code was inspired by Docs.
Implement secure recording file access through authentication instead of
exposing S3 bucket or using temporary signed links with loose permissions.
Inspired by docs and @spaccoud's implementation, with comprehensive
viewset checks to prevent unauthorized recording downloads.
The ingress reserved to media intercept the original request, and thanks to
Nginx annotations, check with the backend if the user is allowed to donwload
this recording file. This might introduce a dependency to Nginx in the project
by the way.
Note: Tests are integration-based rather than unit tests, requiring minio in
the compose stack and CI environment. Implementation includes known botocore
deprecation warnings that per GitHub issues won't be resolved for months.
Add Django built-in mixins to recording viewset to support individual record
retrieval. Enables frontend to access single recording details needed for
the upcoming download page implementation.
Introduce new property that verifies if a recording file has a saved
status. While the implementation is straightforward, it improves code
readability and provides a clear, semantic way to check file status.
Move logic for calculating recording keys and file extensions into proper
properties on recording objects. Simplifies access to Minio storage keys
and clearly documents expected behavior when saving recordings across the
application.
Include recording mode in serialized data to enable conditional UI elements
in frontend. Allows download controls to be dynamically enabled or disabled
based on the specific recording type being used.
Screen recording will be downloadable when transcript won't.
Implement backend method to send email notifications when screen recordings
are ready for download. Enables users to be alerted when their recordings are
available. Frontend implementation to follow in upcoming commits.
This service is triggered by the storage hook from Minio.
Add minimal unit test coverage for notification service, addressing previous
lack of tests in this area. The notification service was responsible for
calling the unstable summary service feature, which was developped way too
quickly.
The email template has been reviewed by a LLM, to make it user-friendly and
crystal clear.