Zwei-Faktor-Anmeldung bei Calendarr
Deine Zugangsdaten stimmten. Der Fehler lag im gewählten Endpunkt:
auth_router.py:81 if user.totp_enabled:
raise HTTPException(401, detail="2fa_required")
/api/auth/token nimmt nur ein Formular mit Benutzername und Passwort entgegen
und weist Konten mit zweitem Faktor grundsätzlich ab — mit demselben 401 wie
ein falsches Passwort. Deshalb stand da „Benutzername oder Passwort stimmen
nicht", obwohl beides stimmte.
Angemeldet wird jetzt über /api/auth/login: der Weg nimmt den Code entgegen
und kennt remember_me. Das ist hier keine Bequemlichkeit, sondern die
Bedingung — mit zweitem Faktor kann Onyx sich nicht selbst neu anmelden, den
Code hat nur der Mensch. Ein gewöhnliches Token hielte sieben Tage,
remember_me hält hundertachtzig.
Damit wandert auch das Token in den Schlüsselbund. Bei einem Konto mit
zweitem Faktor ist es das einzige, womit Onyx noch hineinkommt; ginge es beim
Beenden verloren, müsste man sich bei jedem Start neu anmelden.
Die drei Fälle sind jetzt auseinandergehalten und einzeln beschriftet:
falsches Passwort, fehlender Code, falscher Code. Der Unterschied ist der
zwischen „Passwort neu tippen" und „App aufmachen" — und er steckt beim
Server nur im Begleittext eines 401.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -289,6 +289,7 @@ private struct CalendarrServerSettings: View {
|
||||
@State private var server = ""
|
||||
@State private var username = ""
|
||||
@State private var password = ""
|
||||
@State private var totpCode = ""
|
||||
@State private var isChecking = false
|
||||
@State private var failure: String?
|
||||
|
||||
@@ -324,6 +325,11 @@ private struct CalendarrServerSettings: View {
|
||||
.textContentType(.username)
|
||||
SecureField(text: $password) { Text("options.calendar.server.password") }
|
||||
.textContentType(.password)
|
||||
// Nur ausfüllen, wer einen zweiten Faktor hat. Ein Pflichtfeld
|
||||
// wäre es für alle anderen eine Frage, die sie nicht beantworten
|
||||
// können.
|
||||
TextField(text: $totpCode) { Text("options.calendar.server.totp") }
|
||||
.textContentType(.oneTimeCode)
|
||||
|
||||
HStack {
|
||||
if let failure {
|
||||
@@ -357,12 +363,15 @@ private struct CalendarrServerSettings: View {
|
||||
Task {
|
||||
defer { isChecking = false }
|
||||
switch await CalendarrLiveSource.check(server: url, username: username,
|
||||
password: password) {
|
||||
case .success:
|
||||
credentials.save(server: url, username: username, password: password)
|
||||
// Das Passwort nicht im Formular stehen lassen — es liegt jetzt
|
||||
// im Schlüsselbund, und ein zweiter Ort ist einer zu viel.
|
||||
password: password,
|
||||
totpCode: totpCode) {
|
||||
case .success(let token):
|
||||
credentials.save(server: url, username: username,
|
||||
password: password, token: token)
|
||||
// Passwort und Code nicht im Formular stehen lassen — beide
|
||||
// liegen jetzt dort, wo sie hingehören.
|
||||
password = ""
|
||||
totpCode = ""
|
||||
model.calendarrAccountChanged()
|
||||
case .failure(let error):
|
||||
failure = error.localizedDescription
|
||||
|
||||
Reference in New Issue
Block a user