feat(base)!: no anonymous accounts on the server (ADR 0020, D25)
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:
parent
5b9df73756
commit
87671208df
1 changed files with 837 additions and 1353 deletions
|
|
@ -168,8 +168,8 @@ void main() {
|
|||
(await RateLimitedRequestAttempt.db.insertRow(
|
||||
session,
|
||||
RateLimitedRequestAttempt(
|
||||
domain: 'anonymous',
|
||||
source: 'account_creation',
|
||||
domain: 'password_sign_in',
|
||||
source: 'proofs_per_address',
|
||||
key: '203.0.113.7',
|
||||
ipAddress: '203.0.113.7',
|
||||
attemptedAt: now.subtract(ago),
|
||||
|
|
@ -198,7 +198,10 @@ void main() {
|
|||
});
|
||||
});
|
||||
|
||||
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));
|
||||
|
|
@ -220,45 +223,70 @@ void main() {
|
|||
session = sessionBuilder.build();
|
||||
});
|
||||
|
||||
test('the module signs nobody in anonymously', () {
|
||||
expect(
|
||||
session.serverpod.endpoints.getConnectorByName('anonymousIdp'),
|
||||
isNull,
|
||||
);
|
||||
});
|
||||
|
||||
test(
|
||||
'deletes anonymous people unused for 90 days, and only them, with '
|
||||
"the host's data of theirs",
|
||||
'deletes every anonymous account left, however new or lately used, '
|
||||
"with the host's data of theirs",
|
||||
() async {
|
||||
final unused = await newAnonymousPerson(
|
||||
session,
|
||||
createdAt: daysAgo(100),
|
||||
);
|
||||
await _lastUsed(session, unused, daysAgo(95));
|
||||
final neverSignedIn = await newAnonymousPerson(
|
||||
session,
|
||||
createdAt: daysAgo(100),
|
||||
);
|
||||
final old = 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));
|
||||
await _lastUsed(session, recentlyUsed, daysAgo(1));
|
||||
final newcomer = await newAnonymousPerson(session);
|
||||
await signInNewDevice(session, newcomer);
|
||||
|
||||
final pruned = await pruneAbandonedAnonymousPeople(session, now: now);
|
||||
// Others may have been left by earlier runs.
|
||||
expect(await pruneAnonymousPeople(session), greaterThanOrEqualTo(3));
|
||||
|
||||
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);
|
||||
for (final gone in [old, recentlyUsed, newcomer]) {
|
||||
expect(await AuthUser.db.findById(session, gone), isNull);
|
||||
expect(await _sessionsOf(session, gone), 0);
|
||||
}
|
||||
expect(deletedByHost, unorderedEquals([unused, neverSignedIn]));
|
||||
expect(deletedByHost, containsAll([old, recentlyUsed, newcomer]));
|
||||
},
|
||||
);
|
||||
|
||||
test('deletes nobody but anonymous accounts: never someone identified, '
|
||||
"even with an anonymous account's row left, nor anyone without "
|
||||
'one', () async {
|
||||
final identifiedPerson = (await AuthServices.instance.authUsers.create(
|
||||
session,
|
||||
scopes: {identified},
|
||||
)).id;
|
||||
// INSERT INTO "serverpod_auth_idp_anonymous_account" (…) VALUES (…)
|
||||
await AnonymousAccount.db.insertRow(
|
||||
session,
|
||||
AnonymousAccount(authUserId: identifiedPerson, createdAt: daysAgo(400)),
|
||||
);
|
||||
await _lastUsed(session, identifiedPerson, daysAgo(365));
|
||||
final noAnonymousAccount = (await AuthServices.instance.authUsers.create(
|
||||
session,
|
||||
)).id;
|
||||
|
||||
await pruneAnonymousPeople(session);
|
||||
|
||||
for (final kept in [identifiedPerson, noAnonymousAccount]) {
|
||||
expect(await AuthUser.db.findById(session, kept), isNotNull);
|
||||
expect(deletedByHost, isNot(contains(kept)));
|
||||
}
|
||||
expect(await _sessionsOf(session, identifiedPerson), 1);
|
||||
});
|
||||
|
||||
test('deletes at most as many as it is given at once', () async {
|
||||
for (var i = 0; i < 3; i++) {
|
||||
await newAnonymousPerson(session);
|
||||
}
|
||||
|
||||
expect(await pruneAnonymousPeople(session, limit: 2), 2);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue