halkyon_app: instances, sign-in and sync plumbing for Halkyon's apps
What every Halkyon app's shell needs, taken from Halkyon Learn's and set
on halkyon_base's client: the instances a device keeps an account on,
the first start's tiles and where an instance keeps people's data, the
account switcher; signing in anonymously, with GitHub or an email code,
switching person and the start screen; the gates that build an app's
dependencies per instance and per person; and the sync service with its
status, Settings card and banner. Texts of its own in English and
German take the app's name; what only the app can say, it passes in.
Fakes for apps' tests are in testing.dart.
An instance's basics now name the app it serves (`app`, set by the
host's configuration), and an app accepts only an instance of its own.
SerialQueue and CoalescingRunner move into halkyon_core.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-19 15:18:49 +02:00
|
|
|
import 'package:halkyon_app/src/auth/model/auth_status.dart';
|
|
|
|
|
import 'package:halkyon_app/src/auth/model/email_sign_in.dart';
|
|
|
|
|
import 'package:halkyon_app/src/auth/model/github_link_outcome.dart';
|
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>
2026-10-20 15:42:45 +02:00
|
|
|
import 'package:halkyon_app/src/auth/model/session_grant.dart';
|
halkyon_app: instances, sign-in and sync plumbing for Halkyon's apps
What every Halkyon app's shell needs, taken from Halkyon Learn's and set
on halkyon_base's client: the instances a device keeps an account on,
the first start's tiles and where an instance keeps people's data, the
account switcher; signing in anonymously, with GitHub or an email code,
switching person and the start screen; the gates that build an app's
dependencies per instance and per person; and the sync service with its
status, Settings card and banner. Texts of its own in English and
German take the app's name; what only the app can say, it passes in.
Fakes for apps' tests are in testing.dart.
An instance's basics now name the app it serves (`app`, set by the
host's configuration), and an app accepts only an instance of its own.
SerialQueue and CoalescingRunner move into halkyon_core.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-19 15:18:49 +02:00
|
|
|
import 'package:halkyon_app/src/auth/model/sign_in_ways.dart';
|
|
|
|
|
|
|
|
|
|
/// The boundary to the server's authentication: who is signed in, and signing
|
|
|
|
|
/// in and out. Tests replace it with a fake.
|
|
|
|
|
abstract interface class AuthGateway() {
|
|
|
|
|
/// Who is signed in right now.
|
|
|
|
|
AuthStatus get status;
|
|
|
|
|
|
|
|
|
|
/// Every later change of [status].
|
|
|
|
|
Stream<AuthStatus> get statusChanges;
|
|
|
|
|
|
|
|
|
|
/// Creates an anonymous account on the server and signs in with it.
|
|
|
|
|
Future<void> signInAnonymously();
|
|
|
|
|
|
|
|
|
|
/// Signs out on this device.
|
|
|
|
|
Future<void> signOut();
|
|
|
|
|
|
|
|
|
|
/// Deletes the signed-in person with everything they have, on the server
|
|
|
|
|
/// and so on all their devices, and forgets the session here.
|
|
|
|
|
Future<void> deleteAccount();
|
|
|
|
|
|
|
|
|
|
/// The client ID of this instance's GitHub OAuth app; null when GitHub
|
|
|
|
|
/// sign-in is off on this instance.
|
|
|
|
|
Future<String?> gitHubClientId();
|
|
|
|
|
|
|
|
|
|
/// Authorizes with GitHub in the browser and links the GitHub account to the
|
|
|
|
|
/// signed-in person: the lossless upgrade from an anonymous account.
|
|
|
|
|
Future<GitHubLinkOutcome> linkGitHub();
|
|
|
|
|
|
|
|
|
|
/// Authorizes with GitHub again and switches this device to the person who
|
|
|
|
|
/// has that GitHub account; false when the person cancelled on GitHub.
|
|
|
|
|
Future<bool> switchToGitHub();
|
|
|
|
|
|
|
|
|
|
/// Whether the app itself waits for GitHub's page in the browser, so that
|
|
|
|
|
/// only the app can cancel it (Linux, Windows). Elsewhere the system's
|
|
|
|
|
/// sign-in screen has its own Cancel.
|
|
|
|
|
bool get canCancelGitHub;
|
|
|
|
|
|
|
|
|
|
/// Stops waiting for GitHub's page in the browser: the [linkGitHub] or
|
|
|
|
|
/// [switchToGitHub] in progress ends as cancelled. Does nothing once
|
|
|
|
|
/// GitHub has answered.
|
|
|
|
|
Future<void> cancelGitHub();
|
|
|
|
|
|
|
|
|
|
/// Whether this instance offers signing in with an email address.
|
|
|
|
|
Future<bool> offersEmailSignIn();
|
|
|
|
|
|
|
|
|
|
/// Mails a code to [address], worded in [language] (`en`, `de`), and
|
|
|
|
|
/// returns the request it answers. Throws [EmailSignInFailed].
|
|
|
|
|
Future<String> sendEmailCode(String address, {required String language});
|
|
|
|
|
|
|
|
|
|
/// Accepts [code] for [request]: the address becomes the signed-in
|
|
|
|
|
/// person's, the lossless upgrade from an anonymous account, unless it is
|
|
|
|
|
/// another person's. Throws [EmailSignInFailed].
|
|
|
|
|
Future<EmailCodeOutcome> verifyEmailCode(String request, String code);
|
|
|
|
|
|
|
|
|
|
/// Switches this device to the person [switchToken] signs in as, and
|
|
|
|
|
/// returns them (their `AuthUser` id). Throws [EmailSignInFailed].
|
|
|
|
|
Future<String> switchToEmail(String switchToken);
|
|
|
|
|
|
|
|
|
|
/// How the signed-in person signs in.
|
|
|
|
|
Future<SignInWays> signInWays();
|
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>
2026-10-20 15:42:45 +02:00
|
|
|
|
|
|
|
|
/// Puts [grant] in place of this device's session: its person is signed
|
|
|
|
|
/// in. An anonymous account left behind is deleted, as nobody could sign
|
|
|
|
|
/// into it again (as switching to GitHub does).
|
|
|
|
|
Future<void> useSession(SessionGrant grant);
|
|
|
|
|
|
|
|
|
|
/// The signed-in person was made identified on the instance (a password
|
|
|
|
|
/// and address set, ADR 0017): this device's copy of their session
|
|
|
|
|
/// follows.
|
|
|
|
|
Future<void> markIdentified();
|
halkyon_app: instances, sign-in and sync plumbing for Halkyon's apps
What every Halkyon app's shell needs, taken from Halkyon Learn's and set
on halkyon_base's client: the instances a device keeps an account on,
the first start's tiles and where an instance keeps people's data, the
account switcher; signing in anonymously, with GitHub or an email code,
switching person and the start screen; the gates that build an app's
dependencies per instance and per person; and the sync service with its
status, Settings card and banner. Texts of its own in English and
German take the app's name; what only the app can say, it passes in.
Fakes for apps' tests are in testing.dart.
An instance's basics now name the app it serves (`app`, set by the
host's configuration), and an app accepts only an instance of its own.
SerialQueue and CoalescingRunner move into halkyon_core.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-19 15:18:49 +02:00
|
|
|
}
|