fix(auth): Einmalkennwoerter nicht zweimal annehmen

Ein TOTP-Code blieb rund 90 Sekunden gueltig und liess sich darin beliebig
oft verwenden. RFC 6238 verlangt in Abschnitt 5.2 ausdruecklich das
Gegenteil: derselbe Code darf kein zweites Mal angenommen werden.

Ohne diese Sperre kann ein abgefangener Code - ueber die Schulter gesehen,
von einer weiterleitenden Phishing-Seite, von Schadsoftware vom Bildschirm
gelesen - die restliche Gueltigkeitsdauer ueber wiederverwendet werden.

verifyTotp gibt dafuer jetzt ueber matchTotp das Zeitfenster zurueck, das
gepasst hat; angenommen wird nur, was neuer ist als das zuletzt eingeloeste.
Der Vergleich bleibt dabei in konstanter Zeit - es wird weiterhin nicht
fruehzeitig abgebrochen.

Auch die Aktivierung verbrennt ihr Fenster: der Code, der eben
scharfgeschaltet hat, soll nicht Sekunden spaeter noch zum Anmelden taugen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-27 00:14:10 +02:00
co-authored by Claude Opus 5
parent 72e5eb43c4
commit 27f51b6be8
7 changed files with 101 additions and 21 deletions
+3
View File
@@ -75,6 +75,8 @@ export interface PendingLoginState {
isActive: boolean;
/** Entschluesselter TOTP-Schluessel; null, wenn er nicht lesbar ist. */
totpSecret: string | null;
/** Zuletzt eingeloestes Zeitfenster – gegen die Wiederverwendung. */
totpLastCounter: number | null;
};
}
@@ -104,6 +106,7 @@ export async function readPendingLogin(): Promise<PendingLoginState | null> {
role: pending.user.role,
isActive: pending.user.isActive,
totpSecret: tryDecryptSecret(pending.user.totpSecretCipher),
totpLastCounter: pending.user.totpLastCounter,
},
};
}