fix(auth): a member behind the register's clock says the password changed
When a member's clock ran more than its allowance behind the register's, the session after a reset was refused as unavailable (the re-review's tenth finding), after the register had already reset the account and ended every session, and the app read it as "this instance has no password sign-in". It now has a refusal of its own (passwordChanged): the app says the password was changed and to sign in again with the new one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
293ec6b103
commit
09a627d1c7
14 changed files with 60 additions and 7 deletions
|
|
@ -214,5 +214,6 @@
|
|||
"passwordSessionEnded": "Zu viele falsche Codes hintereinander von diesem Gerät: Es wurde abgemeldet. Melde dich wieder mit deinem Passwort und einem Code an.",
|
||||
"passwordAddressUnknown": "Dieses Gerät kennt die Adresse deines Kontos nicht, die das braucht. Melde dich ab und wieder mit deiner Adresse und deinem Passwort an.",
|
||||
"passwordDeleteIntroByMail": "Dein Konto wird mit allem darin gelöscht, in jedem Dienst dieser Instanz. Seine Authenticator-App ist nicht eingerichtet, daher bestätigt es ein Code per E-Mail: Gib dein Passwort ein, dann den Code, den wir an deine Adresse schicken.",
|
||||
"passwordDeleteSendCode": "Code per E-Mail schicken"
|
||||
"passwordDeleteSendCode": "Code per E-Mail schicken",
|
||||
"passwordChangedSignInAgain": "Dein Passwort wurde geändert, aber dieser Dienst konnte dich gerade noch nicht anmelden. Melde dich wieder mit dem neuen Passwort an."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -461,5 +461,6 @@
|
|||
"passwordSessionEnded": "Too many wrong codes in a row from this device: it was signed out. Sign in again with your password and a code.",
|
||||
"passwordAddressUnknown": "This device doesn't know your account's address, which this takes. Sign out, then sign in again with your address and password.",
|
||||
"passwordDeleteIntroByMail": "Your account goes with everything in it, in every service of this instance. Its authenticator isn't set up, so a code by mail confirms it: type your password, then the code we email to your address.",
|
||||
"passwordDeleteSendCode": "Email me a code"
|
||||
"passwordDeleteSendCode": "Email me a code",
|
||||
"passwordChangedSignInAgain": "Your password was changed, but this service couldn't sign you in just yet. Sign in again with the new password."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1392,6 +1392,12 @@ abstract class HalkyonAppLocalizations {
|
|||
/// In en, this message translates to:
|
||||
/// **'Email me a code'**
|
||||
String get passwordDeleteSendCode;
|
||||
|
||||
/// No description provided for @passwordChangedSignInAgain.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Your password was changed, but this service couldn\'t sign you in just yet. Sign in again with the new password.'**
|
||||
String get passwordChangedSignInAgain;
|
||||
}
|
||||
|
||||
class _HalkyonAppLocalizationsDelegate
|
||||
|
|
|
|||
|
|
@ -807,4 +807,8 @@ class HalkyonAppLocalizationsDe extends HalkyonAppLocalizations {
|
|||
|
||||
@override
|
||||
String get passwordDeleteSendCode => 'Code per E-Mail schicken';
|
||||
|
||||
@override
|
||||
String get passwordChangedSignInAgain =>
|
||||
'Dein Passwort wurde geändert, aber dieser Dienst konnte dich gerade noch nicht anmelden. Melde dich wieder mit dem neuen Passwort an.';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -790,6 +790,10 @@ class HalkyonAppLocalizationsEn extends HalkyonAppLocalizations {
|
|||
|
||||
@override
|
||||
String get passwordDeleteSendCode => 'Email me a code';
|
||||
|
||||
@override
|
||||
String get passwordChangedSignInAgain =>
|
||||
'Your password was changed, but this service couldn\'t sign you in just yet. Sign in again with the new password.';
|
||||
}
|
||||
|
||||
/// The translations for English, as used in the United Kingdom (`en_GB`).
|
||||
|
|
|
|||
|
|
@ -462,6 +462,7 @@ PasswordProblem _problemOf(protocol.PasswordRefusal reason) => switch (reason) {
|
|||
protocol.PasswordRefusal.otherInstance => PasswordProblem.instanceUnproven,
|
||||
protocol.PasswordRefusal.codesWait => PasswordProblem.codesWait,
|
||||
protocol.PasswordRefusal.sessionEnded => PasswordProblem.sessionEnded,
|
||||
protocol.PasswordRefusal.passwordChanged => PasswordProblem.passwordChanged,
|
||||
};
|
||||
|
||||
/// Keeps one session in memory, for a client used once.
|
||||
|
|
|
|||
|
|
@ -25,6 +25,10 @@ enum PasswordProblem() {
|
|||
/// device signs in again.
|
||||
sessionEnded,
|
||||
|
||||
/// The password was changed by a reset, but this service couldn't open a
|
||||
/// session for it yet: sign in again with the new password.
|
||||
passwordChanged,
|
||||
|
||||
/// No reset waits for the address.
|
||||
noReset,
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ String passwordProblemText(BuildContext context, PasswordFailed failed) {
|
|||
null => l10n.passwordTooManyAttempts,
|
||||
},
|
||||
PasswordProblem.sessionEnded => l10n.passwordSessionEnded,
|
||||
PasswordProblem.passwordChanged => l10n.passwordChangedSignInAgain,
|
||||
PasswordProblem.noReset => l10n.passwordNoReset,
|
||||
PasswordProblem.passwordExists => l10n.passwordExists,
|
||||
PasswordProblem.addressTaken => l10n.passwordAddressTaken,
|
||||
|
|
|
|||
|
|
@ -63,5 +63,12 @@ void main() {
|
|||
),
|
||||
contains('signed out'),
|
||||
);
|
||||
expect(
|
||||
passwordProblemText(
|
||||
context,
|
||||
const PasswordFailed(PasswordProblem.passwordChanged),
|
||||
),
|
||||
allOf(contains('was changed'), contains('Sign in again')),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,7 +86,13 @@ enum PasswordRefusal implements _isc.SerializableModel {
|
|||
|
||||
/// Ten codes in a row from this signed-in session were wrong: the
|
||||
/// session ended (the re-review's third finding); sign in again.
|
||||
sessionEnded;
|
||||
sessionEnded,
|
||||
|
||||
/// The password was changed (a reset carried out), but this service
|
||||
/// couldn't open a session for it yet (its clock is behind the
|
||||
/// register's): sign in again with the new password. Never "this
|
||||
/// instance has no password sign-in" (the re-review's tenth finding).
|
||||
passwordChanged;
|
||||
|
||||
static PasswordRefusal fromJson(String name) {
|
||||
switch (name) {
|
||||
|
|
@ -130,6 +136,8 @@ enum PasswordRefusal implements _isc.SerializableModel {
|
|||
return PasswordRefusal.codesWait;
|
||||
case 'sessionEnded':
|
||||
return PasswordRefusal.sessionEnded;
|
||||
case 'passwordChanged':
|
||||
return PasswordRefusal.passwordChanged;
|
||||
default:
|
||||
throw ArgumentError(
|
||||
'Value "$name" cannot be converted to "PasswordRefusal"',
|
||||
|
|
|
|||
|
|
@ -86,7 +86,13 @@ enum PasswordRefusal implements _is.SerializableModel {
|
|||
|
||||
/// Ten codes in a row from this signed-in session were wrong: the
|
||||
/// session ended (the re-review's third finding); sign in again.
|
||||
sessionEnded;
|
||||
sessionEnded,
|
||||
|
||||
/// The password was changed (a reset carried out), but this service
|
||||
/// couldn't open a session for it yet (its clock is behind the
|
||||
/// register's): sign in again with the new password. Never "this
|
||||
/// instance has no password sign-in" (the re-review's tenth finding).
|
||||
passwordChanged;
|
||||
|
||||
static PasswordRefusal fromJson(String name) {
|
||||
switch (name) {
|
||||
|
|
@ -130,6 +136,8 @@ enum PasswordRefusal implements _is.SerializableModel {
|
|||
return PasswordRefusal.codesWait;
|
||||
case 'sessionEnded':
|
||||
return PasswordRefusal.sessionEnded;
|
||||
case 'passwordChanged':
|
||||
return PasswordRefusal.passwordChanged;
|
||||
default:
|
||||
throw ArgumentError(
|
||||
'Value "$name" cannot be converted to "PasswordRefusal"',
|
||||
|
|
|
|||
|
|
@ -56,3 +56,8 @@ values:
|
|||
### Ten codes in a row from this signed-in session were wrong: the
|
||||
### session ended (the re-review's third finding); sign in again.
|
||||
- sessionEnded
|
||||
### The password was changed (a reset carried out), but this service
|
||||
### couldn't open a session for it yet (its clock is behind the
|
||||
### register's): sign in again with the new password. Never "this
|
||||
### instance has no password sign-in" (the re-review's tenth finding).
|
||||
- passwordChanged
|
||||
|
|
|
|||
|
|
@ -73,7 +73,9 @@ Future<AuthSuccess> issueSession(
|
|||
/// password or of the account): those here end first, as the register's
|
||||
/// notice to this server will end them, and the new one is made only once
|
||||
/// this server's clock is past that moment, so the notice, whenever it
|
||||
/// arrives, leaves it.
|
||||
/// arrives, leaves it. A clock more than [maxClockSkew] behind is refused
|
||||
/// ([PasswordRefusal.passwordChanged]): the reset is done, and the device
|
||||
/// signs in again with the new password.
|
||||
Future<AuthSuccess> issueSessionAfter(
|
||||
Session session,
|
||||
UuidValue account, {
|
||||
|
|
@ -89,7 +91,8 @@ Future<AuthSuccess> issueSessionAfter(
|
|||
'set both by NTP.',
|
||||
level: LogLevel.warning,
|
||||
);
|
||||
throw PasswordRefusedException(reason: PasswordRefusal.unavailable);
|
||||
// The reset is done: the device signs in again, with the new password.
|
||||
throw PasswordRefusedException(reason: PasswordRefusal.passwordChanged);
|
||||
}
|
||||
if (!wait.isNegative) {
|
||||
await Future<void>.delayed(wait + const Duration(milliseconds: 1));
|
||||
|
|
|
|||
|
|
@ -1159,7 +1159,7 @@ void main() {
|
|||
isA<PasswordRefusedException>().having(
|
||||
(error) => error.reason,
|
||||
'reason',
|
||||
PasswordRefusal.unavailable,
|
||||
PasswordRefusal.passwordChanged,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in a new issue