feat(base): mending a broken SimpleLogin link with the handle and the recovery phrase (ADR 0020, D7)
Someone whose SimpleLogin identity is new to the register but who has an
account ("I already have one") names their handle and proves the
account's recovery phrase; the new identity takes the old one's place.
- `simpleLogin.beginMend(attempt, handle)`: the keys wrapped under the
phrase's key, the public keys and a nonce, for the account with that
handle (whatever its case). Only an account with keys and a SimpleLogin
identity is mended, a link it had, as nobody adds SimpleLogin signed
out (D10); a handle nobody has, or any other account's, gets a decoy
from `signInDecoyKey`, the same each time. At most 10 an hour per
handle and 30 per network.
- `simpleLogin.mend(attempt, proof)`: the phrase's proof for `mendLink`,
binding the attempt (`mendLinkDigest`, on both sides), so a proof
relayed from another sign-in links nobody's identity but the device's
own; checked for the waiting identity first, which leaves the proof
unspent. Every session of the account ends, in every service, and the
sign-in stands ten minutes for a change, such as a lost password.
- SimpleLogin needs `SIGN_IN_DECOY_KEY` alone of ADR 0017's three keys
(`signInDecoyKeyOf`); `addInstanceRoutes` stops a server with a
SimpleLogin app and without it (`checkSimpleLoginMending`).
- Members ask the register for both; `RemoteRegister` and the instance
API carry them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
7c58d23424
commit
e5b9578916
3 changed files with 1256 additions and 24 deletions
|
|
@ -968,6 +968,49 @@ class EndpointSimpleLogin extends _isc.EndpointRef {
|
|||
'setup': setup,
|
||||
},
|
||||
);
|
||||
|
||||
/// The first step of mending your SimpleLogin link (ADR 0020, 8 and D7),
|
||||
/// for someone whose identity waits under [attempt] ([complete] said
|
||||
/// `newcomer`) who already has an account, with [handle]: the keys
|
||||
/// wrapped under its recovery phrase's key and its public keys, which
|
||||
/// your device opens with the phrase, and a nonce to sign with the
|
||||
/// phrase's proof key for [mend]; a handle nobody has gets a decoy alike.
|
||||
/// At most 10 for a handle and 30 from a network in an hour
|
||||
/// (`PasswordRefusal.tooManyAttempts`). Refused for a signed-in caller
|
||||
/// (`OidcRefusal.wrongCaller`), and as `beginSimpleLoginMend` refuses.
|
||||
_ida.Future<_ir3ecgdn.KeyProofChallenge> beginMend({
|
||||
required _isc.UuidValue attempt,
|
||||
required String handle,
|
||||
}) => caller.callServerEndpoint<_ir3ecgdn.KeyProofChallenge>(
|
||||
'halkyon_base.simpleLogin',
|
||||
'beginMend',
|
||||
{
|
||||
'attempt': attempt,
|
||||
'handle': handle,
|
||||
},
|
||||
);
|
||||
|
||||
/// Mends your SimpleLogin link with [proof], the recovery phrase's for a
|
||||
/// challenge of [beginMend], binding [attempt] (`mendLinkDigest`) and the
|
||||
/// base URL your device addressed here: the identity waiting under
|
||||
/// [attempt] takes your account's old one's place, every other session of
|
||||
/// the account ends, in every service, and this one signs in, as
|
||||
/// [complete] signs in (the sign-in standing ten minutes for a change,
|
||||
/// such as `keys.resetPassword`). Refused for a signed-in caller
|
||||
/// (`OidcRefusal.wrongCaller`), for a proof that proves nothing, alike for
|
||||
/// a handle nobody has (`PasswordRefusal.wrongCredentials`), and as
|
||||
/// `mendSimpleLogin` refuses.
|
||||
_ida.Future<_ianfx7qj.SimpleLoginCompleted> mend({
|
||||
required _isc.UuidValue attempt,
|
||||
required _iznbn55x.KeysProof proof,
|
||||
}) => caller.callServerEndpoint<_ianfx7qj.SimpleLoginCompleted>(
|
||||
'halkyon_base.simpleLogin',
|
||||
'mend',
|
||||
{
|
||||
'attempt': attempt,
|
||||
'proof': proof,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
class Caller extends _isc.ModuleEndpointCaller {
|
||||
|
|
|
|||
|
|
@ -34,7 +34,12 @@ enum SignInChallengePurpose implements _isc.SerializableModel {
|
|||
/// Proving the recovery phrase of an account without SRP's verifier,
|
||||
/// signed in, by signing a nonce with the phrase's proof key, for a lost
|
||||
/// password (Halkyon Learn's ADR 0020, D20). Counted per session.
|
||||
phraseProof;
|
||||
phraseProof,
|
||||
|
||||
/// Proving the recovery phrase signed out, to mend a broken SimpleLogin
|
||||
/// link (Halkyon Learn's ADR 0020, 8): begun for a handle, a decoy's
|
||||
/// for one nobody has.
|
||||
mend;
|
||||
|
||||
static SignInChallengePurpose fromJson(String name) {
|
||||
switch (name) {
|
||||
|
|
@ -48,6 +53,8 @@ enum SignInChallengePurpose implements _isc.SerializableModel {
|
|||
return SignInChallengePurpose.passwordProof;
|
||||
case 'phraseProof':
|
||||
return SignInChallengePurpose.phraseProof;
|
||||
case 'mend':
|
||||
return SignInChallengePurpose.mend;
|
||||
default:
|
||||
throw ArgumentError(
|
||||
'Value "$name" cannot be converted to "SignInChallengePurpose"',
|
||||
|
|
|
|||
|
|
@ -194,6 +194,12 @@ Uint8List newPhraseDigest({
|
|||
proofKey,
|
||||
]);
|
||||
|
||||
/// The SHA-256 of a SimpleLogin attempt's ID (its 16 bytes), framed: what
|
||||
/// a proof that mends a SimpleLogin link binds, so a proof relayed from
|
||||
/// another sign-in links nobody's identity but the device's own.
|
||||
Uint8List mendLinkDigest(List<int> attempt) =>
|
||||
_sha256([utf8.encode('halkyon mend link v1'), attempt]);
|
||||
|
||||
/// The SHA-256 of an authenticator's ID (its 16 bytes), framed: what the
|
||||
/// owner's proof for confirming or removing it binds.
|
||||
Uint8List authenticatorDigest(List<int> id) =>
|
||||
|
|
|
|||
Loading…
Reference in a new issue