fix(auth): a capped wait, a long instance secret, writes of their own columns

Three of the second reading's lows:
- A member waits at most 5 seconds (maxClockSkew) for the register's
  moment before making a session after a reset; a clock further behind
  is refused at once (unavailable, logged), rather than holding the
  request for as long as the clocks differ.
- instanceSecret, which lets a service set passwords and delete accounts
  for anyone, stops the server when set shorter than 32 characters, as
  the password keys do.
- A change of password, a reset keeping the keys and a new phrase each
  write their own columns alone (writeNewPassword, columns: in
  setRecoveryKeys), so one made from the row as it was before another
  never undoes it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-20 21:30:19 +02:00
commit 5b9cb85b0f
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -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/passwords.dart'
show writeNewPassword;
import 'package:serverpod/serverpod.dart';
import 'package:serverpod_auth_idp_server/core.dart';
import 'package:test/test.dart';
@ -412,6 +414,39 @@ void main() {
expect(await proves(freshProof), isTrue);
});
test('and a change of password made from the row as it was before '
'each write their own columns alone: neither undoes the '
'other', () async {
final before = (await AccountPassword.db.findFirstRow(
session,
where: (t) => t.authUserId.equals(anna),
))!;
await newPhrase();
final newPassword = device.newPassword();
await writeNewPassword(session, before, newPassword);
final after = (await AccountPassword.db.findFirstRow(
session,
where: (t) => t.authUserId.equals(anna),
))!;
expect(
WrappedKeys(
nonce: after.recoveryKeysNonce,
cipherText: after.recoveryKeysCipherText,
).toJson(),
fresh.toJson(),
);
expect(
Uint8List.sublistView(after.recoveryProofKey!),
Uint8List.sublistView(freshProofKey),
);
expect(
Uint8List.sublistView(after.verifier),
Uint8List.sublistView(newPassword.verifier),
);
});
test("is refused alike without the password's proof, for a wrong "
"one, another person's, and a missing or wrong code, keeping "
'the wrapping', () async {