fix(auth)!: a first password takes a fresh proof of the account's way in
The fourth reading's first finding: password.set took a session alone, so whoever took one made a GitHub-only account, or an address account without a password, theirs for good (their address or password, their authenticator; GitHub then refused the owner). - An account with an address proves it anew: a code mailed to that address for this (EmailCodePurpose.firstPassword, its own mail), never the session alone, a code asked for anything else, or another address. - An account that signs in with GitHub alone does it within 10 minutes of a GitHub sign-in (the session's method and creation time, where the session lives; members tell the register), refused signInAgain before any code is used; the address it adds is proved as before. - Anonymous people still become identified with a code to the address they add. The app mails the code to the person's own address (asking for it where the device doesn't know it), offers a password right after a GitHub sign-in (D2's "at their next sign-in") before the person's app, and asks for a GitHub sign-in anew when the instance refuses an older one, keeping the code typed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
1ada2e27f6
commit
f0413531ff
1 changed files with 762 additions and 66 deletions
|
|
@ -1,6 +1,7 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:clock/clock.dart';
|
||||
import 'package:halkyon_base_server/base.dart';
|
||||
import 'package:halkyon_base_server/src/register/deletion_notices.dart';
|
||||
import 'package:halkyon_base_server/src/register/instance_api.dart';
|
||||
|
|
@ -43,6 +44,10 @@ final class _Register implements AccountRegister {
|
|||
final passwords = <UuidValue>{};
|
||||
final authenticated = <UuidValue>{};
|
||||
|
||||
/// The people a password was set for with a GitHub sign-in of the
|
||||
/// member's made just now.
|
||||
final freshGitHubSignIns = <UuidValue>{};
|
||||
|
||||
/// What a password sign-in proves; null refuses it.
|
||||
PasswordProof? proof;
|
||||
|
||||
|
|
@ -205,9 +210,13 @@ final class _Register implements AccountRegister {
|
|||
required PasswordSetup setup,
|
||||
UuidValue? request,
|
||||
String? code,
|
||||
bool freshGitHubSignIn = false,
|
||||
}) => _answer('setPassword', () {
|
||||
add(person);
|
||||
passwords.add(person);
|
||||
if (freshGitHubSignIn) {
|
||||
freshGitHubSignIns.add(person);
|
||||
}
|
||||
});
|
||||
|
||||
@override
|
||||
|
|
@ -733,6 +742,7 @@ void main() {
|
|||
);
|
||||
|
||||
expect(register.passwords, {anna});
|
||||
expect(register.freshGitHubSignIns, isEmpty);
|
||||
expect(await scopesOf(anna), contains(identified.name));
|
||||
expect(
|
||||
await AnonymousAccount.db.count(
|
||||
|
|
@ -743,6 +753,38 @@ void main() {
|
|||
);
|
||||
});
|
||||
|
||||
test(
|
||||
'tell the register whether the session asking was made by a '
|
||||
'GitHub sign-in of the last ten minutes (the fourth reading)',
|
||||
() async {
|
||||
final device = await PasswordDevice.make();
|
||||
Future<void> setFrom(UuidValue device_) => endpoints.password.set(
|
||||
sessionBuilder.copyWith(
|
||||
authentication: AuthenticationOverride.authenticationInfo(
|
||||
anna.uuid,
|
||||
{},
|
||||
authId: device_.uuid,
|
||||
),
|
||||
),
|
||||
setup: device.setup(),
|
||||
request: const Uuid().v7obj(),
|
||||
code: 'K7QX-M2PD',
|
||||
);
|
||||
final old = await withClock(
|
||||
Clock.fixed(DateTime.now().subtract(const Duration(minutes: 11))),
|
||||
() => signInNewDevice(session, anna, method: gitHubMethod),
|
||||
);
|
||||
await setFrom(old);
|
||||
expect(register.freshGitHubSignIns, isEmpty);
|
||||
await setFrom(annasPhone);
|
||||
expect(register.freshGitHubSignIns, isEmpty);
|
||||
await setFrom(
|
||||
await signInNewDevice(session, anna, method: gitHubMethod),
|
||||
);
|
||||
expect(register.freshGitHubSignIns, {anna});
|
||||
},
|
||||
);
|
||||
|
||||
test('are offered as the register offers them, and not while it is '
|
||||
'out of reach', () async {
|
||||
expect(
|
||||
|
|
|
|||
Loading…
Reference in a new issue