feat(auth): проверка устройств на входе — запрос доступа и превью камеры

Отключаемый модуль (instance_settings.device_check, дефолт выключен):
запрос доступа к камере/микрофону на LoginPage и в карточке "Как вас
зовут?" (JoinPage), живое зеркальное превью и кнопки вкл/выкл камеры и
микрофона там же. На JoinPage кнопки определяют, с чем гость войдёт в
конференцию (RoomPage.LiveKitRoom audio/video вместо жёстких false) —
на LoginPage только пре-авторизуют разрешение, без UI (карточка ведёт
в лобби, применить выбор некуда). Вход в комнату по умолчанию, как и
раньше, с выключенными микрофоном/камерой.

Публичный GET /api/v1/public/settings отдаёт флаг модуля обеим
страницам до аутентификации.
This commit is contained in:
2026-08-09 18:09:10 +03:00
parent ba01548088
commit 7ee68b17ee
19 changed files with 713 additions and 7 deletions

View File

@@ -75,6 +75,7 @@ _MANAGED_KEYS = (
"contact_email",
"media_limits",
"consent_policy",
"device_check",
)
@@ -133,6 +134,7 @@ async def test_ensure_bootstrapped_imports_yaml_defaults(
"contact_email",
"media_limits",
"consent_policy",
"device_check",
}
cfg = await service.get()
assert cfg.transcriber.provider == "faster_whisper_cpu"
@@ -159,6 +161,9 @@ async def test_ensure_bootstrapped_imports_yaml_defaults(
assert cfg.consent_required is False
assert cfg.consent_policy_text != ""
assert cfg.consent_policy_version == 1
# Проверка устройств на входе (сессия 33) — выключена по умолчанию,
# существующие инсталляции не должны молча начать спрашивать доступ.
assert cfg.device_check_enabled is False
async def test_ensure_bootstrapped_is_idempotent_and_keeps_admin_edits(
@@ -193,6 +198,22 @@ async def test_update_hand_queue_enabled(
assert cfg.hand_queue.enabled is True
async def test_update_device_check_enabled(
db_session: AsyncSession, clean_instance_settings: None
) -> None:
service = InstanceSettingsService(db_session)
await service.ensure_bootstrapped(PLUGINS_YAML)
cfg = await service.update(SettingsUpdateIn(device_check_enabled=True))
assert cfg.device_check_enabled is True
cfg = await service.get()
assert cfg.device_check_enabled is True
cfg = await service.update(SettingsUpdateIn(device_check_enabled=False))
assert cfg.device_check_enabled is False
@pytest.mark.parametrize(
("preset", "chat_enabled", "ai_enabled", "ai_level"),
[

View File

@@ -0,0 +1,43 @@
"""Тесты публичного эндпоинта настроек клиента (`/api/v1/public/settings`, сессия 33)."""
import httpx
import pytest_asyncio
from sqlalchemy.ext.asyncio import AsyncSession
from services.instance_settings import InstanceSettingsService, SettingsUpdateIn
@pytest_asyncio.fixture(autouse=True)
async def _reset_device_check(db_session: AsyncSession) -> None:
"""Сбросить `device_check_enabled` перед каждым тестом — общая dev-БД не изолирована
от ручных правок администратора (та же дисциплина, что и `_reset_registration_gating`
в `test_auth.py`); `db_session` не коммитится в реальную БД, см. `conftest.py`."""
await InstanceSettingsService(db_session).update(SettingsUpdateIn(device_check_enabled=False))
async def test_public_settings_disabled_by_default(
client: httpx.AsyncClient, db_session: AsyncSession
) -> None:
response = await client.get("/api/v1/public/settings")
assert response.status_code == 200, response.text
assert response.json() == {"device_check_enabled": False}
async def test_public_settings_reflects_enabled(
client: httpx.AsyncClient, db_session: AsyncSession
) -> None:
await InstanceSettingsService(db_session).update(SettingsUpdateIn(device_check_enabled=True))
await db_session.commit()
response = await client.get("/api/v1/public/settings")
assert response.status_code == 200, response.text
assert response.json() == {"device_check_enabled": True}
async def test_public_settings_requires_no_auth(
client: httpx.AsyncClient, db_session: AsyncSession
) -> None:
"""Эндпоинт публичный — работает без заголовка `Authorization`, как и обязан
(обе страницы, которым он нужен, доступны до входа в систему)."""
response = await client.get("/api/v1/public/settings")
assert response.status_code == 200, response.text