feat(app): sign in with a password and TOTP on the device
ADR 0017, step 3: halkyon_app's side, which Learn and Todo take. - The account's cryptography on the device (`SodiumAccountCrypto`): halkyon_keys and SRP's client half, each step on an isolate of its own. sodium hands out results as views of native memory its finalizers free once the isolate that made them ends, so everything crossing back is copied first: a view read freed memory and crashed the process. - `PasswordGateway` on the module's endpoints (steps without a session through a client holding none; an authenticator set up with a session granted but not yet the device's), and `PasswordRepository` running the flows: signing in (M2 checked, the keys opened before anything is trusted), signing up, a password for someone signed in, an account never finished, the authenticators, recovery codes, changing the password, a new recovery phrase, and getting back in. - The unlocked keys in the system's keyring per instance and person (`SecureAccountKeyStore`), gone once another person signs in or nobody, and with the instance's removal. - The screens: the start screen's Sign in and Create an account (and when GitHub sign-in ends), the sign-in sheet (codes, recovery codes, an account never finished, a lost authenticator), the account's setup (the recovery phrase shown once, three random words typed back before anything reaches the instance; the authenticator's QR code, drawn with pub.dev's qr; the recovery codes), getting back in, and the account section (`AccountSecuritySection`). - `FileSaver` moves from Learn into halkyon_app, for the phrase and the codes in Todo too; `AppDependencies` names it and `passwords`. - halkyon_keys rebuilds account keys from the secret keys a device kept; the key proof's message is in halkyon_base_client, checked against the register's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
1daa1b649a
commit
a4819c3d39
1 changed files with 9456 additions and 35 deletions
|
|
@ -330,6 +330,517 @@ class HalkyonAppLocalizationsEn extends HalkyonAppLocalizations {
|
|||
|
||||
@override
|
||||
String get syncMismatchMore => 'More';
|
||||
|
||||
@override
|
||||
String get startSignIn => 'Sign in';
|
||||
|
||||
@override
|
||||
String get startCreateAccount => 'Create an account';
|
||||
|
||||
@override
|
||||
String startGitHubEnds(String date) {
|
||||
return 'Sign-in with GitHub ends on $date.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get passwordAddress => 'Email address';
|
||||
|
||||
@override
|
||||
String get passwordPassword => 'Password';
|
||||
|
||||
@override
|
||||
String get passwordNewPassword => 'New password';
|
||||
|
||||
@override
|
||||
String get passwordCurrent => 'Current password';
|
||||
|
||||
@override
|
||||
String get passwordRepeat => 'New password again';
|
||||
|
||||
@override
|
||||
String get passwordsDiffer => 'The two passwords differ.';
|
||||
|
||||
@override
|
||||
String get passwordShow => 'Show password';
|
||||
|
||||
@override
|
||||
String get passwordHide => 'Hide password';
|
||||
|
||||
@override
|
||||
String get passwordContinue => 'Continue';
|
||||
|
||||
@override
|
||||
String get passwordBack => 'Back';
|
||||
|
||||
@override
|
||||
String get passwordDone => 'Done';
|
||||
|
||||
@override
|
||||
String get passwordCopy => 'Copy';
|
||||
|
||||
@override
|
||||
String get passwordCopied => 'Copied.';
|
||||
|
||||
@override
|
||||
String get passwordSave => 'Save as a file';
|
||||
|
||||
@override
|
||||
String get passwordQrCode => 'QR code for your authenticator app';
|
||||
|
||||
@override
|
||||
String get passwordMailCode => 'Code from the email';
|
||||
|
||||
@override
|
||||
String passwordMailCodeSent(String address) {
|
||||
return 'We emailed a code to $address. It\'s valid for 15 minutes.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get passwordResendCode => 'Send a new code';
|
||||
|
||||
@override
|
||||
String get passwordTotpCode => 'Code from your authenticator app';
|
||||
|
||||
@override
|
||||
String get passwordRecoveryCode => 'Recovery code';
|
||||
|
||||
@override
|
||||
String get passwordUseRecoveryCode => 'Use a recovery code instead';
|
||||
|
||||
@override
|
||||
String get passwordUseTotpCode => 'Use your authenticator app instead';
|
||||
|
||||
@override
|
||||
String get passwordUnlocking => 'Unlocking your password on this device…';
|
||||
|
||||
@override
|
||||
String get passwordMakingKeys => 'Making your keys on this device…';
|
||||
|
||||
@override
|
||||
String get passwordSignInTitle => 'Sign in';
|
||||
|
||||
@override
|
||||
String get passwordSignInAction => 'Sign in';
|
||||
|
||||
@override
|
||||
String passwordSignInIntro(String instance) {
|
||||
return 'With the email address and password of your account on $instance.';
|
||||
}
|
||||
|
||||
@override
|
||||
String passwordSecondFactorIntro(String instance) {
|
||||
return 'Enter the code your authenticator app shows for $instance.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get passwordRecoveryCodeIntro =>
|
||||
'Enter one of the recovery codes you kept. Each signs in once.';
|
||||
|
||||
@override
|
||||
String get passwordForgot => 'Forgot your password?';
|
||||
|
||||
@override
|
||||
String get passwordOtherPassword => 'Change the address or password';
|
||||
|
||||
@override
|
||||
String get passwordLostAuthenticator => 'Lost your authenticator?';
|
||||
|
||||
@override
|
||||
String get passwordLostAuthenticatorTitle => 'Lost authenticator';
|
||||
|
||||
@override
|
||||
String get passwordLostAuthenticatorIntro =>
|
||||
'With a recovery code you sign in as usual. Without one, ask to reset your authenticators: it takes effect 72 hours later, and signing in with a code before then stops it.';
|
||||
|
||||
@override
|
||||
String get passwordResetWithMail => 'Reset with a code by email';
|
||||
|
||||
@override
|
||||
String passwordResetWithMailHint(String address) {
|
||||
return 'We email a code to $address.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get passwordResetWithPhrase => 'Reset with your recovery phrase';
|
||||
|
||||
@override
|
||||
String get passwordResetWithPhraseHint => 'The 24 words you wrote down.';
|
||||
|
||||
@override
|
||||
String get passwordSignInWithoutCode => 'Sign in without a code';
|
||||
|
||||
@override
|
||||
String get passwordSignInWithoutCodeHint =>
|
||||
'Once a reset you asked for has taken effect.';
|
||||
|
||||
@override
|
||||
String get passwordResetAction => 'Reset';
|
||||
|
||||
@override
|
||||
String passwordAuthenticatorResetAt(String date) {
|
||||
return 'Your authenticators are reset on $date. We emailed you: if it wasn\'t you, sign in with a code before then to stop it. After that, sign in with your password alone and set up a new authenticator.';
|
||||
}
|
||||
|
||||
@override
|
||||
String passwordUnfinishedIntro(String address) {
|
||||
return 'Your account was never finished. We emailed a code to $address: with it, you sign in and set up an authenticator.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get passwordPhraseField => 'Recovery phrase';
|
||||
|
||||
@override
|
||||
String get passwordPhraseFieldHint =>
|
||||
'The 24 words, with spaces between them';
|
||||
|
||||
@override
|
||||
String get passwordPhraseFieldIntro =>
|
||||
'Enter your recovery phrase: it proves the account is yours.';
|
||||
|
||||
@override
|
||||
String get passwordAuthenticatorTitle => 'Set up an authenticator';
|
||||
|
||||
@override
|
||||
String get passwordAuthenticatorIntro =>
|
||||
'Every sign-in takes a code from an authenticator app, such as Aegis, 2FAS or Proton Authenticator. Scan this code with it, or type in the key.';
|
||||
|
||||
@override
|
||||
String get passwordAuthenticatorKey => 'Key';
|
||||
|
||||
@override
|
||||
String get passwordAuthenticatorOpen => 'Open in your authenticator app';
|
||||
|
||||
@override
|
||||
String get passwordAuthenticatorNoApp =>
|
||||
'No app on this device opens it. Scan the code or type in the key.';
|
||||
|
||||
@override
|
||||
String get passwordAuthenticatorCodeIntro => 'Then enter the code it shows.';
|
||||
|
||||
@override
|
||||
String get passwordExistingCode =>
|
||||
'Code from an authenticator you added before';
|
||||
|
||||
@override
|
||||
String get passwordRecoveryCodesTitle => 'Recovery codes';
|
||||
|
||||
@override
|
||||
String get passwordRecoveryCodesIntro =>
|
||||
'Each signs in once in place of a code from your authenticator, should you lose it. Keep them where only you find them: they\'re shown only now.';
|
||||
|
||||
@override
|
||||
String get passwordCodesKept => 'I\'ve kept them';
|
||||
|
||||
@override
|
||||
String get passwordPhraseTitle => 'Your recovery phrase';
|
||||
|
||||
@override
|
||||
String get passwordPhraseIntro =>
|
||||
'These 24 words open your account\'s keys should you forget your password: without them, what\'s encrypted is lost. Write them down in order, and keep them where only you find them. They\'re shown only now.';
|
||||
|
||||
@override
|
||||
String get passwordPhraseWrittenDown => 'I\'ve written it down';
|
||||
|
||||
@override
|
||||
String get passwordPhraseCheckTitle => 'Check your recovery phrase';
|
||||
|
||||
@override
|
||||
String get passwordPhraseCheckIntro =>
|
||||
'Type the words asked for, from what you wrote down.';
|
||||
|
||||
@override
|
||||
String passwordPhraseWord(int number) {
|
||||
return 'Word $number';
|
||||
}
|
||||
|
||||
@override
|
||||
String get passwordPhraseWrong =>
|
||||
'These aren\'t the words. Look again at what you wrote down.';
|
||||
|
||||
@override
|
||||
String get passwordPhraseShowAgain => 'Show the phrase again';
|
||||
|
||||
@override
|
||||
String get passwordCreateTitle => 'Create an account';
|
||||
|
||||
@override
|
||||
String passwordCreateIntro(String instance) {
|
||||
return 'On $instance, with your email address, a password, and an authenticator app for the code every sign-in takes.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get passwordSetUpTitle => 'Set up a password';
|
||||
|
||||
@override
|
||||
String get passwordSetUpIntro =>
|
||||
'Sign in with your email address, a password and an authenticator app, on this device and others. Everything you have stays.';
|
||||
|
||||
@override
|
||||
String get passwordChooseIntro =>
|
||||
'Choose a password. It never leaves this device: the instance can\'t read it, and can\'t reset it without what it protects being lost.';
|
||||
|
||||
@override
|
||||
String get passwordFinishResetTitle => 'Finish the reset';
|
||||
|
||||
@override
|
||||
String get passwordFinishResetIntro =>
|
||||
'72 hours after you asked to reset your account, finish it here with a new code by email.';
|
||||
|
||||
@override
|
||||
String get passwordFinishResetHavePhrase => 'I have my recovery phrase';
|
||||
|
||||
@override
|
||||
String get passwordFinishResetKeepsKeys =>
|
||||
'Your account keeps its keys: nothing encrypted is lost.';
|
||||
|
||||
@override
|
||||
String get passwordFinishResetNewKeys =>
|
||||
'Without your recovery phrase, your account gets new keys: what was encrypted with the old ones is lost.';
|
||||
|
||||
@override
|
||||
String get passwordRecoveryTitle => 'Get back into your account';
|
||||
|
||||
@override
|
||||
String get passwordRecoveryWithPhrase => 'I have my recovery phrase';
|
||||
|
||||
@override
|
||||
String get passwordRecoveryWithPhraseHint =>
|
||||
'Set a new password at once. Everything stays.';
|
||||
|
||||
@override
|
||||
String get passwordRecoveryWithPhraseIntro =>
|
||||
'Your recovery phrase opens your account\'s keys, which your new password then protects. A code from your authenticator, or a recovery code, confirms it.';
|
||||
|
||||
@override
|
||||
String get passwordRecoveryNoCodeHint =>
|
||||
'Lost your authenticator as well? Reset your account 72 hours later with your recovery phrase: it keeps everything.';
|
||||
|
||||
@override
|
||||
String get passwordRecoveryWithoutPhrase => 'Reset my account';
|
||||
|
||||
@override
|
||||
String get passwordRecoveryWithoutPhraseHint =>
|
||||
'Takes effect 72 hours later. Without your recovery phrase, what\'s encrypted is lost.';
|
||||
|
||||
@override
|
||||
String get passwordRecoveryStartIntro =>
|
||||
'We email a code to your address. Your account can then be reset 72 hours later; signing in with your password before then stops it.';
|
||||
|
||||
@override
|
||||
String get passwordRecoveryFinish => 'Finish a reset';
|
||||
|
||||
@override
|
||||
String get passwordRecoveryFinishHint => '72 hours after you asked for it.';
|
||||
|
||||
@override
|
||||
String passwordResetStarted(String date) {
|
||||
return 'Your account can be reset from $date. We emailed you: if it wasn\'t you, sign in with your password before then to stop it.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get passwordSectionTitle => 'Password and authenticator';
|
||||
|
||||
@override
|
||||
String get passwordSectionFailed =>
|
||||
'Couldn\'t read how your account signs in.';
|
||||
|
||||
@override
|
||||
String passwordSectionGitHubEnds(String date) {
|
||||
return 'Sign-in with GitHub ends on $date. Set up a password before then to keep your account.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get passwordSectionSetUp => 'Set up a password';
|
||||
|
||||
@override
|
||||
String get passwordSectionSetUpHint =>
|
||||
'Sign in with your email address, a password and an authenticator app.';
|
||||
|
||||
@override
|
||||
String get passwordSectionUnfinished =>
|
||||
'Finish setting up: add an authenticator';
|
||||
|
||||
@override
|
||||
String get passwordSectionUnfinishedHint =>
|
||||
'Your password signs in once an authenticator is set up.';
|
||||
|
||||
@override
|
||||
String get passwordSectionChange => 'Change password';
|
||||
|
||||
@override
|
||||
String get passwordSectionKeysMissing =>
|
||||
'Sign in on this device with your password to change it.';
|
||||
|
||||
@override
|
||||
String get passwordSectionNotConfirmed => 'Not set up yet';
|
||||
|
||||
@override
|
||||
String passwordSectionRemove(String name) {
|
||||
return 'Remove $name?';
|
||||
}
|
||||
|
||||
@override
|
||||
String get passwordSectionRemoveIntro =>
|
||||
'Enter a code from one of your authenticators to confirm.';
|
||||
|
||||
@override
|
||||
String get passwordSectionRemoveAction => 'Remove';
|
||||
|
||||
@override
|
||||
String get passwordSectionAddAuthenticator => 'Add an authenticator';
|
||||
|
||||
@override
|
||||
String get passwordSectionRecoveryCodes => 'New recovery codes';
|
||||
|
||||
@override
|
||||
String get passwordSectionRecoveryCodesHint => 'The old ones stop working.';
|
||||
|
||||
@override
|
||||
String get passwordSectionNewPhrase => 'New recovery phrase';
|
||||
|
||||
@override
|
||||
String get passwordSectionPhraseHint => 'Your old phrase stops working.';
|
||||
|
||||
@override
|
||||
String get passwordChangeTitle => 'Change password';
|
||||
|
||||
@override
|
||||
String get passwordChangeIntro =>
|
||||
'Your other devices are signed out. Your keys stay: nothing is lost.';
|
||||
|
||||
@override
|
||||
String get passwordChangeAction => 'Change password';
|
||||
|
||||
@override
|
||||
String get passwordChanged =>
|
||||
'Password changed. Your other devices are signed out.';
|
||||
|
||||
@override
|
||||
String get passwordNewCodesIntro =>
|
||||
'Enter a code from your authenticator app. Your old recovery codes stop working.';
|
||||
|
||||
@override
|
||||
String get passwordNewPhraseIntro =>
|
||||
'Your new recovery phrase. Write the 24 words down in order: once you confirm it, your old phrase opens nothing any more.';
|
||||
|
||||
@override
|
||||
String get passwordPhraseKept =>
|
||||
'New recovery phrase set. The old one no longer works.';
|
||||
|
||||
@override
|
||||
String get passwordWrongCredentials =>
|
||||
'The address, the password or the code isn\'t right.';
|
||||
|
||||
@override
|
||||
String get passwordTooManyAttempts =>
|
||||
'Too many attempts. Try again in an hour.';
|
||||
|
||||
@override
|
||||
String get passwordSetupIncomplete =>
|
||||
'Your account was never finished: set up an authenticator.';
|
||||
|
||||
@override
|
||||
String passwordNotDue(String date) {
|
||||
return 'This takes effect on $date.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get passwordNotDueYet => 'This hasn\'t taken effect yet.';
|
||||
|
||||
@override
|
||||
String get passwordNoReset => 'No reset waits for this address.';
|
||||
|
||||
@override
|
||||
String get passwordExists =>
|
||||
'This address\'s account has a password already. Sign in with it, or get back in if you\'ve forgotten it.';
|
||||
|
||||
@override
|
||||
String get passwordAddressTaken => 'This address belongs to another account.';
|
||||
|
||||
@override
|
||||
String get passwordNoAddress => 'Your account has no email address yet.';
|
||||
|
||||
@override
|
||||
String get passwordNoPassword => 'This account has no password.';
|
||||
|
||||
@override
|
||||
String get passwordWrongCode =>
|
||||
'This code isn\'t right. Enter the one your app shows now.';
|
||||
|
||||
@override
|
||||
String get passwordWrongExistingCode =>
|
||||
'The code from your other authenticator isn\'t right.';
|
||||
|
||||
@override
|
||||
String get passwordLastAuthenticator =>
|
||||
'Your last authenticator can\'t go: every sign-in needs one.';
|
||||
|
||||
@override
|
||||
String get passwordTooManyAuthenticators =>
|
||||
'Your account has as many authenticators as it may. Remove one first.';
|
||||
|
||||
@override
|
||||
String get passwordUnknownAuthenticator => 'This authenticator is gone.';
|
||||
|
||||
@override
|
||||
String get passwordInvalidSetup =>
|
||||
'The instance didn\'t accept what this device made. Update the app, or try again.';
|
||||
|
||||
@override
|
||||
String get passwordChallengeExpired => 'That took too long. Try again.';
|
||||
|
||||
@override
|
||||
String get passwordUnavailable => 'This instance offers no password sign-in.';
|
||||
|
||||
@override
|
||||
String get passwordNotAnAddress => 'This isn\'t an email address.';
|
||||
|
||||
@override
|
||||
String get passwordTooManyCodes =>
|
||||
'Too many codes asked for. Try again in an hour.';
|
||||
|
||||
@override
|
||||
String get passwordWrongMailCode => 'This code isn\'t right.';
|
||||
|
||||
@override
|
||||
String get passwordTooManyMailTries =>
|
||||
'Too many wrong codes. Ask for a new one.';
|
||||
|
||||
@override
|
||||
String get passwordMailCodeExpired =>
|
||||
'This code is no longer valid. Ask for a new one.';
|
||||
|
||||
@override
|
||||
String get passwordMailFailed =>
|
||||
'The instance couldn\'t send the email. Try again later.';
|
||||
|
||||
@override
|
||||
String get passwordOtherAddressLinked =>
|
||||
'Your account has another email address.';
|
||||
|
||||
@override
|
||||
String get passwordNotAPhrase =>
|
||||
'This isn\'t a recovery phrase: check every word and their number.';
|
||||
|
||||
@override
|
||||
String get passwordPhraseDoesNotFit =>
|
||||
'The address or the recovery phrase isn\'t right.';
|
||||
|
||||
@override
|
||||
String get passwordUnusable =>
|
||||
'This password can\'t be used: it\'s empty, or holds characters that aren\'t text.';
|
||||
|
||||
@override
|
||||
String get passwordInstanceUnproven =>
|
||||
'The instance couldn\'t prove it knows your password, so this device trusts nothing it says. Try again; if it happens again, the instance isn\'t the one it claims to be.';
|
||||
|
||||
@override
|
||||
String get passwordKeysMissing =>
|
||||
'Your account\'s keys aren\'t on this device. Sign out and in again with your password here.';
|
||||
|
||||
@override
|
||||
String get passwordFailed =>
|
||||
'Couldn\'t reach the instance. Check the connection and try again.';
|
||||
}
|
||||
|
||||
/// The translations for English, as used in the United Kingdom (`en_GB`).
|
||||
|
|
|
|||
Loading…
Reference in a new issue