feat(api): authentication — login, two-step TOTP, invites and resets

Sessions are stored as hashes, so a database dump contains nothing that
can be pasted into a browser. A login that has passed the password but
not the second factor is modelled as a pending session: it expires, is
revocable, and reaches exactly one endpoint.

Signup is invite-only and takes the email address from the invite rather
than the form, so a link cannot be redirected to somebody else. The
first admin is created by an endpoint that refuses once any account
exists, checked and inserted in one transaction because it is
necessarily unauthenticated.

Login failures and forgotten-password requests answer identically
whether or not the address has an account — otherwise the form is an
account-existence oracle. Password rules are a length floor and nothing
else: composition rules push people towards worse passwords.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kristian Wind
2026-08-16 23:31:29 +02:00
co-authored by Claude Opus 5
parent 8251e69155
commit 1bb644f4d1
15 changed files with 2943 additions and 10 deletions
+12 -5
View File
@@ -50,21 +50,28 @@ func TestMigrateIsIdempotent(t *testing.T) {
if err != nil {
t.Fatalf("first open: %v", err)
}
var first int
if err := db.QueryRow(`SELECT count(*) FROM schema_migrations`).Scan(&first); err != nil {
t.Fatalf("count migrations: %v", err)
}
db.Close()
if first == 0 {
t.Fatal("no migrations were applied")
}
// Re-opening must not try to re-run 0001 and fail on "table already exists".
// Re-opening must not re-run anything and fail on "table already exists".
db2, err := Open(path)
if err != nil {
t.Fatalf("second open: %v", err)
}
defer db2.Close()
var n int
if err := db2.QueryRow(`SELECT count(*) FROM schema_migrations`).Scan(&n); err != nil {
var second int
if err := db2.QueryRow(`SELECT count(*) FROM schema_migrations`).Scan(&second); err != nil {
t.Fatalf("count migrations: %v", err)
}
if n != 1 {
t.Errorf("schema_migrations has %d rows, want 1", n)
if second != first {
t.Errorf("re-opening applied %d further migrations", second-first)
}
}