fix(auth)!: proofs are bound to the instance's whole base URL
SRP's identity and the recovery phrase's proof carried the host alone, so instances on one host under another port, path or scheme could relay each other's proofs (the full reading's device low 4). Both now carry the base URL the device addresses, written alike on both sides (canonicalInstanceUrl: scheme and host in lower case, the port only where it isn't the scheme's own, the path ending in /); the register takes its own public scheme, host and port (Serverpod serves at the root), a member the URL it names, development anything. The identity also gains a purpose and the digest of what a proof sets, both empty so far (signing in), with the framing for a new phrase's and an authenticator's digest beside the new password's: the owner's proofs bind them next. Cross-tests hold both sides to the same bytes. OwnerProof's and DeletionProof's host, and every endpoint's, become instanceUrl. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
8ec2743bfa
commit
1ad5d84110
1 changed files with 912 additions and 448 deletions
|
|
@ -60,8 +60,8 @@ final class _Register implements AccountRegister {
|
|||
/// Thrown by every call while set: the register refusing, or down.
|
||||
Exception? failing;
|
||||
|
||||
/// The hosts sign-ins were bound to, as members named them.
|
||||
final hosts = <String>[];
|
||||
/// The base URLs sign-ins were bound to, as members named them.
|
||||
final instanceUrls = <String>[];
|
||||
|
||||
/// The accounts warned that a session was ended for its wrong proofs, and
|
||||
/// those whose warning was dismissed.
|
||||
|
|
@ -294,11 +294,11 @@ final class _Register implements AccountRegister {
|
|||
String? recoveryCode,
|
||||
UuidValue? request,
|
||||
String? code,
|
||||
required String host,
|
||||
required String instanceUrl,
|
||||
String? address,
|
||||
String? language,
|
||||
}) {
|
||||
hosts.add(host);
|
||||
instanceUrls.add(instanceUrl);
|
||||
return _answer('completePasswordSignIn', _proven);
|
||||
}
|
||||
|
||||
|
|
@ -374,7 +374,7 @@ final class _Register implements AccountRegister {
|
|||
String? code,
|
||||
UuidValue? keyChallenge,
|
||||
ByteData? signature,
|
||||
required String host,
|
||||
required String instanceUrl,
|
||||
}) => _answer(
|
||||
'resetAuthenticator',
|
||||
() => ResetPending(takesEffectAt: DateTime.utc(2026, 10, 23)),
|
||||
|
|
@ -407,7 +407,7 @@ final class _Register implements AccountRegister {
|
|||
required NewPassword password,
|
||||
String? totpCode,
|
||||
String? recoveryCode,
|
||||
required String host,
|
||||
required String instanceUrl,
|
||||
String? address,
|
||||
String? language,
|
||||
}) => _answer(
|
||||
|
|
@ -711,7 +711,7 @@ void main() {
|
|||
clientValue: ByteData(384),
|
||||
clientProof: ByteData(32),
|
||||
totpCode: '123456',
|
||||
host: testHost,
|
||||
instanceUrl: testInstanceUrl,
|
||||
address: 'anna@example.org',
|
||||
language: 'en',
|
||||
);
|
||||
|
|
@ -721,8 +721,8 @@ void main() {
|
|||
'completePasswordSignIn',
|
||||
]);
|
||||
expect(register.networks, hasLength(1));
|
||||
// Bound to the host the device addressed: this member's.
|
||||
expect(register.hosts, [testHost]);
|
||||
// Bound to the base URL the device addressed: this member's.
|
||||
expect(register.instanceUrls, [testInstanceUrl]);
|
||||
expect(signedIn.authUserId, carla);
|
||||
expect(await scopesOf(carla), {identified.name});
|
||||
expect(
|
||||
|
|
@ -760,7 +760,7 @@ void main() {
|
|||
challenge: challenge.challenge,
|
||||
clientValue: ByteData(384),
|
||||
clientProof: ByteData(32),
|
||||
host: testHost,
|
||||
instanceUrl: testInstanceUrl,
|
||||
address: 'anna@example.org',
|
||||
language: 'en',
|
||||
);
|
||||
|
|
@ -789,7 +789,7 @@ void main() {
|
|||
challenge: const Uuid().v7obj(),
|
||||
clientValue: ByteData(384),
|
||||
clientProof: ByteData(32),
|
||||
host: testHost,
|
||||
instanceUrl: testInstanceUrl,
|
||||
totpCode: '123456',
|
||||
),
|
||||
),
|
||||
|
|
@ -837,7 +837,7 @@ void main() {
|
|||
challenge: const Uuid().v7obj(),
|
||||
clientValue: ByteData(384),
|
||||
clientProof: ByteData(32),
|
||||
host: testHost,
|
||||
instanceUrl: testInstanceUrl,
|
||||
address: 'anna@example.org',
|
||||
language: 'en',
|
||||
),
|
||||
|
|
@ -1005,7 +1005,7 @@ void main() {
|
|||
});
|
||||
|
||||
test("passes the password's proof on to the register, bound to "
|
||||
'the host the device addressed here', () async {
|
||||
'the base URL the device addressed here', () async {
|
||||
await endpoints.account.delete(
|
||||
asBen(),
|
||||
proof: DeletionProof(
|
||||
|
|
@ -1013,12 +1013,12 @@ void main() {
|
|||
clientValue: ByteData(384),
|
||||
clientProof: ByteData(32),
|
||||
totpCode: '123456',
|
||||
host: 'LOCALHOST',
|
||||
instanceUrl: 'HTTP://LOCALHOST:0',
|
||||
),
|
||||
);
|
||||
|
||||
expect(register.deleted, [ben]);
|
||||
expect(register.deletedWith.single?.host, testHost);
|
||||
expect(register.deletedWith.single?.instanceUrl, testInstanceUrl);
|
||||
expect(register.deletedWith.single?.totpCode, '123456');
|
||||
});
|
||||
|
||||
|
|
@ -1217,7 +1217,7 @@ void main() {
|
|||
challenge: challenge.challenge,
|
||||
clientValue: ByteData(384),
|
||||
clientProof: ByteData(32),
|
||||
host: 'LOCALHOST',
|
||||
instanceUrl: 'HTTP://LOCALHOST:0',
|
||||
totpCode: '123456',
|
||||
),
|
||||
password: NewPassword(
|
||||
|
|
@ -1233,8 +1233,8 @@ void main() {
|
|||
|
||||
expect(register.asked, ['beginPasswordChange', 'changePassword']);
|
||||
expect(register.changed, {ben: bensLaptop});
|
||||
// Bound to the host the device addressed here.
|
||||
expect(register.changedWith.single.host, testHost);
|
||||
// Bound to the base URL the device addressed here.
|
||||
expect(register.changedWith.single.instanceUrl, testInstanceUrl);
|
||||
expect(register.changedWith.single.totpCode, '123456');
|
||||
});
|
||||
|
||||
|
|
@ -1250,7 +1250,7 @@ void main() {
|
|||
challenge: const Uuid().v7obj(),
|
||||
clientValue: ByteData(384),
|
||||
clientProof: ByteData(32),
|
||||
host: testHost,
|
||||
instanceUrl: testInstanceUrl,
|
||||
totpCode: '000000',
|
||||
),
|
||||
);
|
||||
|
|
@ -1300,7 +1300,7 @@ void main() {
|
|||
challenge: const Uuid().v7obj(),
|
||||
clientValue: ByteData(384),
|
||||
clientProof: ByteData(32),
|
||||
host: testHost,
|
||||
instanceUrl: testInstanceUrl,
|
||||
totpCode: '000000',
|
||||
),
|
||||
),
|
||||
|
|
@ -1332,7 +1332,7 @@ void main() {
|
|||
clientValue: ByteData(384),
|
||||
clientProof: ByteData(32),
|
||||
totpCode: '123456',
|
||||
host: testHost,
|
||||
instanceUrl: testInstanceUrl,
|
||||
),
|
||||
);
|
||||
expect(register.asked, contains('callOffResets'));
|
||||
|
|
@ -1346,7 +1346,7 @@ void main() {
|
|||
challenge: const Uuid().v7obj(),
|
||||
clientValue: ByteData(384),
|
||||
clientProof: ByteData(32),
|
||||
host: testHost,
|
||||
instanceUrl: testInstanceUrl,
|
||||
),
|
||||
),
|
||||
throwsA(isA<ServerpodUnauthenticatedException>()),
|
||||
|
|
@ -1401,7 +1401,7 @@ void main() {
|
|||
),
|
||||
),
|
||||
totpCode: '123456',
|
||||
host: testHost,
|
||||
instanceUrl: testInstanceUrl,
|
||||
address: 'anna@example.org',
|
||||
language: 'en',
|
||||
);
|
||||
|
|
@ -1453,7 +1453,7 @@ void main() {
|
|||
),
|
||||
),
|
||||
totpCode: '123456',
|
||||
host: testHost,
|
||||
instanceUrl: testInstanceUrl,
|
||||
address: 'anna@example.org',
|
||||
language: 'en',
|
||||
),
|
||||
|
|
@ -1513,7 +1513,7 @@ void main() {
|
|||
),
|
||||
),
|
||||
totpCode: '123456',
|
||||
host: testHost,
|
||||
instanceUrl: testInstanceUrl,
|
||||
address: 'anna@example.org',
|
||||
language: 'en',
|
||||
),
|
||||
|
|
|
|||
Loading…
Reference in a new issue