mirror of
https://github.com/Zproger/Aurum.git
synced 2026-10-13 11:06:40 +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:
@@ -3,7 +3,7 @@ import { Logo } from "@/components/layout/Logo";
|
||||
import { Button } from "@/components/ui/Button";
|
||||
import { Card, CardContent } from "@/components/ui/Card";
|
||||
import { Input, Label } from "@/components/ui/Input";
|
||||
import { buildBasicAuthHeader, checkCredentials, setCredentials } from "@/lib/auth";
|
||||
import { buildBasicAuthHeader, checkCredentials, isRememberSupported, setCredentials } from "@/lib/auth";
|
||||
import { useTranslation } from "@/lib/i18n";
|
||||
|
||||
type Status = "idle" | "submitting" | "invalid" | "unreachable";
|
||||
@@ -19,13 +19,18 @@ export function LoginScreen() {
|
||||
const [password, setPassword] = useState("");
|
||||
const [remember, setRemember] = useState(false);
|
||||
const [status, setStatus] = useState<Status>("idle");
|
||||
// Remembering encrypts the credential with WebCrypto, which browsers only
|
||||
// expose in a secure context — over plain HTTP to a LAN address there's no
|
||||
// safe way to persist it, so the option is hidden rather than offered and
|
||||
// quietly ignored. See lib/credentialVault.ts.
|
||||
const canRemember = isRememberSupported();
|
||||
|
||||
const handleSubmit = async (event: FormEvent) => {
|
||||
event.preventDefault();
|
||||
setStatus("submitting");
|
||||
const result = await checkCredentials(buildBasicAuthHeader(username, password));
|
||||
if (result === "ok") {
|
||||
setCredentials(username, password, remember);
|
||||
setCredentials(username, password, canRemember && remember);
|
||||
return;
|
||||
}
|
||||
setStatus(result === "unauthorized" ? "invalid" : "unreachable");
|
||||
@@ -67,15 +72,19 @@ export function LoginScreen() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<label className="flex items-center gap-2 text-xs text-text-muted">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={remember}
|
||||
onChange={(event) => setRemember(event.target.checked)}
|
||||
className="h-3.5 w-3.5 accent-text-primary"
|
||||
/>
|
||||
{t("auth.rememberMe")}
|
||||
</label>
|
||||
{canRemember ? (
|
||||
<label className="flex items-center gap-2 text-xs text-text-muted">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={remember}
|
||||
onChange={(event) => setRemember(event.target.checked)}
|
||||
className="h-3.5 w-3.5 accent-text-primary"
|
||||
/>
|
||||
{t("auth.rememberMe")}
|
||||
</label>
|
||||
) : (
|
||||
<p className="text-xs text-text-muted">{t("auth.sessionOnlyHint")}</p>
|
||||
)}
|
||||
|
||||
{status === "invalid" && <p className="text-sm text-danger">{t("auth.errorInvalidCredentials")}</p>}
|
||||
{status === "unreachable" && <p className="text-sm text-danger">{t("auth.errorUnreachable")}</p>}
|
||||
|
||||
Reference in New Issue
Block a user