fix(auth)!: a session alone never gets a code checked
The third reading found a stolen session guessing TOTP codes without end: nine wrong codes for new recovery codes, then removing an unconfirmed authenticator of its own, a success that cleared the session's count. As Proton has it, every signed-in change of how an account is secured now takes the owner's proof (OwnerProof): the password's SRP proof for a change's challenge and a TOTP or recovery code. Adding an authenticator once one is confirmed, removing a confirmed one, new recovery codes, a new phrase, a new password and deleting take it; only the first authenticator is confirmed with its own code alone, and removing an unconfirmed one takes nothing. A proof given where none is taken is refused, and a confirmed authenticator's code is never checked for a session alone, so no success passes a proof unchecked. A session's proofs count for it alone, before they are checked, cleared only by one accepted; the tenth wrong in a row ends the session. They never count toward the account's waits nor wait for them, so whoever knows the password no longer holds a signed-in owner out of changing it (the second finding), and a new password forgets those waits. Beginning a change counts nothing against the account (the third finding): 30 an hour per session, holding back that session alone. Deleting takes a TOTP code wherever an authenticator is confirmed, a code by mail where no second factor works (the fourth finding), which SignInWays now tells (secondFactorWorks). Signed-in calls no longer take the address: nothing there mails. wrongExistingCode goes, a wrong existing code being a wrong proof. The app follows in the next commit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
70b434bb84
commit
a9459bb875
6 changed files with 1461 additions and 1026 deletions
|
|
@ -3,6 +3,8 @@ import 'dart:typed_data';
|
|||
import 'package:clock/clock.dart';
|
||||
import 'package:cryptography/cryptography.dart';
|
||||
import 'package:halkyon_base_server/base.dart';
|
||||
import 'package:halkyon_base_server/src/password/password_endpoint.dart'
|
||||
show changesBegunPerSession;
|
||||
import 'package:halkyon_base_server/src/password/passwords.dart'
|
||||
show writeNewPassword;
|
||||
import 'package:serverpod/serverpod.dart';
|
||||
|
|
@ -71,6 +73,13 @@ void main() {
|
|||
authId: annasPhone.uuid,
|
||||
),
|
||||
);
|
||||
TestSessionBuilder onLaptop() => sessionBuilder.copyWith(
|
||||
authentication: AuthenticationOverride.authenticationInfo(
|
||||
anna.uuid,
|
||||
{identified},
|
||||
authId: annasLaptop.uuid,
|
||||
),
|
||||
);
|
||||
|
||||
Future<({UuidValue request, String code})> mailedCode(
|
||||
String address, {
|
||||
|
|
@ -106,28 +115,30 @@ void main() {
|
|||
);
|
||||
});
|
||||
|
||||
/// Changes Anna's password from her phone, the old one proved by
|
||||
/// [privateValue] (hers by default) with [totpCode] (her
|
||||
/// authenticator's now by default).
|
||||
/// Changes Anna's password from her phone (or [from]), the old one
|
||||
/// proved by [privateValue] (hers by default) with [totpCode] (her
|
||||
/// authenticator's now by default); sent as [as] where given.
|
||||
Future<void> change({
|
||||
BigInt? privateValue,
|
||||
String? totpCode,
|
||||
bool withoutCode = false,
|
||||
NewPassword? password,
|
||||
TestSessionBuilder? from,
|
||||
TestSessionBuilder? as,
|
||||
}) => atNow(() async {
|
||||
final challenge = await endpoints.password.beginChange(onPhone());
|
||||
final answer = device.answer(challenge, privateValue: privateValue);
|
||||
final challenge = await endpoints.password.beginChange(
|
||||
from ?? onPhone(),
|
||||
);
|
||||
await endpoints.password.change(
|
||||
as ?? onPhone(),
|
||||
challenge: challenge.challenge,
|
||||
clientValue: answer.clientValue,
|
||||
clientProof: answer.clientProof,
|
||||
totpCode: withoutCode ? null : totpCode ?? codeOf(enrolment, at: now),
|
||||
as ?? from ?? onPhone(),
|
||||
proof: device.ownerProof(
|
||||
challenge,
|
||||
privateValue: privateValue,
|
||||
totpCode: withoutCode
|
||||
? null
|
||||
: totpCode ?? codeOf(enrolment, at: now),
|
||||
),
|
||||
password: password ?? device.newPassword(),
|
||||
host: testHost,
|
||||
address: 'anna@example.org',
|
||||
language: 'en',
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -344,20 +355,16 @@ void main() {
|
|||
),
|
||||
);
|
||||
await expectLater(
|
||||
atNow(() async {
|
||||
final answer = device.answer(signInChallenge);
|
||||
await endpoints.password.change(
|
||||
atNow(
|
||||
() => endpoints.password.change(
|
||||
onPhone(),
|
||||
challenge: signInChallenge.challenge,
|
||||
clientValue: answer.clientValue,
|
||||
clientProof: answer.clientProof,
|
||||
totpCode: codeOf(enrolment, at: now),
|
||||
proof: device.ownerProof(
|
||||
signInChallenge,
|
||||
totpCode: codeOf(enrolment, at: now),
|
||||
),
|
||||
password: device.newPassword(),
|
||||
host: testHost,
|
||||
address: 'anna@example.org',
|
||||
language: 'en',
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
refused(PasswordRefusal.challengeExpired),
|
||||
);
|
||||
final changeChallenge = await atNow(
|
||||
|
|
@ -381,43 +388,89 @@ void main() {
|
|||
);
|
||||
});
|
||||
|
||||
test("takes the account's address, refused for another before "
|
||||
'the code is checked; its wrong codes make codes wait, the owner '
|
||||
'mailed', () async {
|
||||
await expectLater(
|
||||
atNow(() async {
|
||||
final challenge = await endpoints.password.beginChange(
|
||||
onPhone(),
|
||||
);
|
||||
final answer = device.answer(challenge);
|
||||
await endpoints.password.change(
|
||||
onPhone(),
|
||||
challenge: challenge.challenge,
|
||||
clientValue: answer.clientValue,
|
||||
clientProof: answer.clientProof,
|
||||
totpCode: codeOf(enrolment, at: now),
|
||||
// Another device's: a refused change leaves Anna's as it is.
|
||||
password: (await PasswordDevice.make()).newPassword(),
|
||||
host: testHost,
|
||||
address: 'ben@example.org',
|
||||
language: 'en',
|
||||
);
|
||||
}),
|
||||
refused(PasswordRefusal.wrongCredentials),
|
||||
);
|
||||
expect(await SecondFactorFailures.db.count(session), 0);
|
||||
// Past the hour of the changes begun (ten an hour per account).
|
||||
now = now.add(const Duration(hours: 1, seconds: 1));
|
||||
test("a session's wrong proofs count for it alone: the tenth in a "
|
||||
"row ends it, the account's codes never waiting, the owner's "
|
||||
'other sessions changing as before', () async {
|
||||
// Another device's new password: refused changes leave Anna's.
|
||||
final other = await PasswordDevice.make();
|
||||
for (var i = 0; i < 10; i++) {
|
||||
for (var i = 0; i < 9; i++) {
|
||||
await expectLater(
|
||||
change(totpCode: '000000', password: other.newPassword()),
|
||||
refused(PasswordRefusal.wrongCredentials),
|
||||
);
|
||||
}
|
||||
expect(mailbox.codesWait.single.to, 'anna@example.org');
|
||||
expect(mailbox.codesWait.single.language, 'en');
|
||||
await expectLater(
|
||||
change(
|
||||
privateValue: BigInt.from(4242),
|
||||
password: other.newPassword(),
|
||||
),
|
||||
refused(PasswordRefusal.sessionEnded),
|
||||
);
|
||||
expect(await sessionsOf(anna), {annasLaptop});
|
||||
// A proof sent on by the ended session is refused all the same.
|
||||
await expectLater(
|
||||
change(password: other.newPassword()),
|
||||
refused(PasswordRefusal.sessionEnded),
|
||||
);
|
||||
expect(await SecondFactorFailures.db.count(session), 0);
|
||||
expect(mailbox.codesWait, isEmpty);
|
||||
|
||||
nextStep();
|
||||
await change(from: onLaptop());
|
||||
});
|
||||
|
||||
test('a proof accepted clears the count: wrong ones before it never '
|
||||
'add up to ten with those after', () async {
|
||||
final other = await PasswordDevice.make();
|
||||
for (var i = 0; i < 9; i++) {
|
||||
await expectLater(
|
||||
change(totpCode: '000000', password: other.newPassword()),
|
||||
refused(PasswordRefusal.wrongCredentials),
|
||||
);
|
||||
}
|
||||
nextStep();
|
||||
await change(password: other.newPassword());
|
||||
device = other;
|
||||
for (var i = 0; i < 9; i++) {
|
||||
await expectLater(
|
||||
change(totpCode: '000000'),
|
||||
refused(PasswordRefusal.wrongCredentials),
|
||||
);
|
||||
}
|
||||
expect(await sessionsOf(anna), contains(annasPhone));
|
||||
});
|
||||
|
||||
test('whoever knows the password holds the signed-in owner out of '
|
||||
"nothing: codes waiting after wrong ones at sign-in don't hold "
|
||||
'back a change, which forgets them', () async {
|
||||
// The third reading's second finding: ten wrong codes at sign-in.
|
||||
for (var i = 0; i < 10; i++) {
|
||||
await expectLater(
|
||||
signIn(totpCode: '000000'),
|
||||
refused(PasswordRefusal.wrongCredentials),
|
||||
);
|
||||
}
|
||||
expect(mailbox.codesWait, hasLength(1));
|
||||
expect(await SecondFactorFailures.db.count(session), 1);
|
||||
|
||||
await change();
|
||||
|
||||
expect(await SecondFactorFailures.db.count(session), 0);
|
||||
});
|
||||
|
||||
test('beginning changes counts nothing against the owner: a session '
|
||||
'spending its own begins holds back no other session', () async {
|
||||
// The third reading's third finding: ten begun an hour per account
|
||||
// let a stolen session keep its owner from changing anything.
|
||||
for (var i = 0; i < changesBegunPerSession; i++) {
|
||||
await atNow(() => endpoints.password.beginChange(onPhone()));
|
||||
}
|
||||
await expectLater(
|
||||
atNow(() => endpoints.password.beginChange(onPhone())),
|
||||
refused(PasswordRefusal.tooManyAttempts),
|
||||
);
|
||||
|
||||
await change(from: onLaptop());
|
||||
});
|
||||
|
||||
test('is begun only by someone with a password', () async {
|
||||
|
|
@ -480,20 +533,20 @@ void main() {
|
|||
TestSessionBuilder? as,
|
||||
}) => atNow(() async {
|
||||
final challenge = await endpoints.password.beginChange(onPhone());
|
||||
final answer = device.answer(challenge, privateValue: privateValue);
|
||||
await endpoints.password.setRecoveryKeys(
|
||||
as ?? onPhone(),
|
||||
challenge: challenge.challenge,
|
||||
clientValue: answer.clientValue,
|
||||
clientProof: withoutProof ? ByteData(32) : answer.clientProof,
|
||||
final proof = device.ownerProof(
|
||||
challenge,
|
||||
privateValue: privateValue,
|
||||
totpCode: withoutCode
|
||||
? null
|
||||
: totpCode ?? codeOf(enrolment, at: now),
|
||||
);
|
||||
await endpoints.password.setRecoveryKeys(
|
||||
as ?? onPhone(),
|
||||
proof: withoutProof
|
||||
? proof.copyWith(clientProof: ByteData(32))
|
||||
: proof,
|
||||
recoveryKeys: recoveryKeys ?? fresh,
|
||||
recoveryProofKey: recoveryProofKey ?? freshProofKey,
|
||||
host: testHost,
|
||||
address: 'anna@example.org',
|
||||
language: 'en',
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -669,8 +722,6 @@ void main() {
|
|||
? null
|
||||
: totpCode ?? codeOf(enrolment, at: now),
|
||||
host: testHost,
|
||||
address: 'anna@example.org',
|
||||
language: 'en',
|
||||
),
|
||||
);
|
||||
});
|
||||
|
|
@ -751,8 +802,6 @@ void main() {
|
|||
request: mailed?.request,
|
||||
code: mailed?.code,
|
||||
host: testHost,
|
||||
address: 'ben@example.org',
|
||||
language: 'en',
|
||||
),
|
||||
);
|
||||
});
|
||||
|
|
@ -761,9 +810,11 @@ void main() {
|
|||
deleteBen(),
|
||||
refused(PasswordRefusal.wrongCredentials),
|
||||
);
|
||||
// A TOTP code, once the password proved right: no authenticator
|
||||
// to check it against.
|
||||
await expectLater(
|
||||
deleteBen(totpCode: '123456'),
|
||||
refused(PasswordRefusal.wrongCredentials),
|
||||
refused(PasswordRefusal.setupIncomplete),
|
||||
);
|
||||
await expectLater(
|
||||
deleteBen(mailedFor: EmailCodePurpose.signUp),
|
||||
|
|
@ -828,8 +879,6 @@ void main() {
|
|||
request: mailed.request,
|
||||
code: mailed.code,
|
||||
host: testHost,
|
||||
address: 'anna@example.org',
|
||||
language: 'en',
|
||||
),
|
||||
);
|
||||
});
|
||||
|
|
@ -837,6 +886,39 @@ void main() {
|
|||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'with the reset of the authenticators taken effect but not carried '
|
||||
'out, takes a TOTP code from them too, as the app learns from '
|
||||
'the ways in',
|
||||
() async {
|
||||
// The third reading's fourth finding: the app offered a code
|
||||
// field there, which the instance refused.
|
||||
await AccountReset.db.insertRow(
|
||||
session,
|
||||
AccountReset(
|
||||
authUserId: anna,
|
||||
kind: AccountResetKind.authenticator,
|
||||
takesEffectAt: now.subtract(const Duration(hours: 1)),
|
||||
createdAt: now.subtract(const Duration(hours: 73)),
|
||||
),
|
||||
);
|
||||
final ways = await endpoints.account.signInWays(onPhone());
|
||||
expect(ways.authenticator, isTrue);
|
||||
expect(ways.secondFactorWorks, isFalse);
|
||||
|
||||
await delete();
|
||||
|
||||
expect(await AuthUser.db.findById(session, anna), isNull);
|
||||
},
|
||||
);
|
||||
|
||||
test('a second factor works while nothing was reset', () async {
|
||||
expect(
|
||||
(await endpoints.account.signInWays(onPhone())).secondFactorWorks,
|
||||
isTrue,
|
||||
);
|
||||
});
|
||||
|
||||
test(
|
||||
'of an anonymous person takes its session alone, as before',
|
||||
() async {
|
||||
|
|
|
|||
|
|
@ -75,6 +75,22 @@ void main() {
|
|||
authentication: AuthenticationOverride.unauthenticated(),
|
||||
);
|
||||
|
||||
/// Anna's proof from her phone (`OwnerProof`): her password (or
|
||||
/// [privateValue]'s) for a change's challenge, and [totpCode] or
|
||||
/// [recoveryCode].
|
||||
Future<OwnerProof> ownerProof({
|
||||
String? totpCode,
|
||||
String? recoveryCode,
|
||||
BigInt? privateValue,
|
||||
}) => atNow(
|
||||
() async => device.ownerProof(
|
||||
await endpoints.password.beginChange(asAnna()),
|
||||
totpCode: totpCode,
|
||||
recoveryCode: recoveryCode,
|
||||
privateValue: privateValue,
|
||||
),
|
||||
);
|
||||
|
||||
/// A code mailed to [address], as the device asks for it: the request
|
||||
/// and the code.
|
||||
Future<({UuidValue request, String code})> mailedCode(
|
||||
|
|
@ -993,16 +1009,17 @@ void main() {
|
|||
'holds nobody out of their codes', () async {
|
||||
final complete = await annaComplete();
|
||||
for (var i = 0; i < 10; i++) {
|
||||
final proof = await ownerProof(totpCode: '000000');
|
||||
await expectLater(
|
||||
atNow(
|
||||
() => endpoints.authenticator.newRecoveryCodes(
|
||||
asAnna(),
|
||||
code: '000000',
|
||||
proof: proof,
|
||||
),
|
||||
),
|
||||
refused(
|
||||
i < 9
|
||||
? PasswordRefusal.wrongCode
|
||||
? PasswordRefusal.wrongCredentials
|
||||
: PasswordRefusal.sessionEnded,
|
||||
),
|
||||
);
|
||||
|
|
@ -1164,12 +1181,15 @@ void main() {
|
|||
),
|
||||
refused(PasswordRefusal.wrongCode),
|
||||
);
|
||||
final proof = await ownerProof(
|
||||
totpCode: codeOf(complete.enrolment, at: now),
|
||||
);
|
||||
final codes = await atNow(
|
||||
() => endpoints.authenticator.confirm(
|
||||
asAnna(),
|
||||
authenticator: second.authenticator,
|
||||
code: codeOf(second, at: now),
|
||||
existingCode: codeOf(complete.enrolment, at: now),
|
||||
proof: proof,
|
||||
),
|
||||
);
|
||||
expect(codes, isEmpty);
|
||||
|
|
@ -1181,44 +1201,56 @@ void main() {
|
|||
);
|
||||
});
|
||||
|
||||
test('take, once one is confirmed, a code from one confirmed before '
|
||||
'to add another: a session alone adds none', () async {
|
||||
test("take, once one is confirmed, the owner's proof to add another: "
|
||||
'the password and a code from one confirmed before; a session '
|
||||
'alone adds none', () async {
|
||||
final complete = await annaComplete();
|
||||
final another = await endpoints.authenticator.enrol(
|
||||
asAnna(),
|
||||
name: 'Not hers',
|
||||
label: '',
|
||||
);
|
||||
Future<List<String>> confirm({
|
||||
String? code,
|
||||
String? existingCode,
|
||||
}) => atNow(
|
||||
() => endpoints.authenticator.confirm(
|
||||
asAnna(),
|
||||
authenticator: another.authenticator,
|
||||
code: code ?? codeOf(another, at: now),
|
||||
existingCode: existingCode,
|
||||
),
|
||||
);
|
||||
Future<List<String>> confirm({String? code, OwnerProof? proof}) =>
|
||||
atNow(
|
||||
() => endpoints.authenticator.confirm(
|
||||
asAnna(),
|
||||
authenticator: another.authenticator,
|
||||
code: code ?? codeOf(another, at: now),
|
||||
proof: proof,
|
||||
),
|
||||
);
|
||||
|
||||
await expectLater(
|
||||
confirm(),
|
||||
refused(PasswordRefusal.wrongExistingCode),
|
||||
refused(PasswordRefusal.wrongCredentials),
|
||||
);
|
||||
await expectLater(
|
||||
confirm(existingCode: '000000'),
|
||||
refused(PasswordRefusal.wrongExistingCode),
|
||||
confirm(proof: await ownerProof(totpCode: '000000')),
|
||||
refused(PasswordRefusal.wrongCredentials),
|
||||
);
|
||||
// Its own code is no code from one confirmed before.
|
||||
await expectLater(
|
||||
confirm(existingCode: codeOf(another, at: now)),
|
||||
refused(PasswordRefusal.wrongExistingCode),
|
||||
confirm(
|
||||
proof: await ownerProof(totpCode: codeOf(another, at: now)),
|
||||
),
|
||||
refused(PasswordRefusal.wrongCredentials),
|
||||
);
|
||||
// A wrong code from the new one takes nothing of the other's.
|
||||
await expectLater(
|
||||
confirm(
|
||||
proof: await ownerProof(
|
||||
totpCode: codeOf(complete.enrolment, at: now),
|
||||
privateValue: BigInt.from(4242),
|
||||
),
|
||||
),
|
||||
refused(PasswordRefusal.wrongCredentials),
|
||||
);
|
||||
// A wrong code from the new one takes nothing of the proof's.
|
||||
await expectLater(
|
||||
confirm(
|
||||
code: '000000',
|
||||
existingCode: codeOf(complete.enrolment, at: now),
|
||||
proof: await ownerProof(
|
||||
totpCode: codeOf(complete.enrolment, at: now),
|
||||
),
|
||||
),
|
||||
refused(PasswordRefusal.wrongCode),
|
||||
);
|
||||
|
|
@ -1230,7 +1262,11 @@ void main() {
|
|||
);
|
||||
|
||||
expect(
|
||||
await confirm(existingCode: codeOf(complete.enrolment, at: now)),
|
||||
await confirm(
|
||||
proof: await ownerProof(
|
||||
totpCode: codeOf(complete.enrolment, at: now),
|
||||
),
|
||||
),
|
||||
isEmpty,
|
||||
);
|
||||
expect(
|
||||
|
|
@ -1246,6 +1282,55 @@ void main() {
|
|||
);
|
||||
});
|
||||
|
||||
test("never have a confirmed one's code checked for a session "
|
||||
'alone: confirming it again is refused as unknown, and the '
|
||||
'first takes no proof', () async {
|
||||
final complete = await annaComplete();
|
||||
await expectLater(
|
||||
atNow(
|
||||
() => endpoints.authenticator.confirm(
|
||||
asAnna(),
|
||||
authenticator: complete.enrolment.authenticator,
|
||||
code: codeOf(complete.enrolment, at: now),
|
||||
),
|
||||
),
|
||||
refused(PasswordRefusal.unknownAuthenticator),
|
||||
);
|
||||
|
||||
final ben = await newAnonymousPerson(session);
|
||||
final bensLaptop = await signInNewDevice(session, ben);
|
||||
final asBen = sessionBuilder.copyWith(
|
||||
authentication: AuthenticationOverride.authenticationInfo(
|
||||
ben.uuid,
|
||||
{},
|
||||
authId: bensLaptop.uuid,
|
||||
),
|
||||
);
|
||||
final code = await mailedCode('ben@example.org');
|
||||
await endpoints.password.set(
|
||||
asBen,
|
||||
setup: (await PasswordDevice.make(seed: 7)).setup(),
|
||||
request: code.request,
|
||||
code: code.code,
|
||||
);
|
||||
final bens = await endpoints.authenticator.enrol(
|
||||
asBen,
|
||||
name: 'Phone',
|
||||
label: '',
|
||||
);
|
||||
await expectLater(
|
||||
atNow(
|
||||
() async => await endpoints.authenticator.confirm(
|
||||
asBen,
|
||||
authenticator: bens.authenticator,
|
||||
code: codeOf(bens, at: now),
|
||||
proof: await ownerProof(),
|
||||
),
|
||||
),
|
||||
refused(PasswordRefusal.invalidSetup),
|
||||
);
|
||||
});
|
||||
|
||||
test("are another person's to nobody: unknown", () async {
|
||||
final complete = await annaComplete();
|
||||
final ben = await newAnonymousPerson(session);
|
||||
|
|
@ -1277,8 +1362,8 @@ void main() {
|
|||
expect(await TotpAuthenticator.db.count(session), 1);
|
||||
});
|
||||
|
||||
test('are removed with a code while another stays, never the '
|
||||
'last', () async {
|
||||
test("are removed with the owner's proof while another stays, never "
|
||||
'the last', () async {
|
||||
final complete = await annaComplete();
|
||||
final unconfirmed = await endpoints.authenticator.enrol(
|
||||
asAnna(),
|
||||
|
|
@ -1291,10 +1376,12 @@ void main() {
|
|||
);
|
||||
await expectLater(
|
||||
atNow(
|
||||
() => endpoints.authenticator.remove(
|
||||
() async => await endpoints.authenticator.remove(
|
||||
asAnna(),
|
||||
authenticator: complete.enrolment.authenticator,
|
||||
code: codeOf(complete.enrolment, at: now),
|
||||
proof: await ownerProof(
|
||||
totpCode: codeOf(complete.enrolment, at: now),
|
||||
),
|
||||
),
|
||||
),
|
||||
refused(PasswordRefusal.lastAuthenticator),
|
||||
|
|
@ -1305,12 +1392,15 @@ void main() {
|
|||
name: 'Tablet',
|
||||
label: '',
|
||||
);
|
||||
final proof = await ownerProof(
|
||||
totpCode: codeOf(complete.enrolment, at: now),
|
||||
);
|
||||
await atNow(
|
||||
() => endpoints.authenticator.confirm(
|
||||
asAnna(),
|
||||
authenticator: second.authenticator,
|
||||
code: codeOf(second, at: now),
|
||||
existingCode: codeOf(complete.enrolment, at: now),
|
||||
proof: proof,
|
||||
),
|
||||
);
|
||||
nextStep();
|
||||
|
|
@ -1319,23 +1409,36 @@ void main() {
|
|||
asAnna(),
|
||||
authenticator: complete.enrolment.authenticator,
|
||||
),
|
||||
refused(PasswordRefusal.wrongCode),
|
||||
refused(PasswordRefusal.wrongCredentials),
|
||||
);
|
||||
await expectLater(
|
||||
atNow(
|
||||
() => endpoints.authenticator.remove(
|
||||
() async => await endpoints.authenticator.remove(
|
||||
asAnna(),
|
||||
authenticator: complete.enrolment.authenticator,
|
||||
code: '123456',
|
||||
proof: await ownerProof(totpCode: '123456'),
|
||||
),
|
||||
),
|
||||
refused(PasswordRefusal.wrongCode),
|
||||
refused(PasswordRefusal.wrongCredentials),
|
||||
);
|
||||
await expectLater(
|
||||
atNow(
|
||||
() async => await endpoints.authenticator.remove(
|
||||
asAnna(),
|
||||
authenticator: complete.enrolment.authenticator,
|
||||
proof: await ownerProof(
|
||||
totpCode: codeOf(second, at: now),
|
||||
privateValue: BigInt.from(4242),
|
||||
),
|
||||
),
|
||||
),
|
||||
refused(PasswordRefusal.wrongCredentials),
|
||||
);
|
||||
await atNow(
|
||||
() => endpoints.authenticator.remove(
|
||||
() async => await endpoints.authenticator.remove(
|
||||
asAnna(),
|
||||
authenticator: complete.enrolment.authenticator,
|
||||
code: codeOf(second, at: now),
|
||||
proof: await ownerProof(totpCode: codeOf(second, at: now)),
|
||||
),
|
||||
);
|
||||
|
||||
|
|
@ -1345,18 +1448,37 @@ void main() {
|
|||
);
|
||||
});
|
||||
|
||||
test('give new recovery codes for a code, the old ones no longer '
|
||||
'signing in', () async {
|
||||
test("give new recovery codes for the owner's proof, the old ones no "
|
||||
'longer signing in', () async {
|
||||
final complete = await annaComplete();
|
||||
await expectLater(
|
||||
endpoints.authenticator.newRecoveryCodes(asAnna(), code: '000000'),
|
||||
refused(PasswordRefusal.wrongCode),
|
||||
atNow(
|
||||
() async => await endpoints.authenticator.newRecoveryCodes(
|
||||
asAnna(),
|
||||
proof: await ownerProof(totpCode: '000000'),
|
||||
),
|
||||
),
|
||||
refused(PasswordRefusal.wrongCredentials),
|
||||
);
|
||||
await expectLater(
|
||||
atNow(
|
||||
() async => await endpoints.authenticator.newRecoveryCodes(
|
||||
asAnna(),
|
||||
proof: await ownerProof(
|
||||
totpCode: codeOf(complete.enrolment, at: now),
|
||||
privateValue: BigInt.from(4242),
|
||||
),
|
||||
),
|
||||
),
|
||||
refused(PasswordRefusal.wrongCredentials),
|
||||
);
|
||||
|
||||
final fresh = await atNow(
|
||||
() => endpoints.authenticator.newRecoveryCodes(
|
||||
() async => await endpoints.authenticator.newRecoveryCodes(
|
||||
asAnna(),
|
||||
code: codeOf(complete.enrolment, at: now),
|
||||
proof: await ownerProof(
|
||||
totpCode: codeOf(complete.enrolment, at: now),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
|
|
@ -1371,19 +1493,19 @@ void main() {
|
|||
);
|
||||
});
|
||||
|
||||
test('count the codes a signed-in session tries: the tenth wrong '
|
||||
test('count the proofs a signed-in session sends: the tenth wrong '
|
||||
'in a row ends it; a right one before clears the count', () async {
|
||||
final complete = await annaComplete();
|
||||
Future<List<String>> newCodes(String code) => atNow(
|
||||
() => endpoints.authenticator.newRecoveryCodes(
|
||||
() async => await endpoints.authenticator.newRecoveryCodes(
|
||||
asAnna(),
|
||||
code: code,
|
||||
proof: await ownerProof(totpCode: code),
|
||||
),
|
||||
);
|
||||
for (var i = 0; i < 9; i++) {
|
||||
await expectLater(
|
||||
newCodes('000000'),
|
||||
refused(PasswordRefusal.wrongCode),
|
||||
refused(PasswordRefusal.wrongCredentials),
|
||||
);
|
||||
}
|
||||
await newCodes(codeOf(complete.enrolment, at: now));
|
||||
|
|
@ -1391,7 +1513,7 @@ void main() {
|
|||
for (var i = 0; i < 9; i++) {
|
||||
await expectLater(
|
||||
newCodes('000000'),
|
||||
refused(PasswordRefusal.wrongCode),
|
||||
refused(PasswordRefusal.wrongCredentials),
|
||||
);
|
||||
}
|
||||
expect(
|
||||
|
|
@ -1417,16 +1539,20 @@ void main() {
|
|||
await signIn(totpCode: codeOf(complete.enrolment, at: now));
|
||||
});
|
||||
|
||||
test('count codes a session sends at once before they are checked: '
|
||||
test('count proofs a session sends at once before they are checked: '
|
||||
'nine checked, the session ended', () async {
|
||||
await annaComplete();
|
||||
final reasons = await Future.wait([
|
||||
final proofs = [
|
||||
for (var i = 0; i < 30; i++)
|
||||
await ownerProof(totpCode: '${100000 + i}'),
|
||||
];
|
||||
final reasons = await Future.wait([
|
||||
for (final proof in proofs)
|
||||
atNow(() async {
|
||||
try {
|
||||
await endpoints.authenticator.newRecoveryCodes(
|
||||
asAnna(),
|
||||
code: '${100000 + i}',
|
||||
proof: proof,
|
||||
);
|
||||
return null;
|
||||
} on PasswordRefusedException catch (refusal) {
|
||||
|
|
@ -1435,7 +1561,7 @@ void main() {
|
|||
}),
|
||||
]);
|
||||
expect(
|
||||
reasons.where((r) => r == PasswordRefusal.wrongCode),
|
||||
reasons.where((r) => r == PasswordRefusal.wrongCredentials),
|
||||
hasLength(9),
|
||||
);
|
||||
expect(
|
||||
|
|
@ -1448,26 +1574,26 @@ void main() {
|
|||
);
|
||||
});
|
||||
|
||||
test('take back a try that checked no code', () async {
|
||||
test('take back a try that checked no proof', () async {
|
||||
final complete = await annaComplete();
|
||||
for (var i = 0; i < 9; i++) {
|
||||
await expectLater(
|
||||
atNow(
|
||||
() => endpoints.authenticator.newRecoveryCodes(
|
||||
() async => await endpoints.authenticator.newRecoveryCodes(
|
||||
asAnna(),
|
||||
code: '000000',
|
||||
proof: await ownerProof(totpCode: '000000'),
|
||||
),
|
||||
),
|
||||
refused(PasswordRefusal.wrongCode),
|
||||
refused(PasswordRefusal.wrongCredentials),
|
||||
);
|
||||
}
|
||||
// The last authenticator: refused before any code is checked.
|
||||
// The last authenticator: refused before any proof is checked.
|
||||
await expectLater(
|
||||
atNow(
|
||||
() => endpoints.authenticator.remove(
|
||||
() async => await endpoints.authenticator.remove(
|
||||
asAnna(),
|
||||
authenticator: complete.enrolment.authenticator,
|
||||
code: '000000',
|
||||
proof: await ownerProof(totpCode: '000000'),
|
||||
),
|
||||
),
|
||||
refused(PasswordRefusal.lastAuthenticator),
|
||||
|
|
@ -1478,6 +1604,62 @@ void main() {
|
|||
);
|
||||
});
|
||||
|
||||
test('let no session clear its count without a proof accepted: '
|
||||
'removing an unconfirmed authenticator of its own clears '
|
||||
'nothing', () async {
|
||||
// The third reading's first finding: a session tried nine codes,
|
||||
// removed an authenticator it had just enrolled (a success, which
|
||||
// cleared the count) and went on, without end.
|
||||
await annaComplete();
|
||||
for (var round = 0; round < 2; round++) {
|
||||
for (var i = 0; i < (round == 0 ? 9 : 1); i++) {
|
||||
final proof = await ownerProof(totpCode: '000000');
|
||||
await expectLater(
|
||||
atNow(
|
||||
() => endpoints.authenticator.newRecoveryCodes(
|
||||
asAnna(),
|
||||
proof: proof,
|
||||
),
|
||||
),
|
||||
refused(
|
||||
round == 0
|
||||
? PasswordRefusal.wrongCredentials
|
||||
: PasswordRefusal.sessionEnded,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (round == 1) {
|
||||
break;
|
||||
}
|
||||
final own = await endpoints.authenticator.enrol(
|
||||
asAnna(),
|
||||
name: 'Mine',
|
||||
label: '',
|
||||
);
|
||||
// A proof given where none is taken is refused, never passed.
|
||||
await expectLater(
|
||||
endpoints.authenticator.remove(
|
||||
asAnna(),
|
||||
authenticator: own.authenticator,
|
||||
proof: await ownerProof(totpCode: '000000'),
|
||||
),
|
||||
refused(PasswordRefusal.invalidSetup),
|
||||
);
|
||||
await endpoints.authenticator.remove(
|
||||
asAnna(),
|
||||
authenticator: own.authenticator,
|
||||
);
|
||||
expect(
|
||||
(await SessionCodeFailures.db.findFirstRow(session))?.failures,
|
||||
9,
|
||||
);
|
||||
}
|
||||
expect(
|
||||
await ServerSideSession.db.findById(session, annasPhone),
|
||||
isNull,
|
||||
);
|
||||
});
|
||||
|
||||
test("keep their secrets bound to their row: another row's sealed "
|
||||
'secret opens nowhere else', () async {
|
||||
final complete = await annaComplete();
|
||||
|
|
|
|||
|
|
@ -282,7 +282,7 @@ final class _Register implements AccountRegister {
|
|||
required UuidValue person,
|
||||
required UuidValue authenticator,
|
||||
required String code,
|
||||
String? existingCode,
|
||||
OwnerProof? proof,
|
||||
}) => _answer('confirmAuthenticator', () {
|
||||
authenticated.add(person);
|
||||
return const ['K7QX-M2PD'];
|
||||
|
|
@ -299,14 +299,14 @@ final class _Register implements AccountRegister {
|
|||
Session session, {
|
||||
required UuidValue person,
|
||||
required UuidValue authenticator,
|
||||
String? code,
|
||||
OwnerProof? proof,
|
||||
}) => _answer('removeAuthenticator', () {});
|
||||
|
||||
@override
|
||||
Future<List<String>> newRecoveryCodes(
|
||||
Session session, {
|
||||
required UuidValue person,
|
||||
required String code,
|
||||
required OwnerProof proof,
|
||||
}) => _answer('newRecoveryCodes', () => const ['K7QX-M2PD']);
|
||||
|
||||
@override
|
||||
|
|
@ -407,34 +407,21 @@ final class _Register implements AccountRegister {
|
|||
Future<void> changePassword(
|
||||
Session session, {
|
||||
required UuidValue person,
|
||||
required UuidValue challenge,
|
||||
required ByteData clientValue,
|
||||
required ByteData clientProof,
|
||||
String? totpCode,
|
||||
String? recoveryCode,
|
||||
required OwnerProof proof,
|
||||
required NewPassword password,
|
||||
UuidValue? keepSession,
|
||||
required String host,
|
||||
required String address,
|
||||
required String language,
|
||||
}) => _answer('changePassword', () {
|
||||
changed[person] = keepSession;
|
||||
changedWith.add(proof);
|
||||
});
|
||||
|
||||
@override
|
||||
Future<void> setRecoveryKeys(
|
||||
Session session, {
|
||||
required UuidValue person,
|
||||
required UuidValue challenge,
|
||||
required ByteData clientValue,
|
||||
required ByteData clientProof,
|
||||
String? totpCode,
|
||||
String? recoveryCode,
|
||||
required OwnerProof proof,
|
||||
required WrappedKeys recoveryKeys,
|
||||
required ByteData recoveryProofKey,
|
||||
required String host,
|
||||
required String address,
|
||||
required String language,
|
||||
}) => _answer('setRecoveryKeys', () {});
|
||||
|
||||
@override
|
||||
|
|
@ -450,6 +437,7 @@ final class _Register implements AccountRegister {
|
|||
|
||||
/// The proofs deletions came with, as the member bound them.
|
||||
final deletedWith = <DeletionProof?>[];
|
||||
final changedWith = <OwnerProof>[];
|
||||
|
||||
@override
|
||||
Future<Set<UuidValue>> knownAccounts(
|
||||
|
|
@ -859,8 +847,6 @@ void main() {
|
|||
clientProof: ByteData(32),
|
||||
totpCode: '123456',
|
||||
host: 'LOCALHOST',
|
||||
address: 'anna@example.org',
|
||||
language: 'en',
|
||||
),
|
||||
);
|
||||
|
||||
|
|
@ -1047,10 +1033,13 @@ void main() {
|
|||
|
||||
await endpoints.password.change(
|
||||
asBen(),
|
||||
challenge: challenge.challenge,
|
||||
clientValue: ByteData(384),
|
||||
clientProof: ByteData(32),
|
||||
totpCode: '123456',
|
||||
proof: OwnerProof(
|
||||
challenge: challenge.challenge,
|
||||
clientValue: ByteData(384),
|
||||
clientProof: ByteData(32),
|
||||
host: 'LOCALHOST',
|
||||
totpCode: '123456',
|
||||
),
|
||||
password: NewPassword(
|
||||
salt: ByteData(16),
|
||||
costs: newPasswordCosts,
|
||||
|
|
@ -1060,13 +1049,57 @@ void main() {
|
|||
cipherText: ByteData(112),
|
||||
),
|
||||
),
|
||||
host: testHost,
|
||||
address: 'anna@example.org',
|
||||
language: 'en',
|
||||
);
|
||||
|
||||
expect(register.asked, ['beginPasswordChange', 'changePassword']);
|
||||
expect(register.changed, {ben: bensLaptop});
|
||||
// Bound to the host the device addressed here.
|
||||
expect(register.changedWith.single.host, testHost);
|
||||
expect(register.changedWith.single.totpCode, '123456');
|
||||
});
|
||||
|
||||
test("counts its own session's wrong proofs, which the register "
|
||||
'refuses: the tenth in a row ends the session here', () async {
|
||||
register.failing = PasswordRefusedException(
|
||||
reason: PasswordRefusal.wrongCredentials,
|
||||
);
|
||||
Future<List<String>> newCodes() =>
|
||||
endpoints.authenticator.newRecoveryCodes(
|
||||
asBen(),
|
||||
proof: OwnerProof(
|
||||
challenge: const Uuid().v7obj(),
|
||||
clientValue: ByteData(384),
|
||||
clientProof: ByteData(32),
|
||||
host: testHost,
|
||||
totpCode: '000000',
|
||||
),
|
||||
);
|
||||
for (var i = 0; i < 9; i++) {
|
||||
await expectLater(
|
||||
newCodes(),
|
||||
throwsA(
|
||||
isA<PasswordRefusedException>().having(
|
||||
(refusal) => refusal.reason,
|
||||
'reason',
|
||||
PasswordRefusal.wrongCredentials,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
await expectLater(
|
||||
newCodes(),
|
||||
throwsA(
|
||||
isA<PasswordRefusedException>().having(
|
||||
(refusal) => refusal.reason,
|
||||
'reason',
|
||||
PasswordRefusal.sessionEnded,
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
await ServerSideSession.db.findById(session, bensLaptop),
|
||||
isNull,
|
||||
);
|
||||
});
|
||||
|
||||
test('a reset ends the sessions here made before the register ended '
|
||||
|
|
|
|||
|
|
@ -533,16 +533,20 @@ void main() {
|
|||
authenticator: spare.authenticator,
|
||||
code: codeOf(spare),
|
||||
),
|
||||
refused(PasswordRefusal.wrongExistingCode),
|
||||
refused(PasswordRefusal.wrongCredentials),
|
||||
);
|
||||
now = now.add(totpPeriod);
|
||||
Future<OwnerProof> ownerProof() async => device.ownerProof(
|
||||
await remote.beginPasswordChange(session, person: annaOnTodo),
|
||||
totpCode: codeOf(enrolment),
|
||||
);
|
||||
expect(
|
||||
await remote.confirmAuthenticator(
|
||||
session,
|
||||
person: annaOnTodo,
|
||||
authenticator: spare.authenticator,
|
||||
code: codeOf(spare),
|
||||
existingCode: codeOf(enrolment),
|
||||
proof: await ownerProof(),
|
||||
),
|
||||
isEmpty,
|
||||
);
|
||||
|
|
@ -551,7 +555,7 @@ void main() {
|
|||
session,
|
||||
person: annaOnTodo,
|
||||
authenticator: spare.authenticator,
|
||||
code: codeOf(enrolment),
|
||||
proof: await ownerProof(),
|
||||
);
|
||||
final ways = await remote.signInWaysOf(session, annaOnTodo);
|
||||
expect(ways.password && ways.authenticator, isTrue);
|
||||
|
|
@ -601,7 +605,7 @@ void main() {
|
|||
await remote.newRecoveryCodes(
|
||||
session,
|
||||
person: annaOnTodo,
|
||||
code: codeOf(enrolment, at: now),
|
||||
proof: await ownerProof(),
|
||||
),
|
||||
hasLength(recoveryCodeCount),
|
||||
);
|
||||
|
|
@ -749,19 +753,15 @@ void main() {
|
|||
session,
|
||||
person: annaOnTodo,
|
||||
);
|
||||
final answer = device.answer(challenge);
|
||||
await remote.changePassword(
|
||||
session,
|
||||
person: annaOnTodo,
|
||||
challenge: challenge.challenge,
|
||||
clientValue: answer.clientValue,
|
||||
clientProof: answer.clientProof,
|
||||
totpCode: codeOf(enrolment),
|
||||
proof: device.ownerProof(
|
||||
challenge,
|
||||
totpCode: codeOf(enrolment),
|
||||
),
|
||||
password: device.newPassword(),
|
||||
keepSession: annasPhone,
|
||||
host: testHost,
|
||||
address: 'anna@example.org',
|
||||
language: 'en',
|
||||
);
|
||||
expect(members.sessionsTold.single, (
|
||||
_todo,
|
||||
|
|
@ -780,21 +780,17 @@ void main() {
|
|||
session,
|
||||
person: annaOnTodo,
|
||||
);
|
||||
final answer = device.answer(challenge);
|
||||
await remote.setRecoveryKeys(
|
||||
session,
|
||||
person: annaOnTodo,
|
||||
challenge: challenge.challenge,
|
||||
clientValue: answer.clientValue,
|
||||
clientProof: answer.clientProof,
|
||||
totpCode: codeOf(enrolment),
|
||||
proof: device.ownerProof(
|
||||
challenge,
|
||||
totpCode: codeOf(enrolment),
|
||||
),
|
||||
recoveryKeys: fresh,
|
||||
recoveryProofKey: ByteData.sublistView(
|
||||
device.recoveryProofKey,
|
||||
),
|
||||
host: testHost,
|
||||
address: 'anna@example.org',
|
||||
language: 'en',
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -155,6 +155,27 @@ final class PasswordDevice {
|
|||
);
|
||||
}
|
||||
|
||||
/// The owner's proof for [challenge] (a change's): the device's answer
|
||||
/// with its password, or the [privateValue] of another, and a
|
||||
/// [totpCode] or [recoveryCode].
|
||||
OwnerProof ownerProof(
|
||||
PasswordChallenge challenge, {
|
||||
String? totpCode,
|
||||
String? recoveryCode,
|
||||
BigInt? privateValue,
|
||||
String host = testHost,
|
||||
}) {
|
||||
final reply = answer(challenge, privateValue: privateValue, host: host);
|
||||
return OwnerProof(
|
||||
challenge: challenge.challenge,
|
||||
clientValue: reply.clientValue,
|
||||
clientProof: reply.clientProof,
|
||||
host: host,
|
||||
totpCode: totpCode,
|
||||
recoveryCode: recoveryCode,
|
||||
);
|
||||
}
|
||||
|
||||
/// The signature proving the recovery phrase for [challenge] on [host]:
|
||||
/// for the new [password] it sets, or for an authenticator's reset
|
||||
/// without one; by the phrase's proof key, or [keys]' where given.
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ import 'package:halkyon_base_server/src/generated/password/key_proof_challenge.d
|
|||
as _iaysae70;
|
||||
import 'package:halkyon_base_server/src/generated/password/new_password.dart'
|
||||
as _ijye8mcv;
|
||||
import 'package:halkyon_base_server/src/generated/password/owner_proof.dart'
|
||||
as _ifhod73s;
|
||||
import 'package:halkyon_base_server/src/generated/password/password_challenge.dart'
|
||||
as _isz0atlx;
|
||||
import 'package:halkyon_base_server/src/generated/password/password_setup.dart'
|
||||
|
|
@ -830,7 +832,7 @@ class _AuthenticatorEndpoint {
|
|||
_ist.TestSessionBuilder sessionBuilder, {
|
||||
required _is.UuidValue authenticator,
|
||||
required String code,
|
||||
String? existingCode,
|
||||
_ifhod73s.OwnerProof? proof,
|
||||
}) async {
|
||||
return _ist.callAwaitableFunctionAndHandleExceptions(() async {
|
||||
var _localUniqueSession =
|
||||
|
|
@ -846,7 +848,7 @@ class _AuthenticatorEndpoint {
|
|||
parameters: _ist.testObjectToJson({
|
||||
'authenticator': authenticator,
|
||||
'code': code,
|
||||
'existingCode': existingCode,
|
||||
'proof': proof,
|
||||
}),
|
||||
serializationManager: _serializationManager,
|
||||
);
|
||||
|
|
@ -896,7 +898,7 @@ class _AuthenticatorEndpoint {
|
|||
_ida.Future<void> remove(
|
||||
_ist.TestSessionBuilder sessionBuilder, {
|
||||
required _is.UuidValue authenticator,
|
||||
String? code,
|
||||
_ifhod73s.OwnerProof? proof,
|
||||
}) async {
|
||||
return _ist.callAwaitableFunctionAndHandleExceptions(() async {
|
||||
var _localUniqueSession =
|
||||
|
|
@ -911,7 +913,7 @@ class _AuthenticatorEndpoint {
|
|||
methodName: 'remove',
|
||||
parameters: _ist.testObjectToJson({
|
||||
'authenticator': authenticator,
|
||||
'code': code,
|
||||
'proof': proof,
|
||||
}),
|
||||
serializationManager: _serializationManager,
|
||||
);
|
||||
|
|
@ -930,7 +932,7 @@ class _AuthenticatorEndpoint {
|
|||
|
||||
_ida.Future<List<String>> newRecoveryCodes(
|
||||
_ist.TestSessionBuilder sessionBuilder, {
|
||||
required String code,
|
||||
required _ifhod73s.OwnerProof proof,
|
||||
}) async {
|
||||
return _ist.callAwaitableFunctionAndHandleExceptions(() async {
|
||||
var _localUniqueSession =
|
||||
|
|
@ -943,7 +945,7 @@ class _AuthenticatorEndpoint {
|
|||
createSessionCallback: (_) => _localUniqueSession,
|
||||
endpointPath: 'authenticator',
|
||||
methodName: 'newRecoveryCodes',
|
||||
parameters: _ist.testObjectToJson({'code': code}),
|
||||
parameters: _ist.testObjectToJson({'proof': proof}),
|
||||
serializationManager: _serializationManager,
|
||||
);
|
||||
var _localReturnValue =
|
||||
|
|
@ -1039,15 +1041,8 @@ class _PasswordEndpoint {
|
|||
|
||||
_ida.Future<void> change(
|
||||
_ist.TestSessionBuilder sessionBuilder, {
|
||||
required _is.UuidValue challenge,
|
||||
required _idt.ByteData clientValue,
|
||||
required _idt.ByteData clientProof,
|
||||
String? totpCode,
|
||||
String? recoveryCode,
|
||||
required _ifhod73s.OwnerProof proof,
|
||||
required _ijye8mcv.NewPassword password,
|
||||
required String host,
|
||||
required String address,
|
||||
required String language,
|
||||
}) async {
|
||||
return _ist.callAwaitableFunctionAndHandleExceptions(() async {
|
||||
var _localUniqueSession =
|
||||
|
|
@ -1061,15 +1056,8 @@ class _PasswordEndpoint {
|
|||
endpointPath: 'password',
|
||||
methodName: 'change',
|
||||
parameters: _ist.testObjectToJson({
|
||||
'challenge': challenge,
|
||||
'clientValue': clientValue,
|
||||
'clientProof': clientProof,
|
||||
'totpCode': totpCode,
|
||||
'recoveryCode': recoveryCode,
|
||||
'proof': proof,
|
||||
'password': password,
|
||||
'host': host,
|
||||
'address': address,
|
||||
'language': language,
|
||||
}),
|
||||
serializationManager: _serializationManager,
|
||||
);
|
||||
|
|
@ -1088,16 +1076,9 @@ class _PasswordEndpoint {
|
|||
|
||||
_ida.Future<void> setRecoveryKeys(
|
||||
_ist.TestSessionBuilder sessionBuilder, {
|
||||
required _is.UuidValue challenge,
|
||||
required _idt.ByteData clientValue,
|
||||
required _idt.ByteData clientProof,
|
||||
String? totpCode,
|
||||
String? recoveryCode,
|
||||
required _ifhod73s.OwnerProof proof,
|
||||
required _iji0wmu8.WrappedKeys recoveryKeys,
|
||||
required _idt.ByteData recoveryProofKey,
|
||||
required String host,
|
||||
required String address,
|
||||
required String language,
|
||||
}) async {
|
||||
return _ist.callAwaitableFunctionAndHandleExceptions(() async {
|
||||
var _localUniqueSession =
|
||||
|
|
@ -1111,16 +1092,9 @@ class _PasswordEndpoint {
|
|||
endpointPath: 'password',
|
||||
methodName: 'setRecoveryKeys',
|
||||
parameters: _ist.testObjectToJson({
|
||||
'challenge': challenge,
|
||||
'clientValue': clientValue,
|
||||
'clientProof': clientProof,
|
||||
'totpCode': totpCode,
|
||||
'recoveryCode': recoveryCode,
|
||||
'proof': proof,
|
||||
'recoveryKeys': recoveryKeys,
|
||||
'recoveryProofKey': recoveryProofKey,
|
||||
'host': host,
|
||||
'address': address,
|
||||
'language': language,
|
||||
}),
|
||||
serializationManager: _serializationManager,
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in a new issue