mirror of
https://github.com/Zproger/Aurum.git
synced 2026-11-05 23:37:23 +07:00
security: TLS, проверка Host, шифрованное запоминание входа и аудит-лог
- TLS через подключаемый overlay docker-compose.tls.yml + Caddyfile: Caddy сам получает и продлевает сертификат Let's Encrypt, HSTS добавляется на уровне прокси — намеренно там, а не в nginx.conf, иначе установки без TLS заперли бы сами себя; - проверка заголовка Host против DNS rebinding: AURUM_ALLOWED_HOSTS, по умолчанию localhost и 127.0.0.1, чужой Host получает 421 с подсказкой, какую переменную поправить; - «запомнить меня на 7 дней» вернулось в безопасном виде: пароль шифруется AES-GCM ключом с extractable: false в IndexedDB, вместо прежнего base64 в localStorage, который разворачивался обратно в пароль одним atob. Требует secure context, поэтому по обычному HTTP к LAN-адресу чекбокс не показывается, а не притворяется рабочим; - аудит-лог необратимых операций (восстановление бэкапа, массовое создание транзакций, удаление счёта) в лог контейнера. Не в БД сознательно: таблица аудита исчезла бы ровно в том событии, которое должна объяснять; - README: раздел про TLS и таблица двух переменных, решающих, кто может достучаться до инстанса.
This commit is contained in:
@@ -2,7 +2,7 @@ import { type ReactNode, useEffect, useState } from "react";
|
||||
import { Logo } from "@/components/layout/Logo";
|
||||
import { LoginScreen } from "@/components/auth/LoginScreen";
|
||||
import { NoAuthModal } from "@/components/auth/NoAuthModal";
|
||||
import { checkCredentials, useAuthHeader } from "@/lib/auth";
|
||||
import { checkCredentials, useAuthHeader, whenAuthRestored } from "@/lib/auth";
|
||||
|
||||
// "not-configured" and "authenticated" both render `children` the same way,
|
||||
// but they must stay distinct: only "not-configured" means this instance has
|
||||
@@ -30,13 +30,20 @@ export function LoginGate({ children }: { children: ReactNode }) {
|
||||
return;
|
||||
}
|
||||
let cancelled = false;
|
||||
checkCredentials(null).then((result) => {
|
||||
if (cancelled) return;
|
||||
// A network/backend error here isn't an auth problem — don't block
|
||||
// the user behind a login screen for an unrelated outage, let the
|
||||
// app's own per-page error states (e.g. dashboard.errorLoading) explain it.
|
||||
setProbe(result === "unauthorized" ? "required" : "not-configured");
|
||||
});
|
||||
// Wait for the encrypted "remember me" credential to be read back before
|
||||
// concluding anything: it arrives asynchronously (IndexedDB + WebCrypto),
|
||||
// and probing first would show the login screen for a moment to someone
|
||||
// who is, in fact, already logged in. When it does arrive it sets the
|
||||
// header, which re-runs this effect down the authenticated path above.
|
||||
void whenAuthRestored()
|
||||
.then(() => (cancelled ? null : checkCredentials(null)))
|
||||
.then((result) => {
|
||||
if (cancelled || result === null) return;
|
||||
// A network/backend error here isn't an auth problem — don't block
|
||||
// the user behind a login screen for an unrelated outage, let the
|
||||
// app's own per-page error states (e.g. dashboard.errorLoading) explain it.
|
||||
setProbe(result === "unauthorized" ? "required" : "not-configured");
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user