fix(auth): a password set up for good stays, through resets too

`password.set` replaced a password whenever the account had no confirmed
authenticator, which is also the state an authenticator's or the
account's reset leaves behind; and in that state a code by mail or GitHub
signed in again. So whoever got a session another way could replace the
password and the keys, skipping the reset's 72 hours.

An account had a confirmed authenticator while it has one or a reset of it
stands (`hadConfirmedAuthenticator`: the reset's row stays until a new
authenticator is confirmed). Then `password.set` is refused
(`passwordExists`), and the account signs in with its password alone:
codes by mail and GitHub are refused (`usePassword`), the latter through
`SignInWays.passwordOnly`, which members read from the register.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-20 14:16:23 +02:00
commit 4e425cb17f
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -20,14 +20,17 @@ abstract class SignInWays
required this.email,
bool? password,
bool? authenticator,
bool? passwordOnly,
}) : password = password ?? false,
authenticator = authenticator ?? false;
authenticator = authenticator ?? false,
passwordOnly = passwordOnly ?? false;
factory SignInWays({
required bool gitHub,
required bool email,
bool? password,
bool? authenticator,
bool? passwordOnly,
}) = _SignInWaysImpl;
factory SignInWays.fromJson(Map<String, dynamic> jsonSerialization) {
@ -40,6 +43,9 @@ abstract class SignInWays
authenticator: jsonSerialization['authenticator'] == null
? null
: _isc.BoolJsonExtension.fromJson(jsonSerialization['authenticator']),
passwordOnly: jsonSerialization['passwordOnly'] == null
? null
: _isc.BoolJsonExtension.fromJson(jsonSerialization['passwordOnly']),
);
}
@ -53,6 +59,11 @@ abstract class SignInWays
/// A confirmed authenticator: the password signs in only with one.
bool authenticator;
/// The account signs in with its password alone: it had a confirmed
/// authenticator (a reset may have left it without one since), so
/// neither a code by mail nor GitHub signs into it (ADR 0017).
bool passwordOnly;
/// Returns a shallow copy of this [SignInWays]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
@ -61,6 +72,7 @@ abstract class SignInWays
bool? email,
bool? password,
bool? authenticator,
bool? passwordOnly,
});
@override
Map<String, dynamic> toJson() {
@ -70,6 +82,7 @@ abstract class SignInWays
'email': email,
'password': password,
'authenticator': authenticator,
'passwordOnly': passwordOnly,
};
}
@ -81,6 +94,7 @@ abstract class SignInWays
'email': email,
'password': password,
'authenticator': authenticator,
'passwordOnly': passwordOnly,
};
}
@ -96,11 +110,13 @@ class _SignInWaysImpl extends SignInWays {
required bool email,
bool? password,
bool? authenticator,
bool? passwordOnly,
}) : super._(
gitHub: gitHub,
email: email,
password: password,
authenticator: authenticator,
passwordOnly: passwordOnly,
);
/// Returns a shallow copy of this [SignInWays]
@ -112,12 +128,14 @@ class _SignInWaysImpl extends SignInWays {
bool? email,
bool? password,
bool? authenticator,
bool? passwordOnly,
}) {
return SignInWays(
gitHub: gitHub ?? this.gitHub,
email: email ?? this.email,
password: password ?? this.password,
authenticator: authenticator ?? this.authenticator,
passwordOnly: passwordOnly ?? this.passwordOnly,
);
}
}