bf0d130ee8
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 на шестой попытке входа, секретов в логах нет.
49 lines
1.6 KiB
TypeScript
49 lines
1.6 KiB
TypeScript
import { Navigate, Outlet, useLocation } from 'react-router';
|
|
|
|
import { ErrorNotice, LoadingNotice } from '@/components/ui/ErrorNotice';
|
|
import { useCurrentUser } from '@/lib/hooks';
|
|
import { isUnauthorized } from '@/lib/http';
|
|
|
|
interface AuthGuardProps {
|
|
/**
|
|
* Если `true`, пользователей с незавершённым онбордингом пускаем внутрь
|
|
* (нужно для /onboarding и /settings).
|
|
*/
|
|
allowIncompleteOnboarding?: boolean;
|
|
}
|
|
|
|
/**
|
|
* Защита маршрутов: проверяет GET /users/@me.
|
|
* Неавторизованных отправляет на /login, не прошедших онбординг — на /onboarding.
|
|
*/
|
|
export function AuthGuard({ allowIncompleteOnboarding = false }: AuthGuardProps) {
|
|
const location = useLocation();
|
|
const currentUser = useCurrentUser();
|
|
|
|
if (currentUser.isPending) {
|
|
return (
|
|
<div className="flex min-h-full items-center justify-center p-6">
|
|
<LoadingNotice />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (currentUser.isError) {
|
|
if (isUnauthorized(currentUser.error)) {
|
|
return <Navigate to="/login" replace state={{ from: location.pathname }} />;
|
|
}
|
|
return (
|
|
<div className="mx-auto flex min-h-full w-full max-w-md flex-col justify-center p-6">
|
|
<ErrorNotice error={currentUser.error} onRetry={() => void currentUser.refetch()} />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const user = currentUser.data;
|
|
if (!allowIncompleteOnboarding && !user.onboarding_completed) {
|
|
return <Navigate to="/onboarding" replace />;
|
|
}
|
|
|
|
return <Outlet context={{ user }} />;
|
|
}
|