import 'dart:typed_data'; import 'package:halkyon_base_server/src/generated/protocol.dart'; import 'package:halkyon_base_server/src/halkyon_base.dart'; import 'package:halkyon_base_server/src/register/accounts_mode.dart'; import 'package:halkyon_base_server/src/register/local_register.dart'; import 'package:halkyon_base_server/src/register/remote_register.dart'; import 'package:halkyon_base_server/src/register/sessions_ended.dart'; import 'package:halkyon_oidc_server/oidc.dart'; import 'package:serverpod/serverpod.dart'; /// The instance's register of accounts (ADR 0016): what one person is across /// every service of the instance, kept by the service its operator names. /// This server asks it here for everything the register keeps, so the same /// call works whether this server holds the register itself /// ([LocalRegister]) or is one of its members. /// /// A person is named by their account ID, which is their `AuthUser`'s ID in /// every service of the instance. abstract interface class AccountRegister { /// The register [pod] asks: its own database while it holds the register /// (also unconfigured, as tests of a host's own code run), else the /// register's server ([RemoteRegister]), which needs the instance's shared /// secret. static AccountRegister of(Serverpod pod) => _ofPod[pod] ?? switch (HalkyonBase.maybeOf(pod)?.accounts) { MemberOfRegister(:final register) => RemoteRegister( register, secret: instanceSecretOf(pod) ?? (throw StateError( 'A member of an instance needs its shared secret: set ' 'instanceSecret in the passwords (INSTANCE_SECRET).', )), ), HoldsRegister() || null => const LocalRegister(), }; /// Puts [register] in place of the one [pod] asks, or with null the one its /// configuration names back: tests do. static void use(Serverpod pod, AccountRegister? register) => _ofPod[pod] = register; static final _ofPod = Expando('Account register'); /// [person]'s public profile, as apps see it; null for none. Future profileOf(Session session, UuidValue person); /// The public profiles of those of [people] who have one, by person. Future> profilesOf( Session session, Set people, ); /// The public profile whose handle is [handle], whatever its case; null for /// none. Future profileByHandle(Session session, String handle); /// The person whose handle is [handle], whatever its case; null for none. /// For a host's own code (who is blocked, who wrote), never for apps: /// profiles never say whose they are. Future personWithHandle(Session session, String handle); /// Saves [person]'s public profile, making it if they have none. Throws /// `ProfileRefusedException` when it can't be saved (see /// `ProfileRefusal`): a handle someone else has, whatever its case, or a /// new handle once theirs is fixed. Future saveProfile( Session session, UuidValue person, { required String handle, required String displayName, required String bio, required List links, }); /// Why [handle] couldn't be [person]'s, as saving it would refuse it; null /// when it can. Future checkHandle( Session session, UuidValue person, String handle, ); /// Checks which of [person]'s links' pages link back to their profile's /// page with `rel="me"`, and keeps the answer; at most 10 times an hour. /// Throws `ProfileNotFoundException` while they have no profile. Future checkLinks(Session session, UuidValue person); /// Shows [png] as [person]'s avatar: a PNG of at most 1 MB. Throws /// `ProfileRefusedException` for anything else, and /// `ProfileNotFoundException` while they have no profile. Future setAvatar(Session session, UuidValue person, ByteData png); /// Shows no avatar for [person] any more. Throws /// `ProfileNotFoundException` while they have no profile. Future removeAvatar(Session session, UuidValue person); /// Removes [person]'s public profile, freeing its handle; nothing when they /// have none. Throws `ProfileRefusedException` once their handle is fixed. Future removeProfile(Session session, UuidValue person); /// Fixes [person]'s handle, as others now refer to it (Halkyon Learn: their /// first publish); nothing when it is fixed already or they have no /// profile. Future fixHandle(Session session, UuidValue person); /// Whether people can sign in with an email address: the register mails /// the codes. Future emailSignInOffered(Session session); /// Mails a code for [purpose] to [address] in [language], the codes asked /// for from [network] counted; the request it answers. Refusals as /// `sendSignInCode`'s. Future sendSignInCode( Session session, { required String address, required String language, required EmailCodePurpose purpose, required String network, }); /// Links the GitHub account [gitHubUserId] to [person] (an account of the /// register, under their ID). Refusals as `linkGitHubAccount`'s. Future linkGitHub( Session session, { required UuidValue person, required String gitHubUserId, }); /// The account that signs in with the GitHub account [gitHubUserId]; null /// for none. Future accountWithGitHub(Session session, String gitHubUserId); /// Whether people sign in with SimpleLogin (ADR 0020): the register has /// the instance's SimpleLogin app, and its operator offers it. Future simpleLoginOffered(Session session); /// Begins a SimpleLogin sign-in for the app and platform [marker] names, /// by [caller] if signed in, the sign-ins begun from [network] counted. /// Refusals as `beginSimpleLogin`'s. Future beginSimpleLogin( Session session, { required String marker, UuidValue? caller, required String network, }); /// Ends the SimpleLogin sign-in [attempt] with SimpleLogin's [code], as /// [caller] (null signed out) began it: how it ended, the account signed /// in to, which the member issues its own session for. Refusals as /// `completeSimpleLogin`'s. Future completeSimpleLogin( Session session, { required UuidValue attempt, required String code, UuidValue? caller, }); /// Whether someone new whose SimpleLogin identity waits under [attempt] /// could take [handle], the checks from [network] counted; null where they /// could. Refusals as `checkSimpleLoginHandle`'s. Future checkSimpleLoginHandle( Session session, { required UuidValue attempt, required String handle, required String network, }); /// Signs up someone new whose SimpleLogin identity waits under [attempt], /// with [handle] and the keys [setup] describes: the new account's ID, /// which the member issues its own session for. Refusals as /// `signUpWithSimpleLogin`'s. Future signUpWithSimpleLogin( Session session, { required UuidValue attempt, required String handle, required KeysSetup setup, }); /// The first step of mending the SimpleLogin link of the account with /// [handle] for someone whose identity waits under [attempt], the begins /// from [network] counted. Refusals as `beginSimpleLoginMend`'s. Future beginSimpleLoginMend( Session session, { required UuidValue attempt, required String handle, required String network, }); /// Mends the SimpleLogin link of the account whose recovery phrase /// [proof] proved, with the identity waiting under [attempt]: the account /// signed in to, which the member issues its own session for, after /// [SimpleLoginProof.endedBefore]. Refusals as `mendSimpleLogin`'s. Future mendSimpleLogin( Session session, { required UuidValue attempt, required KeysProof proof, }); /// The first step of [person] proving their password with its proof key /// (an account that signs in with SimpleLogin). Refusals as /// `beginPasswordKeyProof`'s. Future beginPasswordKeyProof( Session session, UuidValue person, ); /// [person]'s keys for a device, once [proof] proved their password for /// it. Refusals as `unlockKeys`'s. Future unlockKeys( Session session, UuidValue person, KeysProof proof, ); /// Gives [person], an account without keys that SimpleLogin was added to, /// the keys [setup] describes and [handle] where it has none, with their /// SimpleLogin sign-in under [attempt]. Refusals as `setFirstKeys`'. Future setFirstKeys( Session session, { required UuidValue person, required UuidValue attempt, required KeysSetup setup, String? handle, }); /// The first step of [person] proving their recovery phrase, signed in, /// for a lost password (an account that signs in with SimpleLogin). /// Refusals as `beginPhraseKeyProof`'s. Future beginPhraseKeyProof( Session session, UuidValue person, ); /// Changes [person]'s password for their keys to [password], with /// [proof]: every other session ends but [keepSession], in every service. /// Refusals as `changeKeysPassword`'s. Future changeKeysPassword( Session session, { required UuidValue person, required KeysOwnerProof proof, required KeysPassword password, UuidValue? keepSession, }); /// Sets [person]'s new [password] for their keys with the recovery /// phrase's [proof], as [changeKeysPassword] does. Refusals as /// `resetKeysPassword`'s. Future resetKeysPassword( Session session, { required UuidValue person, required KeysOwnerProof proof, required KeysPassword password, UuidValue? keepSession, }); /// Replaces [person]'s recovery phrase's wrapping and proof key, with /// [proof]. Refusals as `setKeysRecovery`'. Future setKeysRecovery( Session session, { required UuidValue person, required KeysOwnerProof proof, required WrappedKeys recoveryKeys, required ByteData recoveryProofKey, }); /// How [person] signs in: with GitHub, an email address, a password and /// whether an authenticator is confirmed; and their warning that a session /// was ended for its wrong proofs, if any. Future signInWaysOf(Session session, UuidValue person); /// Warns [person] that a session of theirs was ended for ten wrong proofs /// in a row (ADR 0017, the fourth reading), as of now: a member tells it /// of its own sessions. Future noteSessionEndedForProofs(Session session, UuidValue person); /// Dismisses [person]'s warning that a session was ended for its proofs. Future dismissSessionEndedWarning( Session session, UuidValue person, { required DateTime? sessionMadeAt, }); /// Whether people sign in with a password and TOTP (ADR 0017): the /// register holds the keys it needs. Future passwordSignInOffered(Session session); /// Gives [person] the password [setup] describes, with the address [code] /// proves for [request] (their own where they have one), and for one who /// signs in with GitHub alone a GitHub sign-in of theirs made recently /// ([freshGitHubSignIn], of the session asking); someone with neither /// only while anonymous where the session lives ([anonymousHere]). /// Replacing a setup never finished ends every other session of the /// account but [keepSession]. Refusals as `setPassword`'s. Future setPassword( Session session, { required UuidValue person, required PasswordSetup setup, UuidValue? request, String? code, bool freshGitHubSignIn = false, bool anonymousHere = false, UuidValue? keepSession, }); /// A password sign-in's first step for [address], the sign-ins begun from /// [network] counted. Refusals as `beginPasswordSignIn`'s. Future beginPasswordSignIn( Session session, { required String address, required String network, }); /// A password sign-in's second step: the account it proved, for the /// caller's session; a [code] mailed for [request] in place of the second /// factor of a setup never finished. Refusals as /// `completePasswordSignIn`'s. Future completePasswordSignIn( Session session, { required UuidValue challenge, required ByteData clientValue, required ByteData clientProof, String? totpCode, String? recoveryCode, UuidValue? request, String? code, required String instanceUrl, required String address, required String language, }); /// Signs up with the address [code] proves for [request] and the password /// [setup] describes: the account, new or one with the address and no /// password, for the caller's session. Refusals as `signUpWithCode`'s. Future signUpWithCode( Session session, { required UuidValue request, required String code, required PasswordSetup setup, }); /// A new authenticator of [person]. Refusals as `enrolAuthenticator`'s. Future enrolAuthenticator( Session session, { required UuidValue person, required String name, required String label, }); /// Confirms [person]'s [authenticator], with the owner's [proof] where /// the account has one confirmed already; the first one's recovery codes. /// Refusals as `confirmAuthenticator`'s. Future> confirmAuthenticator( Session session, { required UuidValue person, required UuidValue authenticator, required String code, OwnerProof? proof, }); /// [person]'s authenticators, the oldest first. Future> authenticatorsOf( Session session, UuidValue person, ); /// Removes [person]'s [authenticator]. Refusals as /// `removeAuthenticator`'s. Future removeAuthenticator( Session session, { required UuidValue person, required UuidValue authenticator, OwnerProof? proof, }); /// New recovery codes for [person], with the owner's [proof]. Refusals as /// `newRecoveryCodes`'. Future> newRecoveryCodes( Session session, { required UuidValue person, required OwnerProof proof, }); /// Asks for the reset of a lost authenticator. Refusals as /// `resetAuthenticator`'s. Future resetAuthenticator( Session session, { required UuidValue challenge, required ByteData clientValue, required ByteData clientProof, required String address, required String language, UuidValue? request, String? code, UuidValue? keyChallenge, ByteData? signature, required String instanceUrl, }); /// The first step of proving the recovery phrase for [address], the /// proofs begun from [network] counted. Refusals as `beginKeyProof`'s. Future beginKeyProof( Session session, { required String address, required String network, }); /// Sets a new password with the recovery phrase's proof; the account, for /// the caller's session made after its sessions ended. Refusals as /// `resetPasswordWithPhrase`'s. Future resetPasswordWithPhrase( Session session, { required UuidValue challenge, required ByteData signature, required NewPassword password, String? totpCode, String? recoveryCode, required String instanceUrl, required String address, required String language, }); /// Asks for the reset of a whole account. Refusals as /// `startAccountReset`'s. Future startAccountReset( Session session, { required String address, required String language, required UuidValue request, required String code, }); /// Carries out the reset of a whole account; the account, for the /// caller's session made after its sessions ended. Refusals as /// `completeAccountReset`'s. Future completeAccountReset( Session session, { required String address, required UuidValue request, required String code, required PasswordSetup setup, }); /// The first step of [person] proving their password to change it. /// Refusals as `beginPasswordChange`'s. Future beginPasswordChange( Session session, { required UuidValue person, }); /// Changes [person]'s password, ending their sessions everywhere but /// [keepSession]. Refusals as `changePassword`'s. Future changePassword( Session session, { required UuidValue person, required OwnerProof proof, required NewPassword password, UuidValue? keepSession, }); /// Calls off every reset of [person]'s account waiting, with the owner's /// [proof] (ADR 0017, the full reading's H1). Refusals as /// `callOffResets`'. Future callOffResets( Session session, { required UuidValue person, required OwnerProof proof, }); /// Replaces the wrapping of [person]'s keys under their recovery phrase /// and the phrase's proof key, with the owner's [proof]. Refusals as /// `setRecoveryKeys`'. Future setRecoveryKeys( Session session, { required UuidValue person, required OwnerProof proof, required WrappedKeys recoveryKeys, required ByteData recoveryProofKey, }); /// Deletes the account [person] with everything the register's server has /// of them, and has every member of the instance delete theirs: one with a /// password only with [proof], one that signs in with SimpleLogin only /// with [keysProof] (`requireOwnerToDelete`). Future deleteAccount( Session session, UuidValue person, { DeletionProof? proof, KeysOwnerProof? keysProof, }); /// Those of [people] that are accounts of the register. Future> knownAccounts( Session session, Set people, ); }