feat: slow down repeated failed logins

The login endpoint answered guesses as fast as they arrived, against a
single static password.

Wrong attempts now back off — 1s, 2s, 4s, up to 30s — and a correct one
clears the count. It is a single counter rather than a table keyed by
address: there is only one account, so an attacker gains nothing by
rotating source IPs, and there is no per-client map to grow. The cap is
there so that someone hammering the login delays the owner by half a
minute rather than locking them out.

Wrong credentials now answer 401 rather than 400. The server also warns
at startup if DIARY_PASSWORD is still the value from .env.example.
This commit is contained in:
nvrl committed 2026-09-05 09:47:27 +02:00
1 parent f0135d34ac
commit 41d2d7ef77
7 files changed
+119 -3

No files matched your search

+9 -1
View File
@@ -1,4 +1,5 @@
<script lang="ts">
import { ApiError } from './api'
import { diary } from './store.svelte'
let username = $state('')
@@ -18,7 +19,14 @@
try {
await diary.login(username, password)
} catch (e) {
error = e instanceof Error ? e.message : 'login failed'
// The server answers a wrong password with a bare 401; the throttle's
// "wait Ns" message is worth showing verbatim.
error =
e instanceof ApiError && e.status === 401
? 'invalid credentials'
: e instanceof Error
? e.message
: 'login failed'
password = ''
} finally {
busy = false