mirror of
https://github.com/suitenumerique/meet.git
synced 2026-10-12 16:07:49 +00:00
eceefa0b6f
Some integrators render our videoconference inside an iframe, where our cookie-based authentication does not work: our cookies are SameSite=Lax/Strict, so the iframe drops them. We looked at what Jitsi offers: a shared secret used to sign JWTs that authenticate users coming from external services. Since we already expose an external API where third parties authenticate as a given user, it was simpler for us to add an exchange mechanism on top of that. Flow: * Through the external API, mint a short-lived, single-use exchange code for a user. * The third party hands that code to the frontend as a URL fragment. * The frontend exchanges the code for a longer-lived JWT that can be used to query the regular API viewsets. Known limitations and follow-ups: * At some point it would be nice to shorten the JWT lifetime and add a refresh mechanism. This will be handled in a follow-up PR when actually needed. * CSP rules to control which origins are allowed to embed the app in an iframe still need to be added. * This alternative authentication cannot easily be scoped to a subset of endpoints without adding a lot of complexity, so it is accepted globally on the API for now.
138 lines
4.6 KiB
Python
138 lines
4.6 KiB
Python
"""Throttling modules for the API."""
|
|
|
|
from django.conf import settings
|
|
|
|
from lasuite.drf.throttling import MonitoredThrottleMixin
|
|
from rest_framework.throttling import AnonRateThrottle, UserRateThrottle
|
|
from sentry_sdk import capture_message
|
|
|
|
|
|
def sentry_monitoring_throttle_failure(message):
|
|
"""Log when a failure occurs to detect rate limiting issues."""
|
|
capture_message(message, "warning")
|
|
|
|
|
|
class MonitoredAnonRateThrottle(MonitoredThrottleMixin, AnonRateThrottle):
|
|
"""Throttle for the monitored scoped rate throttle."""
|
|
|
|
|
|
class MonitoredUserRateThrottle(MonitoredThrottleMixin, UserRateThrottle):
|
|
"""Throttle for the monitored scoped rate throttle."""
|
|
|
|
|
|
class RoomCreationUserRateThrottle(MonitoredUserRateThrottle):
|
|
"""Throttle room creation per authenticated user.
|
|
|
|
Can be declared at the viewset level: every action other than "create"
|
|
is left unthrottled, so the same class can be reused on any viewset
|
|
exposing a room creation endpoint.
|
|
"""
|
|
|
|
scope = "room_creation"
|
|
|
|
def get_cache_key(self, request, view):
|
|
"""Throttle only room creations."""
|
|
if getattr(view, "action", None) != "create":
|
|
return None
|
|
return super().get_cache_key(request, view)
|
|
|
|
|
|
class RoomCreationDailyUserRateThrottle(RoomCreationUserRateThrottle):
|
|
"""Cap room creation per authenticated user over a day.
|
|
|
|
Complements the short-term RoomCreationUserRateThrottle, which absorbs
|
|
bursts but lets a user steadily create rooms over hours or days.
|
|
"""
|
|
|
|
scope = "room_creation_daily"
|
|
|
|
|
|
class RequestEntryAuthenticatedUserRateThrottle(MonitoredUserRateThrottle):
|
|
"""Throttle authenticated user requesting room entry"""
|
|
|
|
scope = "request_entry"
|
|
|
|
def get_cache_key(self, request, view):
|
|
"""Use the authenticated user ID as the throttle cache key."""
|
|
|
|
if request.user and not request.user.is_authenticated:
|
|
return None # Defer to RequestEntryAnonRateThrottle for anonymous users.
|
|
|
|
return super().get_cache_key(request, view)
|
|
|
|
|
|
class RequestEntryAnonRateThrottle(MonitoredAnonRateThrottle):
|
|
"""Throttle Anonymous user requesting room entry"""
|
|
|
|
scope = "request_entry"
|
|
|
|
def get_cache_key(self, request, view):
|
|
"""Use the lobby participant cookie ID as the throttle cache key.
|
|
|
|
Only throttle if a cookie is already set. If no cookie exists yet,
|
|
return None to skip throttling — the cookie will be set on the first
|
|
response, and throttling will apply from the second request onward.
|
|
|
|
Keying on the cookie rather than the IP address prevents penalising
|
|
multiple users behind the same NAT/proxy, and is consistent with how
|
|
LobbyService identifies participants.
|
|
|
|
Note: as per DRF documentation, application-level throttling is not a
|
|
security measure against brute-force or DoS attacks. This throttle exists
|
|
solely to guard against accidental hammering from buggy clients.
|
|
"""
|
|
|
|
if request.user and request.user.is_authenticated:
|
|
return None # Only throttle unauthenticated requests.
|
|
|
|
participant_id = request.COOKIES.get(settings.LOBBY_COOKIE_NAME)
|
|
|
|
if participant_id is None:
|
|
return None # No throttling for cookieless requests
|
|
|
|
return self.cache_format % {
|
|
"scope": self.scope,
|
|
"ident": participant_id,
|
|
}
|
|
|
|
|
|
class CreationCallbackAnonRateThrottle(MonitoredAnonRateThrottle):
|
|
"""Throttle Anonymous user requesting room generation callback"""
|
|
|
|
scope = "creation_callback"
|
|
|
|
|
|
class RoomKitJoinRateThrottle(MonitoredUserRateThrottle):
|
|
"""Throttle the LiveKit SIP module requesting roomkit joins.
|
|
|
|
The roomkit endpoints are authenticated as a machine user, so all requests
|
|
share a single throttle bucket. This is not a security measure against
|
|
brute-force attacks but a guard against accidental hammering from a buggy
|
|
SIP module.
|
|
"""
|
|
|
|
scope = "roomkit_join"
|
|
|
|
|
|
class ConnectionTestUserRateThrottle(MonitoredUserRateThrottle):
|
|
"""Throttle authenticated users requesting connection test tokens."""
|
|
|
|
scope = "connection_test"
|
|
|
|
|
|
class ConnectionTestAnonRateThrottle(MonitoredAnonRateThrottle):
|
|
"""Throttle anonymous users requesting connection test tokens."""
|
|
|
|
scope = "connection_test"
|
|
|
|
|
|
class ExchangeAccessTokenAnonRateThrottle(MonitoredAnonRateThrottle):
|
|
"""Throttle anonymous transit code exchange attempts.
|
|
|
|
Abuse mitigation only, not a security boundary: DRF throttling is
|
|
best-effort. The security of the exchange rests on the codes'
|
|
entropy and single use.
|
|
"""
|
|
|
|
scope = "exchange_access_token"
|