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
4 changed files with 912 additions and 448 deletions
|
|
@ -117,7 +117,7 @@ void main() {
|
|||
clientValue: answer.clientValue,
|
||||
clientProof: answer.clientProof,
|
||||
totpCode: totpCode ?? codeOf(enrolment, at: now),
|
||||
host: testHost,
|
||||
instanceUrl: testInstanceUrl,
|
||||
address: 'anna@example.org',
|
||||
language: 'en',
|
||||
);
|
||||
|
|
@ -340,7 +340,7 @@ void main() {
|
|||
clientValue: answer.clientValue,
|
||||
clientProof: answer.clientProof,
|
||||
totpCode: '000000',
|
||||
host: testHost,
|
||||
instanceUrl: testInstanceUrl,
|
||||
address: 'anna@example.org',
|
||||
language: 'en',
|
||||
),
|
||||
|
|
@ -392,7 +392,7 @@ void main() {
|
|||
clientValue: answer.clientValue,
|
||||
clientProof: answer.clientProof,
|
||||
totpCode: codeOf(enrolment, at: now),
|
||||
host: testHost,
|
||||
instanceUrl: testInstanceUrl,
|
||||
address: 'anna@example.org',
|
||||
language: 'en',
|
||||
);
|
||||
|
|
@ -705,7 +705,7 @@ void main() {
|
|||
signature: signature,
|
||||
password: newPassword,
|
||||
totpCode: codeOf(enrolment, at: now),
|
||||
host: testHost,
|
||||
instanceUrl: testInstanceUrl,
|
||||
address: 'anna@example.org',
|
||||
language: 'en',
|
||||
),
|
||||
|
|
@ -822,13 +822,13 @@ void main() {
|
|||
/// Deletes Anna's account from her phone, with the password's proof
|
||||
/// (hers by default, by [privateValue] otherwise, none [withoutProof])
|
||||
/// and [totpCode] (her authenticator's now by default), bound to
|
||||
/// [host].
|
||||
/// [instanceUrl].
|
||||
Future<void> delete({
|
||||
BigInt? privateValue,
|
||||
String? totpCode,
|
||||
bool withoutCode = false,
|
||||
bool withoutProof = false,
|
||||
String host = testHost,
|
||||
String instanceUrl = testInstanceUrl,
|
||||
}) => atNow(() async {
|
||||
if (withoutProof) {
|
||||
return await endpoints.account.delete(onPhone());
|
||||
|
|
@ -837,7 +837,7 @@ void main() {
|
|||
final answer = device.answer(
|
||||
challenge,
|
||||
privateValue: privateValue,
|
||||
host: host,
|
||||
instanceUrl: instanceUrl,
|
||||
);
|
||||
await endpoints.account.delete(
|
||||
onPhone(),
|
||||
|
|
@ -848,7 +848,7 @@ void main() {
|
|||
totpCode: withoutCode
|
||||
? null
|
||||
: totpCode ?? codeOf(enrolment, at: now),
|
||||
host: testHost,
|
||||
instanceUrl: testInstanceUrl,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
|
@ -874,7 +874,7 @@ void main() {
|
|||
refused(PasswordRefusal.wrongCredentials),
|
||||
);
|
||||
await expectLater(
|
||||
delete(host: 'learn.evil.example'),
|
||||
delete(instanceUrl: 'https://learn.evil.example/'),
|
||||
refused(PasswordRefusal.wrongCredentials),
|
||||
);
|
||||
expect(await AuthUser.db.findById(session, anna), isNotNull);
|
||||
|
|
@ -928,7 +928,7 @@ void main() {
|
|||
totpCode: totpCode,
|
||||
request: mailed?.request,
|
||||
code: mailed?.code,
|
||||
host: testHost,
|
||||
instanceUrl: testInstanceUrl,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
|
@ -977,7 +977,7 @@ void main() {
|
|||
language: 'en',
|
||||
request: code.request,
|
||||
code: code.code,
|
||||
host: testHost,
|
||||
instanceUrl: testInstanceUrl,
|
||||
);
|
||||
});
|
||||
// Before it takes effect, the second factor works: no code
|
||||
|
|
@ -1005,7 +1005,7 @@ void main() {
|
|||
clientProof: answer.clientProof,
|
||||
request: mailed.request,
|
||||
code: mailed.code,
|
||||
host: testHost,
|
||||
instanceUrl: testInstanceUrl,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
|
@ -1083,7 +1083,7 @@ void main() {
|
|||
signature: signature,
|
||||
password: newPassword,
|
||||
totpCode: codeOf(enrolment, at: now),
|
||||
host: testHost,
|
||||
instanceUrl: testInstanceUrl,
|
||||
address: 'anna@example.org',
|
||||
language: 'en',
|
||||
),
|
||||
|
|
|
|||
|
|
@ -191,7 +191,7 @@ void main() {
|
|||
clientProof: answer.clientProof,
|
||||
totpCode: totpCode,
|
||||
recoveryCode: recoveryCode,
|
||||
host: testHost,
|
||||
instanceUrl: testInstanceUrl,
|
||||
address: address,
|
||||
language: 'en',
|
||||
);
|
||||
|
|
@ -623,7 +623,7 @@ void main() {
|
|||
clientValue: answer.clientValue,
|
||||
clientProof: answer.clientProof,
|
||||
totpCode: codeOf(complete.enrolment, at: now),
|
||||
host: testHost,
|
||||
instanceUrl: testInstanceUrl,
|
||||
address: 'anna@example.org',
|
||||
language: 'en',
|
||||
),
|
||||
|
|
@ -764,7 +764,7 @@ void main() {
|
|||
clientValue: answer.clientValue,
|
||||
clientProof: answer.clientProof,
|
||||
totpCode: codeOf(complete.enrolment, at: now),
|
||||
host: testHost,
|
||||
instanceUrl: testInstanceUrl,
|
||||
address: 'anna@example.org',
|
||||
language: 'en',
|
||||
),
|
||||
|
|
@ -793,7 +793,7 @@ void main() {
|
|||
clientValue: lateAnswer.clientValue,
|
||||
clientProof: lateAnswer.clientProof,
|
||||
totpCode: codeOf(complete.enrolment, at: now),
|
||||
host: testHost,
|
||||
instanceUrl: testInstanceUrl,
|
||||
address: 'anna@example.org',
|
||||
language: 'en',
|
||||
),
|
||||
|
|
@ -807,7 +807,7 @@ void main() {
|
|||
challenge: const Uuid().v7obj(),
|
||||
clientValue: lateAnswer.clientValue,
|
||||
clientProof: lateAnswer.clientProof,
|
||||
host: testHost,
|
||||
instanceUrl: testInstanceUrl,
|
||||
address: 'anna@example.org',
|
||||
language: 'en',
|
||||
),
|
||||
|
|
@ -835,7 +835,7 @@ void main() {
|
|||
),
|
||||
clientProof: ByteData(32),
|
||||
totpCode: codeOf(complete.enrolment, at: now),
|
||||
host: testHost,
|
||||
instanceUrl: testInstanceUrl,
|
||||
address: 'anna@example.org',
|
||||
language: 'en',
|
||||
),
|
||||
|
|
@ -845,14 +845,43 @@ void main() {
|
|||
}
|
||||
});
|
||||
|
||||
test('refuses a proof made for another host, as an instance the '
|
||||
'device was lured into adding would relay it, alike; and tells a '
|
||||
'device naming another host that the instance has another '
|
||||
'name', () async {
|
||||
test('refuses a proof made for another instance (another host, '
|
||||
'port, path or scheme), as an instance the device was lured into '
|
||||
'adding would relay it, alike; and tells a device naming another '
|
||||
'that the instance has another address', () async {
|
||||
final complete = await annaComplete();
|
||||
|
||||
// The device proved its password to learn.evil.example, which
|
||||
// passes A and M1 on as they are.
|
||||
// The device proved its password to another instance, which
|
||||
// passes A and M1 on as they are: on another host, or beside this
|
||||
// one on the same host.
|
||||
for (final other in [
|
||||
'https://learn.evil.example/',
|
||||
'http://localhost:8081/',
|
||||
'http://localhost:0/evil/',
|
||||
'https://localhost:0/',
|
||||
]) {
|
||||
await expectLater(
|
||||
atNow(() async {
|
||||
final challenge = await endpoints.passwordSignIn.begin(
|
||||
notSignedIn(),
|
||||
address: 'anna@example.org',
|
||||
);
|
||||
final answer = device.answer(challenge, instanceUrl: other);
|
||||
return await endpoints.passwordSignIn.complete(
|
||||
notSignedIn(),
|
||||
challenge: challenge.challenge,
|
||||
clientValue: answer.clientValue,
|
||||
clientProof: answer.clientProof,
|
||||
totpCode: codeOf(complete.enrolment, at: now),
|
||||
instanceUrl: testInstanceUrl,
|
||||
address: 'anna@example.org',
|
||||
language: 'en',
|
||||
);
|
||||
}),
|
||||
refused(PasswordRefusal.wrongCredentials),
|
||||
reason: other,
|
||||
);
|
||||
}
|
||||
await expectLater(
|
||||
atNow(() async {
|
||||
final challenge = await endpoints.passwordSignIn.begin(
|
||||
|
|
@ -861,7 +890,7 @@ void main() {
|
|||
);
|
||||
final answer = device.answer(
|
||||
challenge,
|
||||
host: 'learn.evil.example',
|
||||
instanceUrl: 'https://learn.evil.example/',
|
||||
);
|
||||
return await endpoints.passwordSignIn.complete(
|
||||
notSignedIn(),
|
||||
|
|
@ -869,30 +898,7 @@ void main() {
|
|||
clientValue: answer.clientValue,
|
||||
clientProof: answer.clientProof,
|
||||
totpCode: codeOf(complete.enrolment, at: now),
|
||||
host: testHost,
|
||||
address: 'anna@example.org',
|
||||
language: 'en',
|
||||
);
|
||||
}),
|
||||
refused(PasswordRefusal.wrongCredentials),
|
||||
);
|
||||
await expectLater(
|
||||
atNow(() async {
|
||||
final challenge = await endpoints.passwordSignIn.begin(
|
||||
notSignedIn(),
|
||||
address: 'anna@example.org',
|
||||
);
|
||||
final answer = device.answer(
|
||||
challenge,
|
||||
host: 'learn.evil.example',
|
||||
);
|
||||
return await endpoints.passwordSignIn.complete(
|
||||
notSignedIn(),
|
||||
challenge: challenge.challenge,
|
||||
clientValue: answer.clientValue,
|
||||
clientProof: answer.clientProof,
|
||||
totpCode: codeOf(complete.enrolment, at: now),
|
||||
host: 'learn.evil.example',
|
||||
instanceUrl: 'https://learn.evil.example/',
|
||||
address: 'anna@example.org',
|
||||
language: 'en',
|
||||
);
|
||||
|
|
@ -975,7 +981,7 @@ void main() {
|
|||
clientValue: answer.clientValue,
|
||||
clientProof: answer.clientProof,
|
||||
totpCode: '000000',
|
||||
host: testHost,
|
||||
instanceUrl: testInstanceUrl,
|
||||
address: address,
|
||||
language: 'en',
|
||||
),
|
||||
|
|
@ -1026,7 +1032,7 @@ void main() {
|
|||
clientProof: answer.clientProof,
|
||||
totpCode: recoveryCode == null ? totpCode : null,
|
||||
recoveryCode: recoveryCode,
|
||||
host: testHost,
|
||||
instanceUrl: testInstanceUrl,
|
||||
address: 'Anna@example.org',
|
||||
language: 'de',
|
||||
);
|
||||
|
|
@ -1122,7 +1128,7 @@ void main() {
|
|||
clientValue: answer.clientValue,
|
||||
clientProof: answer.clientProof,
|
||||
totpCode: '${100000 + i}',
|
||||
host: testHost,
|
||||
instanceUrl: testInstanceUrl,
|
||||
address: 'anna@example.org',
|
||||
language: 'en',
|
||||
);
|
||||
|
|
@ -1165,7 +1171,7 @@ void main() {
|
|||
clientValue: answer.clientValue,
|
||||
clientProof: answer.clientProof,
|
||||
totpCode: codeOf(complete.enrolment, at: now),
|
||||
host: testHost,
|
||||
instanceUrl: testInstanceUrl,
|
||||
address: address,
|
||||
language: 'en',
|
||||
);
|
||||
|
|
@ -1208,7 +1214,7 @@ void main() {
|
|||
clientValue: answer.clientValue,
|
||||
clientProof: answer.clientProof,
|
||||
totpCode: '000000',
|
||||
host: testHost,
|
||||
instanceUrl: testInstanceUrl,
|
||||
address: 'anna@example.org',
|
||||
language: 'en',
|
||||
);
|
||||
|
|
@ -1474,7 +1480,7 @@ void main() {
|
|||
language: 'en',
|
||||
request: code.request,
|
||||
code: code.code,
|
||||
host: testHost,
|
||||
instanceUrl: testInstanceUrl,
|
||||
);
|
||||
});
|
||||
now = now.add(resetDelay);
|
||||
|
|
@ -2156,7 +2162,7 @@ void main() {
|
|||
language: 'de',
|
||||
request: code.request,
|
||||
code: code.code,
|
||||
host: testHost,
|
||||
instanceUrl: testInstanceUrl,
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -2331,7 +2337,7 @@ void main() {
|
|||
language: 'en',
|
||||
keyChallenge: keyChallenge.challenge,
|
||||
signature: await device.sign(keyChallenge),
|
||||
host: testHost,
|
||||
instanceUrl: testInstanceUrl,
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -2392,7 +2398,7 @@ void main() {
|
|||
keyChallenge,
|
||||
keys: await otherSigningKeys(),
|
||||
),
|
||||
host: testHost,
|
||||
instanceUrl: testInstanceUrl,
|
||||
);
|
||||
}),
|
||||
refused(PasswordRefusal.wrongCredentials),
|
||||
|
|
@ -2411,7 +2417,7 @@ void main() {
|
|||
clientProof: answer.clientProof,
|
||||
address: 'anna@example.org',
|
||||
language: 'en',
|
||||
host: testHost,
|
||||
instanceUrl: testInstanceUrl,
|
||||
);
|
||||
}),
|
||||
refused(PasswordRefusal.wrongCredentials),
|
||||
|
|
@ -2449,7 +2455,7 @@ void main() {
|
|||
language: 'en',
|
||||
request: code.request,
|
||||
code: code.code,
|
||||
host: testHost,
|
||||
instanceUrl: testInstanceUrl,
|
||||
),
|
||||
),
|
||||
refusedAsEmail(EmailSignInRefusal.mailFailed),
|
||||
|
|
@ -2485,7 +2491,7 @@ void main() {
|
|||
),
|
||||
password: newPassword,
|
||||
totpCode: totpCode,
|
||||
host: testHost,
|
||||
instanceUrl: testInstanceUrl,
|
||||
address: address,
|
||||
language: 'en',
|
||||
);
|
||||
|
|
@ -2629,7 +2635,7 @@ void main() {
|
|||
challenge: challenge.challenge,
|
||||
signature: signature,
|
||||
password: newPassword,
|
||||
host: testHost,
|
||||
instanceUrl: testInstanceUrl,
|
||||
address: 'anna@example.org',
|
||||
language: 'en',
|
||||
),
|
||||
|
|
@ -2644,7 +2650,7 @@ void main() {
|
|||
signature: signature,
|
||||
password: newPassword,
|
||||
totpCode: codeOf(complete.enrolment, at: now),
|
||||
host: testHost,
|
||||
instanceUrl: testInstanceUrl,
|
||||
address: 'anna@example.org',
|
||||
language: 'en',
|
||||
),
|
||||
|
|
@ -2666,7 +2672,7 @@ void main() {
|
|||
signature: signature,
|
||||
password: newPassword,
|
||||
totpCode: codeOf(complete.enrolment, at: now),
|
||||
host: testHost,
|
||||
instanceUrl: testInstanceUrl,
|
||||
address: 'anna@example.org',
|
||||
language: 'en',
|
||||
),
|
||||
|
|
@ -2719,7 +2725,7 @@ void main() {
|
|||
);
|
||||
});
|
||||
|
||||
test('takes no proof made for another host, for an authenticator '
|
||||
test('takes no proof made for another instance, for an authenticator '
|
||||
'reset, or for another password than the one sent: whoever '
|
||||
'stands between swaps in nothing', () async {
|
||||
final complete = await annaComplete();
|
||||
|
|
@ -2738,7 +2744,7 @@ void main() {
|
|||
signature: await sign(challenge, newPassword),
|
||||
password: newPassword,
|
||||
totpCode: codeOf(complete.enrolment, at: now),
|
||||
host: testHost,
|
||||
instanceUrl: testInstanceUrl,
|
||||
address: 'anna@example.org',
|
||||
language: 'en',
|
||||
);
|
||||
|
|
@ -2749,7 +2755,7 @@ void main() {
|
|||
(challenge, password) => device.sign(
|
||||
challenge,
|
||||
password: password,
|
||||
host: 'learn.evil.example',
|
||||
instanceUrl: 'https://learn.evil.example/',
|
||||
),
|
||||
),
|
||||
refused(PasswordRefusal.wrongCredentials),
|
||||
|
|
@ -2803,7 +2809,7 @@ void main() {
|
|||
clientValue: answer.clientValue,
|
||||
clientProof: answer.clientProof,
|
||||
totpCode: '000000',
|
||||
host: testHost,
|
||||
instanceUrl: testInstanceUrl,
|
||||
address: 'anna@example.org',
|
||||
language: 'en',
|
||||
),
|
||||
|
|
@ -2843,7 +2849,7 @@ void main() {
|
|||
clientValue: answer.clientValue,
|
||||
clientProof: answer.clientProof,
|
||||
totpCode: '000000',
|
||||
host: testHost,
|
||||
instanceUrl: testInstanceUrl,
|
||||
address: 'anna@example.org',
|
||||
language: 'en',
|
||||
),
|
||||
|
|
@ -2904,7 +2910,7 @@ void main() {
|
|||
signature: signature,
|
||||
password: newPassword,
|
||||
totpCode: codeOf(complete.enrolment, at: now),
|
||||
host: testHost,
|
||||
instanceUrl: testInstanceUrl,
|
||||
address: 'anna@example.org',
|
||||
language: 'en',
|
||||
),
|
||||
|
|
@ -3104,7 +3110,7 @@ void main() {
|
|||
language: 'en',
|
||||
request: code.request,
|
||||
code: code.code,
|
||||
host: testHost,
|
||||
instanceUrl: testInstanceUrl,
|
||||
);
|
||||
});
|
||||
// An hour on, whoever reads her mail asks for the account's reset.
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ void main() {
|
|||
clientValue: answer.clientValue,
|
||||
clientProof: answer.clientProof,
|
||||
totpCode: totp,
|
||||
host: testHost,
|
||||
instanceUrl: testInstanceUrl,
|
||||
address: 'anna@example.org',
|
||||
language: 'en',
|
||||
);
|
||||
|
|
@ -162,7 +162,7 @@ void main() {
|
|||
language: 'en',
|
||||
request: code.request,
|
||||
code: code.code,
|
||||
host: testHost,
|
||||
instanceUrl: testInstanceUrl,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
@ -226,7 +226,7 @@ void main() {
|
|||
password: newPassword,
|
||||
totpCode: totp,
|
||||
recoveryCode: recovery,
|
||||
host: testHost,
|
||||
instanceUrl: testInstanceUrl,
|
||||
address: 'anna@example.org',
|
||||
language: 'en',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ void main() {
|
|||
totpCode: totpCode,
|
||||
request: mailed?.request,
|
||||
code: wrongMailedCode ?? mailed?.code,
|
||||
host: testHost,
|
||||
instanceUrl: testInstanceUrl,
|
||||
address: address,
|
||||
language: 'en',
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in a new issue