feat(base): the changes of SimpleLogin accounts' keys, each with the password and a fresh sign-in (ADR 0020, D20)
Every change takes the password's proof by its own proof key (the recovery phrase's for a lost password) and a SimpleLogin sign-in of the last ten minutes on the device (`KeysOwnerProof`, its attempt), as ADR 0017's take the password and a code. The sign-in is checked before the proof and taken with the change, so a wrong proof leaves it standing and each sign-in serves one change. - `keys.set(attempt, setup, handle?)`: the first keys of an account that SimpleLogin was added to (anonymous before, or signing in with GitHub or a code), with the handle where it has none; the sign-in alone, as there is no password to prove yet. - `keys.changePassword`, `keys.beginPhrase` and `keys.resetPassword` (a lost password, with the phrase): every other session ends, in every service, as ADR 0017's change has it. - `keys.setRecoveryKeys`: a new phrase. - `account.delete(keysProof)`: an account without SRP's verifier deletes itself with the proof and the sign-in; `requireOwnerToDelete` no longer asks such an account for SRP. - `newKeysPasswordDigest` on both sides, held to each other by a test; a challenge purpose of its own for the phrase's proof while signed in. - Members ask the register for all of it; `RemoteRegister` and the instance API carry the five new operations and the deletion's proof. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
090d901e92
commit
7c58d23424
1 changed files with 3163 additions and 78 deletions
|
|
@ -733,6 +733,180 @@ void main() {
|
|||
);
|
||||
});
|
||||
|
||||
/// A SimpleLogin sign-in of [person]'s through a member, as
|
||||
/// [subject], just now: its attempt, which stands for it ten
|
||||
/// minutes (D20).
|
||||
Future<UuidValue> signedInAgain(
|
||||
UuidValue person,
|
||||
String subject,
|
||||
) async {
|
||||
final start = await remote.beginSimpleLogin(
|
||||
session,
|
||||
marker: 'todo-desktop',
|
||||
caller: person,
|
||||
network: '203.0.113.0/24',
|
||||
);
|
||||
await remote.completeSimpleLogin(
|
||||
session,
|
||||
attempt: start.attempt,
|
||||
code: simpleLogin.authorize(
|
||||
start.authorizationUrl,
|
||||
subject: subject,
|
||||
),
|
||||
caller: person,
|
||||
);
|
||||
return start.attempt;
|
||||
}
|
||||
|
||||
test(
|
||||
"gives a member's account its first keys and handle, once "
|
||||
'SimpleLogin was added to it, its refusals crossing whole',
|
||||
() async {
|
||||
final device = await KeysDevice.make();
|
||||
final attempt = await signedInAgain(ben, 'sl-ben');
|
||||
|
||||
await expectLater(
|
||||
remote.setFirstKeys(
|
||||
session,
|
||||
person: ben,
|
||||
attempt: attempt,
|
||||
setup: device.setup(),
|
||||
),
|
||||
refused(ProfileRefusal.handleInvalid),
|
||||
);
|
||||
await expectLater(
|
||||
remote.setFirstKeys(
|
||||
session,
|
||||
person: ben,
|
||||
attempt: const Uuid().v7obj(),
|
||||
setup: device.setup(),
|
||||
handle: 'ben',
|
||||
),
|
||||
refused(OidcRefusal.unknownAttempt),
|
||||
);
|
||||
await remote.setFirstKeys(
|
||||
session,
|
||||
person: ben,
|
||||
attempt: attempt,
|
||||
setup: device.setup(),
|
||||
handle: 'ben',
|
||||
);
|
||||
|
||||
expect((await remote.signInWaysOf(session, ben)).keys, isTrue);
|
||||
expect((await remote.profileOf(session, ben))!.handle, 'ben');
|
||||
await expectLater(
|
||||
remote.setFirstKeys(
|
||||
session,
|
||||
person: ben,
|
||||
attempt: await signedInAgain(ben, 'sl-ben'),
|
||||
setup: device.setup(),
|
||||
),
|
||||
refused(PasswordRefusal.passwordExists),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test("changes a member's account's keys, each change with the "
|
||||
"password's proof (the phrase's for a lost one) and a sign-in, "
|
||||
'and deletes it alike', () async {
|
||||
final device = await KeysDevice.make();
|
||||
final next = await KeysDevice.make(seed: 7);
|
||||
final start = await remote.beginSimpleLogin(
|
||||
session,
|
||||
marker: 'todo-desktop',
|
||||
network: '203.0.113.0/24',
|
||||
);
|
||||
await remote.completeSimpleLogin(
|
||||
session,
|
||||
attempt: start.attempt,
|
||||
code: simpleLogin.authorize(
|
||||
start.authorizationUrl,
|
||||
subject: 'sl-carla',
|
||||
),
|
||||
);
|
||||
final carla = await remote.signUpWithSimpleLogin(
|
||||
session,
|
||||
attempt: start.attempt,
|
||||
handle: 'carla',
|
||||
setup: device.setup(),
|
||||
);
|
||||
final keepSession = const Uuid().v7obj();
|
||||
|
||||
await remote.changeKeysPassword(
|
||||
session,
|
||||
person: carla,
|
||||
proof: await device.ownerProof(
|
||||
await signedInAgain(carla, 'sl-carla'),
|
||||
challenge: await remote.beginPasswordKeyProof(session, carla),
|
||||
purpose: KeyProofPurpose.newPassword,
|
||||
digest: newKeysPasswordDigest(next.password()),
|
||||
),
|
||||
password: next.password(),
|
||||
keepSession: keepSession,
|
||||
);
|
||||
// Every member is told to end the account's other sessions.
|
||||
await sessionNoticesDelivered();
|
||||
expect(members.sessionsTold.single.$2, carla);
|
||||
expect(members.sessionsTold.single.$4, keepSession);
|
||||
await remote.unlockKeys(
|
||||
session,
|
||||
carla,
|
||||
await next.prove(
|
||||
await remote.beginPasswordKeyProof(session, carla),
|
||||
),
|
||||
);
|
||||
|
||||
await remote.resetKeysPassword(
|
||||
session,
|
||||
person: carla,
|
||||
proof: await device.ownerProof(
|
||||
await signedInAgain(carla, 'sl-carla'),
|
||||
phrase: await remote.beginPhraseKeyProof(session, carla),
|
||||
purpose: KeyProofPurpose.passwordReset,
|
||||
digest: newKeysPasswordDigest(device.password()),
|
||||
),
|
||||
password: device.password(),
|
||||
);
|
||||
await remote.setKeysRecovery(
|
||||
session,
|
||||
person: carla,
|
||||
proof: await device.ownerProof(
|
||||
await signedInAgain(carla, 'sl-carla'),
|
||||
challenge: await remote.beginPasswordKeyProof(session, carla),
|
||||
purpose: KeyProofPurpose.newPhrase,
|
||||
digest: newPhraseDigest(
|
||||
next.recoveryKeys,
|
||||
ByteData.sublistView(next.recoveryProofKey),
|
||||
),
|
||||
),
|
||||
recoveryKeys: next.recoveryKeys,
|
||||
recoveryProofKey: ByteData.sublistView(next.recoveryProofKey),
|
||||
);
|
||||
expect(
|
||||
(await remote.beginPhraseKeyProof(
|
||||
session,
|
||||
carla,
|
||||
)).recoveryKeys.toJson(),
|
||||
next.recoveryKeys.toJson(),
|
||||
);
|
||||
|
||||
final deletion = await signedInAgain(carla, 'sl-carla');
|
||||
await expectLater(
|
||||
remote.deleteAccount(session, carla),
|
||||
refused(PasswordRefusal.wrongCredentials),
|
||||
);
|
||||
await remote.deleteAccount(
|
||||
session,
|
||||
carla,
|
||||
keysProof: await device.ownerProof(
|
||||
deletion,
|
||||
challenge: await remote.beginPasswordKeyProof(session, carla),
|
||||
purpose: KeyProofPurpose.deleteAccount,
|
||||
),
|
||||
);
|
||||
expect(await AuthUser.db.findById(session, carla), isNull);
|
||||
});
|
||||
|
||||
test('refuses where the register has no SimpleLogin app', () async {
|
||||
useSimpleLogin(sessionBuilder, null);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue