feat(app)!: no anonymous accounts on the device (ADR 0020, D4, D25)
Every account has a way in, so the apps make no anonymous account any more. The start screen shows whenever nobody is signed in, a first start included, and offers SimpleLogin, a password and GitHub for those who have it, with no start without an account. An instance out of reach is said so, with Try again, and one offering no way to create an account too. An anonymous account's session left from before is deleted as the app starts (D25), falling back to signing out on the device. What only anonymous accounts needed goes: signing in anonymously, the signed-out mark, adding GitHub to an account and switching to a GitHub account from the Profile tab (with its Cancel, the only one), an anonymous device's own keys, the warning that signing out deletes the account, and the texts about keeping progress. Settings says on which instance the account is signed in. Learn's publishing, reports and suggestions and its learner count no longer ask whether the person has an identity: everyone signed in has one. The server still offers anonymous sign-in until the next commit, so older apps keep working until v0.20.0 raises the minimum build. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
fad12b4411
commit
5b9df73756
8 changed files with 981 additions and 3339 deletions
|
|
@ -27,7 +27,7 @@ void main() {
|
|||
|
||||
setUp(() {
|
||||
now = DateTime.utc(2026, 10, 20, 12);
|
||||
auth = FakeAuthGateway();
|
||||
auth = FakeAuthGateway(status: const SignedOut());
|
||||
gateway = FakePasswordGateway(auth);
|
||||
crypto = FakeAccountCrypto();
|
||||
passwords = PasswordRepository(
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ void main() {
|
|||
late FakePasswordAccount anna;
|
||||
|
||||
setUp(() async {
|
||||
auth = FakeAuthGateway();
|
||||
auth = FakeAuthGateway(status: const SignedOut());
|
||||
gateway = FakePasswordGateway(auth);
|
||||
crypto = FakeAccountCrypto();
|
||||
passwords = PasswordRepository(
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ void main() {
|
|||
|
||||
setUp(() {
|
||||
now = DateTime.utc(2026, 10, 20, 12);
|
||||
auth = FakeAuthGateway();
|
||||
auth = FakeAuthGateway(status: const SignedOut());
|
||||
gateway = FakePasswordGateway(auth);
|
||||
crypto = FakeAccountCrypto();
|
||||
passwords = PasswordRepository(
|
||||
|
|
@ -190,9 +190,9 @@ void main() {
|
|||
expect(auth.status, const SignedOut());
|
||||
});
|
||||
|
||||
test('gives a signed-in anonymous person a password with an address a '
|
||||
'code proves, the session staying theirs', () async {
|
||||
await auth.signInAnonymously();
|
||||
test('gives one signed in with GitHub a password with an address a code '
|
||||
'proves, the session staying theirs (ADR 0017, D2)', () async {
|
||||
await auth.switchToGitHub();
|
||||
final person = (auth.status as SignedIn).authUserId;
|
||||
final cubit = setUpAs(AccountSetupMode.setPassword)
|
||||
..addressEntered('anna@example.org');
|
||||
|
|
@ -213,7 +213,7 @@ void main() {
|
|||
test('gives a signed-in person with an address their first password only '
|
||||
'with a code mailed to that address for it, never with the session '
|
||||
'alone (the fourth reading)', () async {
|
||||
await auth.signInAnonymously();
|
||||
await auth.switchToGitHub();
|
||||
final person = (auth.status as SignedIn).authUserId;
|
||||
final first = setUpAs(AccountSetupMode.setPassword)
|
||||
..addressEntered('anna@example.org');
|
||||
|
|
@ -280,7 +280,7 @@ void main() {
|
|||
password: 'correct horse',
|
||||
crypto: crypto,
|
||||
);
|
||||
await auth.signInAnonymously();
|
||||
await auth.switchToGitHub();
|
||||
final cubit = setUpAs(AccountSetupMode.setPassword)
|
||||
..addressEntered('anna@example.org');
|
||||
await cubit.passwordEntered('another horse', 'another horse');
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ void main() {
|
|||
|
||||
setUp(() {
|
||||
now = DateTime.utc(2026, 10, 20, 12);
|
||||
auth = FakeAuthGateway();
|
||||
auth = FakeAuthGateway(status: const SignedOut());
|
||||
gateway = FakePasswordGateway(auth);
|
||||
crypto = FakeAccountCrypto();
|
||||
passwords = PasswordRepository(
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ void main() {
|
|||
);
|
||||
|
||||
setUp(() {
|
||||
auth = FakeAuthGateway();
|
||||
auth = FakeAuthGateway(status: const SignedOut());
|
||||
passwords = repository();
|
||||
});
|
||||
tearDown(() => passwords.dispose());
|
||||
|
|
@ -135,7 +135,7 @@ void main() {
|
|||
final gateway = _BrokenGateway(auth);
|
||||
final broken = repository(gateway: gateway, crypto: FakeAccountCrypto());
|
||||
addTearDown(broken.dispose);
|
||||
await auth.signInAnonymously();
|
||||
await auth.switchToGitHub();
|
||||
final section = AccountSecurityCubit(broken);
|
||||
addTearDown(section.close);
|
||||
final states = await statesOf(section, section.load);
|
||||
|
|
|
|||
|
|
@ -13,10 +13,6 @@ final class _UnreadableKeys() implements AccountKeyStore {
|
|||
Future<UnlockedAccountKeys?> read(String person) =>
|
||||
Future.error(PlatformException(code: 'locked'));
|
||||
|
||||
@override
|
||||
Future<bool> holds(String person) =>
|
||||
Future.error(PlatformException(code: 'locked'));
|
||||
|
||||
@override
|
||||
Future<void> forgetAllBut(String? person) async {}
|
||||
}
|
||||
|
|
@ -42,7 +38,7 @@ void main() {
|
|||
late PasswordRepository passwords;
|
||||
|
||||
setUp(() {
|
||||
auth = FakeAuthGateway();
|
||||
auth = FakeAuthGateway(status: const SignedOut());
|
||||
gateway = FakePasswordGateway(auth);
|
||||
crypto = FakeAccountCrypto();
|
||||
keys = MemoryAccountKeyStore();
|
||||
|
|
@ -65,7 +61,7 @@ void main() {
|
|||
);
|
||||
addTearDown(locked.dispose);
|
||||
|
||||
await auth.signInAnonymously();
|
||||
await auth.switchToGitHub();
|
||||
await settle();
|
||||
await auth.signOut();
|
||||
await settle();
|
||||
|
|
@ -304,9 +300,9 @@ void main() {
|
|||
});
|
||||
|
||||
group('signed in', () {
|
||||
test('an anonymous person given a password becomes identified, the keys '
|
||||
'and the address kept', () async {
|
||||
await auth.signInAnonymously();
|
||||
test('one who signs in with GitHub given a password keeps the account, '
|
||||
'the keys and the address kept (ADR 0017, D2)', () async {
|
||||
await auth.switchToGitHub();
|
||||
final request = await passwords.sendCode(
|
||||
'dana@example.org',
|
||||
language: 'en',
|
||||
|
|
@ -323,38 +319,15 @@ void main() {
|
|||
|
||||
expect(
|
||||
auth.status,
|
||||
const SignedIn(authUserId: 'anonymous-1', isIdentified: true),
|
||||
const SignedIn(authUserId: 'github-person', isIdentified: true),
|
||||
);
|
||||
expect(keys.kept['anonymous-1'], material.keys);
|
||||
expect(addresses.addresses['anonymous-1'], 'dana@example.org');
|
||||
});
|
||||
|
||||
test("an anonymous person's device keys become their account's as they "
|
||||
'set a password, so their private studysets stay readable', () async {
|
||||
await auth.signInAnonymously();
|
||||
final device = await crypto.newDeviceKeys();
|
||||
await keys.keep('anonymous-1', device);
|
||||
final request = await passwords.sendCode(
|
||||
'dana@example.org',
|
||||
language: 'en',
|
||||
purpose: MailCodePurpose.signUp,
|
||||
);
|
||||
|
||||
final material = await passwords.makePassword('her password');
|
||||
await passwords.setPassword(
|
||||
material,
|
||||
address: 'dana@example.org',
|
||||
request: request,
|
||||
code: gateway.codeFor('dana@example.org'),
|
||||
);
|
||||
|
||||
expect(material.keys, device);
|
||||
expect(keys.kept['anonymous-1'], device);
|
||||
expect(keys.kept['github-person'], material.keys);
|
||||
expect(addresses.addresses['github-person'], 'dana@example.org');
|
||||
});
|
||||
|
||||
test('someone signed up anew gets new keys, never the keys kept on the '
|
||||
'device for another', () async {
|
||||
await keys.keep('anonymous-1', crypto.keysNumbered(1));
|
||||
await keys.keep('someone-else', crypto.keysNumbered(1));
|
||||
|
||||
final material = await passwords.makePassword('a new password');
|
||||
|
||||
|
|
@ -363,8 +336,8 @@ void main() {
|
|||
|
||||
test("keeps only the signed-in person's keys: another signing in, or "
|
||||
'signing out, takes the others away', () async {
|
||||
await auth.signInAnonymously();
|
||||
await keys.keep('anonymous-1', crypto.keysNumbered(1));
|
||||
await auth.switchToGitHub();
|
||||
await keys.keep('github-person', crypto.keysNumbered(1));
|
||||
await keys.keep('someone-else', crypto.keysNumbered(2));
|
||||
|
||||
await auth.signOut();
|
||||
|
|
|
|||
|
|
@ -194,11 +194,11 @@ void main() {
|
|||
keysSetup = register.KeysSetup.fromJson(keys.setup.toJson());
|
||||
});
|
||||
|
||||
test('its keys are a setup the register takes, keeping the keys an '
|
||||
"anonymous person's device made", () async {
|
||||
test('its keys are a setup the register takes, also keeping keys the '
|
||||
'device holds', () async {
|
||||
expect(register.isKeysSetup(keysSetup), isTrue);
|
||||
|
||||
final deviceKeys = await crypto.newDeviceKeys();
|
||||
final deviceKeys = keys.keys;
|
||||
final kept = await crypto.newKeys(password, keys: deviceKeys);
|
||||
|
||||
expect(
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ void main() {
|
|||
late TestAppDependencies dependencies;
|
||||
|
||||
setUp(() {
|
||||
gateway = FakeAuthGateway();
|
||||
gateway = FakeAuthGateway(status: const SignedOut());
|
||||
dependencies = TestAppDependencies(gateway: gateway);
|
||||
});
|
||||
|
||||
|
|
@ -27,7 +27,6 @@ void main() {
|
|||
tester.view.physicalSize = const Size(412, 1800);
|
||||
tester.view.devicePixelRatio = 1;
|
||||
addTearDown(tester.view.reset);
|
||||
await dependencies.signedOut.write(signedOut: true);
|
||||
await tester.pumpWidget(
|
||||
AppFrameScope(
|
||||
frame: testFrame(),
|
||||
|
|
@ -518,8 +517,9 @@ void main() {
|
|||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
testWidgets('offers an anonymous person a password', (tester) async {
|
||||
await gateway.signInAnonymously();
|
||||
testWidgets('offers one signed in with GitHub a password (ADR 0017, '
|
||||
'D2)', (tester) async {
|
||||
await gateway.switchToGitHub();
|
||||
await show(tester);
|
||||
|
||||
expect(find.text('Set up a password'), findsOneWidget);
|
||||
|
|
|
|||
Loading…
Reference in a new issue