(all) allow forcing SSO display name for authenticated users

Add a new setting that controls whether an authenticated user can
rename or modify their display name, or if they must use the one
returned by the SSO.

When the setting is disabled, only anonymous users can set a display
name freely; authenticated users always use their SSO display name.

Requested by many self-hosters.
This commit is contained in:
lebaudantoine
2026-07-10 15:49:25 +02:00
committed by aleb_the_flash
parent 05a67320b1
commit 0d5136206f
8 changed files with 92 additions and 24 deletions
+1
View File
@@ -12,6 +12,7 @@ and this project adheres to
- ✨(backend) allow searching the recording admin table by owner email
- ✨(frontend) add participant color gradient when camera is off #1490
- ✨(all) allow forcing SSO display name for authenticated users
### Changed
+3
View File
@@ -68,6 +68,9 @@ def get_frontend_configuration(request):
"enable_firefox_proxy_workaround": settings.LIVEKIT_ENABLE_FIREFOX_PROXY_WORKAROUND,
"default_sources": settings.LIVEKIT_DEFAULT_SOURCES,
},
"authenticated_users_can_edit_display_name": (
settings.AUTHENTICATED_PARTICIPANTS_CAN_EDIT_DISPLAY_NAME
),
}
frontend_configuration.update(settings.FRONTEND_CONFIGURATION)
return Response(frontend_configuration)
+41
View File
@@ -7,6 +7,7 @@ import json
from unittest import mock
from django.conf import settings
from django.contrib.auth.models import AnonymousUser
import jwt
import pytest
@@ -66,6 +67,46 @@ def test_generate_token_explicit_username_overrides_default():
assert claims["name"] == "Custom Name"
def test_authenticated_username_ignored_when_editing_disabled(settings):
"""With editing disabled, an authenticated user's username is ignored."""
settings.AUTHENTICATED_PARTICIPANTS_CAN_EDIT_DISPLAY_NAME = False
user = UserFactory(full_name="Jane Doe")
token = generate_token(room="my-room", user=user, username="Custom Name")
claims = decode_token(token)
assert claims["name"] == "Jane Doe"
def test_authenticated_default_name_unaffected_when_editing_disabled(settings):
"""Disabling editing doesn't disturb the default full-name path."""
settings.AUTHENTICATED_PARTICIPANTS_CAN_EDIT_DISPLAY_NAME = False
user = UserFactory(full_name="Jane Doe")
token = generate_token(room="my-room", user=user)
claims = decode_token(token)
assert claims["name"] == "Jane Doe"
def test_anonymous_uses_username_when_provided():
"""An anonymous user's provided username is used as the display name."""
token = generate_token(room="my-room", user=AnonymousUser(), username="Guest42")
claims = decode_token(token)
assert claims["name"] == "Guest42"
def test_anonymous_username_used_even_when_editing_disabled(settings):
"""The setting governs authenticated users only; anonymous can still set a name."""
settings.AUTHENTICATED_PARTICIPANTS_CAN_EDIT_DISPLAY_NAME = False
token = generate_token(room="my-room", user=AnonymousUser(), username="Guest42")
claims = decode_token(token)
assert claims["name"] == "Guest42"
def test_anonymous_falls_back_to_anonymous_label():
"""With no username, an anonymous user is labelled 'Anonymous'."""
token = generate_token(room="my-room", user=AnonymousUser())
claims = decode_token(token)
assert claims["name"] == "Anonymous"
@mock.patch("asyncio.get_running_loop")
@mock.patch("core.utils.LiveKitAPI")
def test_create_livekit_client_ssl_enabled(
+6 -1
View File
@@ -114,6 +114,11 @@ def generate_token(
if color is None:
color = generate_color(identity)
can_edit = (
settings.AUTHENTICATED_PARTICIPANTS_CAN_EDIT_DISPLAY_NAME or user.is_anonymous
)
display_name = (username or default_username) if can_edit else default_username
token = (
AccessToken(
api_key=settings.LIVEKIT_CONFIGURATION["api_key"],
@@ -121,7 +126,7 @@ def generate_token(
)
.with_grants(video_grants)
.with_identity(identity)
.with_name(username or default_username)
.with_name(display_name)
.with_attributes(
{"color": color, "room_admin": "true" if is_admin_or_owner else "false"}
)
+5
View File
@@ -671,6 +671,11 @@ class Base(Configuration):
environ_name="PARTICIPANT_FORBIDDEN_PERMISSION_FIELDS",
environ_prefix=None,
)
AUTHENTICATED_PARTICIPANTS_CAN_EDIT_DISPLAY_NAME = values.BooleanValue(
True,
environ_name="AUTHENTICATED_PARTICIPANTS_CAN_EDIT_DISPLAY_NAME",
environ_prefix=None,
)
# Recording settings
RECORDING_ENABLE = values.BooleanValue(
+1
View File
@@ -58,6 +58,7 @@ export interface ApiConfig {
transcription_destination?: string
max_participants_for_sound: number
auto_mute_on_join_threshold: number
authenticated_users_can_edit_display_name: boolean
}
const fetchConfig = (): Promise<ApiConfig> => {
@@ -51,6 +51,8 @@ import { saveUsername, userStore } from '@/stores/user'
import { useCannotUseDevice } from '../livekit/hooks/useCannotUseDevice'
import { useSnapshot } from 'valtio'
import { useUser } from '@/features/auth/api/useUser'
import { useConfig } from '@/api/useConfig'
const onError = (e: Error) => console.error('ERROR', e)
@@ -115,6 +117,9 @@ export const Join = ({
}) => {
const { t } = useTranslation('rooms', { keyPrefix: 'join' })
const { data: configData } = useConfig()
const { isLoggedIn } = useUser()
const {
audioEnabled,
videoEnabled,
@@ -449,6 +454,8 @@ export const Join = ({
<H lvl={1} margin="sm" centered>
{t('heading')}
</H>
{(!isLoggedIn ||
configData?.authenticated_users_can_edit_display_name) && (
<Field
type="text"
onChange={saveUsername}
@@ -463,6 +470,7 @@ export const Join = ({
autoComplete="name"
maxLength={50}
/>
)}
</VStack>
</Form>
)
@@ -10,6 +10,7 @@ import { LoginButton } from '@/components/LoginButton'
import { useRenameParticipant } from '@/features/rooms/api/renameParticipant'
import { saveUsername } from '@/stores/user'
import { logout } from '@/features/auth/utils/logout'
import { useConfig } from '@/api/useConfig'
export type AccountTabProps = Pick<DialogProps, 'onOpenChange'> &
Pick<TabPanelProps, 'id'>
@@ -17,6 +18,7 @@ export type AccountTabProps = Pick<DialogProps, 'onOpenChange'> &
export const AccountTab = ({ id, onOpenChange }: AccountTabProps) => {
const { t } = useTranslation('settings')
const room = useRoomContext()
const { data } = useConfig()
const { user, isLoggedIn } = useUser()
const { renameParticipant } = useRenameParticipant()
@@ -45,6 +47,7 @@ export const AccountTab = ({ id, onOpenChange }: AccountTabProps) => {
return (
<TabPanel padding={'md'} flex id={id}>
<H lvl={2}>{t('account.heading')}</H>
{(!isLoggedIn || data?.authenticated_users_can_edit_display_name) && (
<Field
type="text"
label={t('account.nameLabel')}
@@ -54,6 +57,7 @@ export const AccountTab = ({ id, onOpenChange }: AccountTabProps) => {
return !value ? <p>{t('account.nameError')}</p> : null
}}
/>
)}
<H lvl={2}>{t('account.authentication')}</H>
{isLoggedIn ? (
<>