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
|
|
@ -435,56 +435,62 @@ void main() {
|
|||
expect(mailbox.sent, hasLength(11));
|
||||
});
|
||||
|
||||
test("a code proves the address for a member's password, and a "
|
||||
'wrong one refuses it, keeping nothing', () async {
|
||||
test("a code to a member's person's address proves it for their "
|
||||
'password, a wrong one refusing it, and someone without a way '
|
||||
'in sets none, before the code is used', () async {
|
||||
usePasswordKeys(pod, testPasswordKeys);
|
||||
addTearDown(() => usePasswordKeys(pod, null));
|
||||
final device = await PasswordDevice.make();
|
||||
final annaOnTodo = const Uuid().v7obj();
|
||||
final annaOnTodo = await newPersonWithAddress(
|
||||
session,
|
||||
'anna@example.org',
|
||||
);
|
||||
final request = await remote.sendSignInCode(
|
||||
session,
|
||||
address: 'Anna@example.org',
|
||||
language: 'de',
|
||||
network: '203.0.113.7',
|
||||
purpose: EmailCodePurpose.signUp,
|
||||
purpose: EmailCodePurpose.firstPassword,
|
||||
);
|
||||
Future<int> annasPasswords() => AccountPassword.db.count(
|
||||
session,
|
||||
where: (t) => t.authUserId.equals(annaOnTodo),
|
||||
);
|
||||
|
||||
await expectLater(
|
||||
remote.setPassword(
|
||||
session,
|
||||
person: annaOnTodo,
|
||||
anonymousHere: true,
|
||||
setup: device.setup(),
|
||||
request: request,
|
||||
code: 'AAAA-AAAA',
|
||||
),
|
||||
refused(EmailSignInRefusal.wrongCode),
|
||||
);
|
||||
expect(await AuthUser.db.findById(session, annaOnTodo), isNull);
|
||||
// Without the member saying Anna is anonymous there, refused
|
||||
// before her code is used (the full reading's M1).
|
||||
expect(await annasPasswords(), 0);
|
||||
// Someone without a way in (an anonymous account left from
|
||||
// before, ADR 0020, D25, or a GitHub ID forgotten, the full
|
||||
// reading's M1) is refused before the code is used.
|
||||
final carlOnTodo = const Uuid().v7obj();
|
||||
await expectLater(
|
||||
remote.setPassword(
|
||||
session,
|
||||
person: annaOnTodo,
|
||||
person: carlOnTodo,
|
||||
setup: device.setup(),
|
||||
request: request,
|
||||
code: mailbox.codeFor('anna@example.org'),
|
||||
),
|
||||
refused(PasswordRefusal.wrongCredentials),
|
||||
);
|
||||
expect(await AuthUser.db.findById(session, carlOnTodo), isNull);
|
||||
await remote.setPassword(
|
||||
session,
|
||||
person: annaOnTodo,
|
||||
anonymousHere: true,
|
||||
setup: device.setup(),
|
||||
request: request,
|
||||
code: mailbox.codeFor('anna@example.org'),
|
||||
);
|
||||
expect(
|
||||
(await AuthUser.db.findById(session, annaOnTodo))!.scopeNames,
|
||||
contains(identified.name),
|
||||
);
|
||||
expect(await annasPasswords(), 1);
|
||||
expect(await remote.knownAccounts(session, {annaOnTodo, ben}), {
|
||||
annaOnTodo,
|
||||
ben,
|
||||
|
|
@ -493,41 +499,11 @@ void main() {
|
|||
});
|
||||
|
||||
group('GitHub', () {
|
||||
test("links a member's person, who becomes an account under the "
|
||||
'same ID, found by it afterwards', () async {
|
||||
final annaOnTodo = const Uuid().v7obj();
|
||||
|
||||
await remote.linkGitHub(
|
||||
session,
|
||||
person: annaOnTodo,
|
||||
gitHubUserId: '2002',
|
||||
);
|
||||
|
||||
expect(await remote.accountWithGitHub(session, '2002'), annaOnTodo);
|
||||
test('finds the account a GitHub account signs into, and nobody for '
|
||||
'one nobody has', () async {
|
||||
expect(await remote.accountWithGitHub(session, '1001'), ben);
|
||||
expect(await remote.accountWithGitHub(session, '3003'), isNull);
|
||||
expect(
|
||||
(await AuthUser.db.findById(session, annaOnTodo))!.scopeNames,
|
||||
contains(identified.name),
|
||||
);
|
||||
});
|
||||
|
||||
test(
|
||||
"refuses another person's GitHub account, keeping nothing",
|
||||
() async {
|
||||
final annaOnTodo = const Uuid().v7obj();
|
||||
|
||||
await expectLater(
|
||||
remote.linkGitHub(
|
||||
session,
|
||||
person: annaOnTodo,
|
||||
gitHubUserId: '1001',
|
||||
),
|
||||
refused(GitHubSignInRefusal.takenByAnotherPerson),
|
||||
);
|
||||
expect(await AuthUser.db.findById(session, annaOnTodo), isNull);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
group('SimpleLogin (ADR 0020)', () {
|
||||
|
|
@ -1109,18 +1085,20 @@ void main() {
|
|||
'register refuses', () async {
|
||||
final device = await PasswordDevice.make();
|
||||
await atNow(() async {
|
||||
final annaOnTodo = const Uuid().v7obj();
|
||||
final annaOnTodo = await newPersonWithAddress(
|
||||
session,
|
||||
'anna@example.org',
|
||||
);
|
||||
final request = await remote.sendSignInCode(
|
||||
session,
|
||||
address: 'anna@example.org',
|
||||
language: 'en',
|
||||
network: '203.0.113.7',
|
||||
purpose: EmailCodePurpose.signUp,
|
||||
purpose: EmailCodePurpose.firstPassword,
|
||||
);
|
||||
await remote.setPassword(
|
||||
session,
|
||||
person: annaOnTodo,
|
||||
anonymousHere: true,
|
||||
setup: device.setup(),
|
||||
request: request,
|
||||
code: mailbox.codeFor('anna@example.org'),
|
||||
|
|
@ -1409,19 +1387,21 @@ void main() {
|
|||
() async {
|
||||
final device = await PasswordDevice.make();
|
||||
await atNow(() async {
|
||||
final annaOnTodo = const Uuid().v7obj();
|
||||
final annaOnTodo = await newPersonWithAddress(
|
||||
session,
|
||||
'anna@example.org',
|
||||
);
|
||||
final annasPhone = const Uuid().v7obj();
|
||||
final request = await remote.sendSignInCode(
|
||||
session,
|
||||
address: 'anna@example.org',
|
||||
language: 'en',
|
||||
network: '203.0.113.7',
|
||||
purpose: EmailCodePurpose.signUp,
|
||||
purpose: EmailCodePurpose.firstPassword,
|
||||
);
|
||||
await remote.setPassword(
|
||||
session,
|
||||
person: annaOnTodo,
|
||||
anonymousHere: true,
|
||||
setup: device.setup(),
|
||||
request: request,
|
||||
code: mailbox.codeFor('anna@example.org'),
|
||||
|
|
@ -1691,8 +1671,8 @@ void main() {
|
|||
expect(members.told, [(_todo, gone)]);
|
||||
});
|
||||
|
||||
test('tells nobody of an anonymous person, a member never having '
|
||||
'had them', () async {
|
||||
test('tells nobody of an anonymous account left from before, a member '
|
||||
'never having had it', () async {
|
||||
final anna = await newAnonymousPerson(session);
|
||||
|
||||
await remote.deleteAccount(session, anna);
|
||||
|
|
|
|||
Loading…
Reference in a new issue