fix(app): no address in debug logs, and deleting goes on whatever reading the account met
The reading of the full reading's fixes found that SignInWaysState kept Equatable's default text, which debug builds log with the address, and that an unreadable sign_in.json logged its FormatException, whose message holds the file's text; and that the account section's Delete caught only refusals, so a keyring that couldn't be read escaped and the tap did nothing. The state says only whether an address is kept; the file's error logs its type alone. Reading what keeps the account counts the keys as not here where the keyring can't be read, and Delete goes on whatever the reading met, failing as the session bloc says if it must. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
16ad925095
commit
28fb6c0309
6 changed files with 77 additions and 4 deletions
|
|
@ -49,4 +49,11 @@ final class const SignInWaysState({
|
||||||
}) with Equatable {
|
}) with Equatable {
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [loaded, ways, emailAddress];
|
List<Object?> get props => [loaded, ways, emailAddress];
|
||||||
|
|
||||||
|
/// Never the address: blocs' states are logged (the reading of the
|
||||||
|
/// fixes).
|
||||||
|
@override
|
||||||
|
String toString() =>
|
||||||
|
'SignInWaysState(loaded: $loaded, ways: $ways, '
|
||||||
|
'address: ${emailAddress == null ? 'none' : 'kept'})';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,8 @@ class FileSignInAddresses(final Directory _people) implements SignInAddresses {
|
||||||
} on IOException {
|
} on IOException {
|
||||||
return null;
|
return null;
|
||||||
} on FormatException catch (error) {
|
} on FormatException catch (error) {
|
||||||
log('Unreadable sign_in.json', name: 'Auth', error: error);
|
// Its type only: its message holds the file's text, the address.
|
||||||
|
log('Unreadable sign_in.json: ${error.runtimeType}', name: 'Auth');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:halkyon_app/l10n/halkyon_app_localizations.dart';
|
import 'package:halkyon_app/l10n/halkyon_app_localizations.dart';
|
||||||
import 'package:halkyon_app/src/app/app_scope.dart';
|
import 'package:halkyon_app/src/app/app_scope.dart';
|
||||||
|
|
@ -129,8 +131,13 @@ class const AccountTiles({
|
||||||
final AccountSecurity? security;
|
final AccountSecurity? security;
|
||||||
try {
|
try {
|
||||||
security = identified ? await passwords.security() : null;
|
security = identified ? await passwords.security() : null;
|
||||||
} on PasswordFailed {
|
} on Object catch (error) {
|
||||||
// Unreachable: deleting would fail too, as the session bloc says.
|
// Unreachable, or anything else (the reading of the fixes: a failure
|
||||||
|
// that isn't a refusal escaped, and the tap did nothing): deleting
|
||||||
|
// goes on, and fails as the session bloc says if it must.
|
||||||
|
if (error is! PasswordFailed) {
|
||||||
|
log('Reading the account failed: ${error.runtimeType}', name: 'Auth');
|
||||||
|
}
|
||||||
session.add(const SessionEventDeleteRequested());
|
session.add(const SessionEventDeleteRequested());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:developer';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:clock/clock.dart';
|
import 'package:clock/clock.dart';
|
||||||
|
|
@ -427,10 +428,21 @@ class PasswordRepository(
|
||||||
authenticators: ways.password
|
authenticators: ways.password
|
||||||
? await _gateway.authenticators()
|
? await _gateway.authenticators()
|
||||||
: const <AccountAuthenticator>[],
|
: const <AccountAuthenticator>[],
|
||||||
keysHere: await _keys.read(person) != null,
|
keysHere: await _keysKept(person),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether [person]'s keys are in the keyring; not where it can't be read
|
||||||
|
/// (locked, its service gone), which says nothing else of the account.
|
||||||
|
Future<bool> _keysKept(String person) async {
|
||||||
|
try {
|
||||||
|
return await _keys.read(person) != null;
|
||||||
|
} on Object catch (error) {
|
||||||
|
log('The keyring could not be read: ${error.runtimeType}', name: 'Keys');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Dismisses the warning that a session of the account was ended for its
|
/// Dismisses the warning that a session of the account was ended for its
|
||||||
/// wrong proofs: the signed-in person's, or [pending]'s before it is put
|
/// wrong proofs: the signed-in person's, or [pending]'s before it is put
|
||||||
/// in place. The session alone does: it hides a warning, nothing more.
|
/// in place. The session alone does: it hides a warning, nothing more.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:halkyon_app/halkyon_app.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
test("what the sign-in ways' state logs never holds the address (the "
|
||||||
|
'reading of the fixes)', () {
|
||||||
|
const state = SignInWaysState(
|
||||||
|
loaded: true,
|
||||||
|
emailAddress: 'anna@example.org',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect('$state', isNot(contains('anna')));
|
||||||
|
expect('$state', contains('loaded'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,22 @@
|
||||||
import 'package:clock/clock.dart';
|
import 'package:clock/clock.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:halkyon_app/halkyon_app.dart';
|
import 'package:halkyon_app/halkyon_app.dart';
|
||||||
import 'package:halkyon_app/testing.dart';
|
import 'package:halkyon_app/testing.dart';
|
||||||
|
|
||||||
|
/// A keyring the system won't read: locked, or its service gone.
|
||||||
|
final class _UnreadableKeys() implements AccountKeyStore {
|
||||||
|
@override
|
||||||
|
Future<void> keep(String person, UnlockedAccountKeys keys) async {}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<UnlockedAccountKeys?> read(String person) =>
|
||||||
|
Future.error(PlatformException(code: 'locked'));
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> forgetAllBut(String? person) async {}
|
||||||
|
}
|
||||||
|
|
||||||
Matcher failsWith(PasswordProblem problem) => throwsA(
|
Matcher failsWith(PasswordProblem problem) => throwsA(
|
||||||
isA<PasswordFailed>().having((failed) => failed.problem, 'problem', problem),
|
isA<PasswordFailed>().having((failed) => failed.problem, 'problem', problem),
|
||||||
);
|
);
|
||||||
|
|
@ -457,6 +471,23 @@ void main() {
|
||||||
expect(security.finished, isTrue);
|
expect(security.finished, isTrue);
|
||||||
expect(security.keysHere, isTrue);
|
expect(security.keysHere, isTrue);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("says what keeps the account where the keyring can't be read, "
|
||||||
|
'the keys counted as not here (the reading of the fixes)', () async {
|
||||||
|
final unreadable = PasswordRepository(
|
||||||
|
gateway,
|
||||||
|
crypto,
|
||||||
|
_UnreadableKeys(),
|
||||||
|
auth,
|
||||||
|
addresses,
|
||||||
|
);
|
||||||
|
addTearDown(unreadable.dispose);
|
||||||
|
|
||||||
|
final security = await unreadable.security();
|
||||||
|
|
||||||
|
expect(security.hasPassword, isTrue);
|
||||||
|
expect(security.keysHere, isFalse);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue