fix(auth): an account with a password deletes itself only with it and a code
A session alone deleted any account, with everything encrypted under its keys (the second reading's lows; decided by the developer: the password and a code, as a change of password takes). account.delete now takes a DeletionProof (the password's SRP proof for a beginChange challenge, a TOTP or recovery code, the host the device addressed); the register checks it (requireOwnerToDelete), a member passing it on bound to its own host. Accounts without a password, anonymous ones among them, delete with their session as before. In the app, deleting an account with a password asks for it and a code in a sheet of its own; a wrong one deletes nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
c4f863ff8b
commit
d53844bc93
2 changed files with 873 additions and 32 deletions
|
|
@ -173,6 +173,54 @@ void main() {
|
|||
expect(find.text('Signed out'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('deleting an account with a password asks for it and a '
|
||||
'code, which a wrong one fails, deleting nothing', (tester) async {
|
||||
// The first person the fake password gateway makes.
|
||||
const person = '00000000-0000-4000-8000-000000000001';
|
||||
gateway = FakeAuthGateway(
|
||||
status: const SignedIn(authUserId: person, isIdentified: true),
|
||||
);
|
||||
await start(tester);
|
||||
dependencies.passwordGateway.addAccount(
|
||||
'anna@example.org',
|
||||
password: 'correct horse',
|
||||
crypto: dependencies.crypto,
|
||||
);
|
||||
Finder field(String label) => find.widgetWithText(TextField, label);
|
||||
|
||||
await tester.tap(find.text('Delete account'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('Delete account').last);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(gateway.accountDeletions, 0);
|
||||
expect(field('Current password'), findsOneWidget);
|
||||
await tester.enterText(field('Current password'), 'wrong horse');
|
||||
await tester.enterText(
|
||||
field('Code from your authenticator app'),
|
||||
fakeTotpCode,
|
||||
);
|
||||
await tester.tap(find.text('Delete account').last);
|
||||
await tester.pumpAndSettle();
|
||||
expect(
|
||||
find.text("The address, the password or the code isn't right."),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(gateway.accountDeletions, 0);
|
||||
expect(dependencies.passwordGateway.accounts, contains('anna@example.org'));
|
||||
|
||||
await tester.enterText(field('Current password'), 'correct horse');
|
||||
await tester.tap(find.text('Delete account').last);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(gateway.accountDeletions, 1);
|
||||
expect(
|
||||
dependencies.passwordGateway.accounts,
|
||||
isNot(contains('anna@example.org')),
|
||||
);
|
||||
expect(find.text('Signed out'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('speaks German', (tester) async {
|
||||
await start(tester);
|
||||
final german = lookupHalkyonAppLocalizations(const Locale('de'));
|
||||
|
|
|
|||
|
|
@ -302,6 +302,28 @@ void main() {
|
|||
expect(anna.password, 'correct horse');
|
||||
});
|
||||
|
||||
test('deletes the account with the password and a code alone, then '
|
||||
'signed out here; a wrong one or none deletes nothing', () async {
|
||||
await expectLater(
|
||||
passwords.deleteAccount(password: 'wrong', totpCode: fakeTotpCode),
|
||||
failsWith(PasswordProblem.wrongCredentials),
|
||||
);
|
||||
await expectLater(
|
||||
passwords.deleteAccount(password: 'correct horse'),
|
||||
failsWith(PasswordProblem.wrongCredentials),
|
||||
);
|
||||
expect(gateway.accounts, contains('anna@example.org'));
|
||||
expect(auth.status, isA<SignedIn>());
|
||||
|
||||
await passwords.deleteAccount(
|
||||
password: 'correct horse',
|
||||
totpCode: fakeTotpCode,
|
||||
);
|
||||
|
||||
expect(gateway.accounts, isNot(contains('anna@example.org')));
|
||||
expect(auth.status, const SignedOut());
|
||||
});
|
||||
|
||||
test('makes a new recovery phrase, which the instance takes for the '
|
||||
'password and a code alone', () async {
|
||||
final phrase = await passwords.newPhrase();
|
||||
|
|
|
|||
Loading…
Reference in a new issue