feat(base)!: no anonymous accounts on the server (ADR 0020, D25)
All checks were successful
CI / check (push) Successful in 30m13s
CI / deploy (push) Has been skipped

The server's half of D25: every account has a way in, and the anonymous
accounts left from before go with the daily pruning.

- The module signs nobody in anonymously: `anonymousIdp` and
  `gitHubUpgrade` are gone, and so is linking GitHub at the register.
- A password is set only by someone whose address is theirs (a
  `firstPassword` code) or who signs in with GitHub alone (a fresh
  sign-in and a `signUp` code); an anonymous account is refused
  `wrongCredentials` before any code is used.
- SimpleLogin refuses to link onto a caller without `identified`
  (`wrongCaller`), the member's endpoint too, so nothing turns an
  anonymous account into one with a way in.
- `pruneAnonymousPeople` deletes every anonymous account left, however
  new, never one identified. Learn and Todo schedule it as
  `prune-accounts` and cancel the old `prune-abandoned-anonymous-people`
  entry once.
- Learn: someone without `identified` keeps no files;
  `FILE_LIMIT_ANONYMOUS_MB` is gone. `minimumAppBuild` is 23, as builds
  up to 22 start with an anonymous account.
- Learn's sync tests make their person and session by SQL in the
  development database, as Serverpod stores sessions.
- Docs, store listings and ADRs 0010, 0014, 0016, 0017 and 0019 amended.

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-24 16:07:27 +02:00
commit 87671208df
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -68,7 +68,10 @@ void main() {
});
// halkyon_base's pruning, with Learn's deletion as it runs on the server.
withServerpod('Pruning abandoned anonymous people', (sessionBuilder, _) {
withServerpod('Pruning anonymous people (ADR 0020, D25)', (
sessionBuilder,
_,
) {
late Session session;
final now = DateTime.now().toUtc();
DateTime daysAgo(int days) => now.subtract(Duration(days: days));
@ -78,47 +81,29 @@ void main() {
session = sessionBuilder.build();
});
test(
'deletes anonymous people unused for 90 days, and only them',
() async {
final unused = await _newAnonymousPerson(
session,
createdAt: daysAgo(100),
);
await _lastUsed(session, unused, daysAgo(95));
final neverSignedIn = await _newAnonymousPerson(
session,
createdAt: daysAgo(100),
);
final recentlyUsed = await _newAnonymousPerson(
session,
createdAt: daysAgo(100),
);
await _lastUsed(session, recentlyUsed, daysAgo(10));
final newcomer = await _newAnonymousPerson(
session,
createdAt: daysAgo(5),
);
final identifiedPerson = (await AuthServices.instance.authUsers.create(
session,
scopes: {identified},
)).id;
await _lastUsed(session, identifiedPerson, daysAgo(365));
test('deletes every anonymous account left, however lately used, and '
'nobody identified', () async {
final old = await _newAnonymousPerson(session, createdAt: daysAgo(100));
final recentlyUsed = await _newAnonymousPerson(session);
await _lastUsed(session, recentlyUsed, daysAgo(1));
final identifiedPerson = (await AuthServices.instance.authUsers.create(
session,
scopes: {identified},
)).id;
await _lastUsed(session, identifiedPerson, daysAgo(365));
final pruned = await pruneAbandonedAnonymousPeople(session, now: now);
// Others may have been left by earlier runs.
expect(await pruneAnonymousPeople(session), greaterThanOrEqualTo(2));
expect(pruned, 2);
expect(await AuthUser.db.findById(session, unused), isNull);
expect(await AuthUser.db.findById(session, neverSignedIn), isNull);
for (final kept in [recentlyUsed, newcomer, identifiedPerson]) {
expect(await AuthUser.db.findById(session, kept), isNotNull);
}
},
);
expect(await AuthUser.db.findById(session, old), isNull);
expect(await AuthUser.db.findById(session, recentlyUsed), isNull);
expect(await AuthUser.db.findById(session, identifiedPerson), isNotNull);
});
});
}
/// A person as the app's first launch creates them: anonymous.
/// An anonymous account, as the app made them on its first start until
/// ADR 0020, D25, left from before.
Future<UuidValue> _newAnonymousPerson(
Session session, {
DateTime? createdAt,