fix(auth): GitHub-only accounts go at GitHub's end; only the anonymous add an address
The full reading's M1 and L6: once GitHub IDs were forgotten at GITHUB_SIGN_IN_ENDS, an account that had signed in with GitHub alone counted as anonymous, so a stolen session and any mailbox gave it a first password and authenticator; and such accounts, with no way in left, were never pruned. Now: - at the end, the daily run that forgets the GitHub IDs first deletes the accounts that sign in with GitHub alone (no address, so never a password), with everything they have, members told (deletePerson); the app tells them for the three months before that their account goes then unless they set up a password (passwordSectionGitHubEndsDeletes, en/de); - password.set adds an address without GitHub only for someone anonymous where the session lives (anonymousHere, which a member tells the register, as it tells freshGitHubSignIn); anyone else is refused before any code is used. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
9d0d037c86
commit
5eb74639b6
4 changed files with 202 additions and 13 deletions
|
|
@ -234,6 +234,35 @@ void main() {
|
|||
});
|
||||
expect(await forgetGitHubAccounts(session, now: ends), 1);
|
||||
expect(await GitHubAccount.db.count(session), 0);
|
||||
// Ben signed in with GitHub alone and never set a password up: his
|
||||
// account goes with it (the full reading's M1), no way in left.
|
||||
expect(await AuthUser.db.findById(session, ben), isNull);
|
||||
});
|
||||
|
||||
test('at its end keeps the account of someone with an address too, '
|
||||
'forgetting only the GitHub ID', () async {
|
||||
final carl = (await AuthServices.instance.authUsers.create(
|
||||
session,
|
||||
scopes: {identified},
|
||||
)).id;
|
||||
await GitHubAccount.db.insertRow(
|
||||
session,
|
||||
GitHubAccount(userIdentifier: '3003', authUserId: carl),
|
||||
);
|
||||
await EmailIdentity.db.insertRow(
|
||||
session,
|
||||
EmailIdentity(
|
||||
authUserId: carl,
|
||||
addressHash: addressHash(session.serverpod, 'carl@example.org'),
|
||||
createdAt: DateTime.utc(2026, 10),
|
||||
),
|
||||
);
|
||||
final ends = DateTime.utc(2027, 1, 22);
|
||||
useGitHubSignInEnds(session.serverpod, ends);
|
||||
expect(await forgetGitHubAccounts(session, now: ends), 2);
|
||||
expect(await AuthUser.db.findById(session, carl), isNotNull);
|
||||
expect(await AuthUser.db.findById(session, ben), isNull);
|
||||
expect(await GitHubAccount.db.count(session), 0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -380,6 +380,43 @@ void main() {
|
|||
});
|
||||
});
|
||||
|
||||
test('is refused to someone neither anonymous nor with a way in (a '
|
||||
'GitHub ID forgotten): a session alone and a code to any address '
|
||||
'set nothing', () async {
|
||||
// Anna, identified, her GitHub ID forgotten (the full reading's
|
||||
// M1): a session of hers, stolen, outlived it.
|
||||
await grantIdentified(session, anna);
|
||||
await AnonymousAccount.db.deleteWhere(
|
||||
session,
|
||||
where: (t) => t.authUserId.equals(anna),
|
||||
);
|
||||
final thief = await PasswordDevice.make(seed: 99);
|
||||
final code = await mailedCode('thief@example.org');
|
||||
await expectLater(
|
||||
endpoints.password.set(
|
||||
asAnna(),
|
||||
setup: thief.setup(),
|
||||
request: code.request,
|
||||
code: code.code,
|
||||
),
|
||||
refused(PasswordRefusal.wrongCredentials),
|
||||
);
|
||||
expect(
|
||||
await EmailIdentity.db.count(
|
||||
session,
|
||||
where: (t) => t.authUserId.equals(anna),
|
||||
),
|
||||
0,
|
||||
);
|
||||
expect(
|
||||
await AccountPassword.db.count(
|
||||
session,
|
||||
where: (t) => t.authUserId.equals(anna),
|
||||
),
|
||||
0,
|
||||
);
|
||||
});
|
||||
|
||||
test('is refused without an address, keeping nothing', () async {
|
||||
await expectLater(
|
||||
endpoints.password.set(asAnna(), setup: device.setup()),
|
||||
|
|
|
|||
|
|
@ -48,6 +48,9 @@ final class _Register implements AccountRegister {
|
|||
/// member's made just now.
|
||||
final freshGitHubSignIns = <UuidValue>{};
|
||||
|
||||
/// Those [setPassword] was told are anonymous on the member.
|
||||
final anonymousHere_ = <UuidValue>{};
|
||||
|
||||
/// What a password sign-in proves; null refuses it.
|
||||
PasswordProof? proof;
|
||||
|
||||
|
|
@ -240,12 +243,16 @@ final class _Register implements AccountRegister {
|
|||
UuidValue? request,
|
||||
String? code,
|
||||
bool freshGitHubSignIn = false,
|
||||
bool anonymousHere = false,
|
||||
}) => _answer('setPassword', () {
|
||||
add(person);
|
||||
passwords.add(person);
|
||||
if (freshGitHubSignIn) {
|
||||
freshGitHubSignIns.add(person);
|
||||
}
|
||||
if (anonymousHere) {
|
||||
anonymousHere_.add(person);
|
||||
}
|
||||
});
|
||||
|
||||
@override
|
||||
|
|
@ -850,6 +857,9 @@ void main() {
|
|||
|
||||
expect(register.passwords, {anna});
|
||||
expect(register.freshGitHubSignIns, isEmpty);
|
||||
// Anna was anonymous here, which the register can't know (the
|
||||
// full reading's M1).
|
||||
expect(register.anonymousHere_, {anna});
|
||||
expect(await scopesOf(anna), contains(identified.name));
|
||||
expect(
|
||||
await AnonymousAccount.db.count(
|
||||
|
|
|
|||
|
|
@ -384,6 +384,7 @@ void main() {
|
|||
remote.setPassword(
|
||||
session,
|
||||
person: annaOnTodo,
|
||||
anonymousHere: true,
|
||||
setup: device.setup(),
|
||||
request: request,
|
||||
code: 'AAAA-AAAA',
|
||||
|
|
@ -391,9 +392,22 @@ void main() {
|
|||
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).
|
||||
await expectLater(
|
||||
remote.setPassword(
|
||||
session,
|
||||
person: annaOnTodo,
|
||||
setup: device.setup(),
|
||||
request: request,
|
||||
code: mailbox.codeFor('anna@example.org'),
|
||||
),
|
||||
refused(PasswordRefusal.wrongCredentials),
|
||||
);
|
||||
await remote.setPassword(
|
||||
session,
|
||||
person: annaOnTodo,
|
||||
anonymousHere: true,
|
||||
setup: device.setup(),
|
||||
request: request,
|
||||
code: mailbox.codeFor('anna@example.org'),
|
||||
|
|
@ -486,6 +500,7 @@ void main() {
|
|||
await remote.setPassword(
|
||||
session,
|
||||
person: annaOnTodo,
|
||||
anonymousHere: true,
|
||||
setup: device.setup(),
|
||||
request: request,
|
||||
code: mailbox.codeFor('anna@example.org'),
|
||||
|
|
@ -755,6 +770,7 @@ void main() {
|
|||
await remote.setPassword(
|
||||
session,
|
||||
person: annaOnTodo,
|
||||
anonymousHere: true,
|
||||
setup: device.setup(),
|
||||
request: request,
|
||||
code: mailbox.codeFor('anna@example.org'),
|
||||
|
|
|
|||
Loading…
Reference in a new issue