🚧(backend) introduce a lobby system

Implement lobby service using cache as LiveKit doesn't natively support
secure lobby functionality. Their teams recommended to create our own
system in our app's backend.

The lobby system is totally independant of the DRF session IDs,
making the request_entry endpoint authentication agnostic.

This decoupling prevents future DRF changes from breaking lobby functionality
and makes participant tracking more explicit.

Security audit is needed as current LiveKit tokens have excessive privileges
for unprivileged users. I'll offer more option ASAP for the admin to control
participant privileges.

Race condition handling also requires improvements, but should not be critical
at this point.

A great enhancement, would be to add a webhook, notifying the backend when the
room is closed, to reset cache.

This commit makes redis a prerequesite to run the suite of tests. The readme
and CI will be updated in dedicated commits.
This commit is contained in:
lebaudantoine
2025-02-18 22:09:02 +01:00
committed by aleb_the_flash
parent 710d7964ee
commit 4d961ed162
10 changed files with 1928 additions and 15 deletions

View File

@@ -37,7 +37,9 @@ def generate_color(identity: str) -> str:
return f"hsl({hue}, {saturation}%, {lightness}%)"
def generate_token(room: str, user, username: Optional[str] = None) -> str:
def generate_token(
room: str, user, username: Optional[str] = None, color: Optional[str] = None
) -> str:
"""Generate a LiveKit access token for a user in a specific room.
Args:
@@ -45,6 +47,8 @@ def generate_token(room: str, user, username: Optional[str] = None) -> str:
user (User): The user which request the access token.
username (Optional[str]): The username to be displayed in the room.
If none, a default value will be used.
color (Optional[str]): The color to be displayed in the room.
If none, a value will be generated
Returns:
str: The LiveKit JWT access token.
@@ -69,6 +73,9 @@ def generate_token(room: str, user, username: Optional[str] = None) -> str:
identity = str(user.sub)
default_username = str(user)
if color is None:
color = generate_color(identity)
token = (
AccessToken(
api_key=settings.LIVEKIT_CONFIGURATION["api_key"],
@@ -77,13 +84,15 @@ def generate_token(room: str, user, username: Optional[str] = None) -> str:
.with_grants(video_grants)
.with_identity(identity)
.with_name(username or default_username)
.with_metadata(json.dumps({"color": generate_color(identity)}))
.with_metadata(json.dumps({"color": color}))
)
return token.to_jwt()
def generate_livekit_config(room_id: str, user, username: str) -> dict:
def generate_livekit_config(
room_id: str, user, username: str, color: Optional[str] = None
) -> dict:
"""Generate LiveKit configuration for room access.
Args:
@@ -97,5 +106,7 @@ def generate_livekit_config(room_id: str, user, username: str) -> dict:
return {
"url": settings.LIVEKIT_CONFIGURATION["url"],
"room": room_id,
"token": generate_token(room=room_id, user=user, username=username),
"token": generate_token(
room=room_id, user=user, username=username, color=color
),
}