feat(cli): включение 2FA инстанс-админа командой totp-setup

2FA обязательна для инстанс-администраторов, но до её включения вход закрыт —
получался замкнутый круг. Добавлены команды обслуживания:

- `glchat totp-setup --email <admin>`: создаёт секрет, подтверждает его кодом,
  печатает секрет, otpauth-ссылку и 8 резервных кодов (каждый одноразовый);
- `glchat totp-reset --email <admin>`: удаляет секрет при потере устройства;
- код `auth.2fa_enrollment_required` с подсказкой, какую команду выполнить;
- установщик: флаг `--admin-2fa` для автоматического включения (по умолчанию
  печатает подсказку, чтобы секреты не оседали в логах установки).

Проверено сквозным прогоном локально: bootstrap → 403 на входе без 2FA →
totp-setup → вход с TOTP-кодом → профиль, серверы, комнаты, роли, участники,
аудит, создание сервера админом, 429 на шестой попытке входа, секретов в логах
нет.
This commit is contained in:
2026-09-19 21:53:35 +03:00
parent c059cd9f51
commit bf0d130ee8
23 changed files with 1115 additions and 269 deletions
@@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next';
import { SelectField } from '@/components/ui/Field';
import { Card } from '@/components/ui/primitives';
import { supportedLanguages, type SupportedLanguage } from '@/i18n';
import { useUiStore, type Theme } from '@/stores/ui';
import { useUiStore } from '@/stores/ui';
/** Внешний вид: тема и язык. Хранятся локально, без запросов к серверу. */
export default function AppearanceSettingsPage() {
@@ -27,7 +27,7 @@ export default function AppearanceSettingsPage() {
label={t('settings.appearance.theme')}
hint={t('settings.appearance.themeHint')}
value={theme}
onChange={(value) => setTheme(value === 'light' ? 'light' : ('dark' as Theme))}
onChange={(value) => setTheme(value === 'light' ? 'light' : 'dark')}
options={[
{ value: 'dark', label: t('theme.dark') },
{ value: 'light', label: t('theme.light') },