halkyon/packages/halkyon_base_server
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Tizian 「ティツィアーン」 1b62c8098c
docs: one repository for every app and shared package (ADR 0015)
The root AGENTS.md holds what applies to every app and package (how
work is done, Serverpod, checks with Melos and the tools, CI); Learn's
own is apps/learn/AGENTS.md, Todo's a note that it is still the
scaffold. The root README introduces the project, Learn's moves to its
folder; LICENSE.md says which licence covers which directory, Todo's
packages unlicensed until decided; one CONTRIBUTING.md. ADR 0015
records the decision; ADR 0014 and 0003 note what moved.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-22 18:11:04 +02:00
..
config halkyon_base: a Serverpod module for sign-in, accounts and instance basics 2026-10-19 14:29:28 +02:00
lib halkyon_app: data locations as precise as a country or a state 2026-10-19 18:50:26 +02:00
migrations halkyon_base: a Serverpod module for sign-in, accounts and instance basics 2026-10-19 14:29:28 +02:00
test halkyon_app: data locations as precise as a country or a state 2026-10-19 18:50:26 +02:00
.gitignore halkyon_base: a Serverpod module for sign-in, accounts and instance basics 2026-10-19 14:29:28 +02:00
analysis_options.yaml halkyon_base: a Serverpod module for sign-in, accounts and instance basics 2026-10-19 14:29:28 +02:00
CHANGELOG.md halkyon_base: a Serverpod module for sign-in, accounts and instance basics 2026-10-19 14:29:28 +02:00
dart_test.yaml halkyon_base: a Serverpod module for sign-in, accounts and instance basics 2026-10-19 14:29:28 +02:00
LICENSE halkyon_base: a Serverpod module for sign-in, accounts and instance basics 2026-10-19 14:29:28 +02:00
pubspec.yaml halkyon_base: a Serverpod module for sign-in, accounts and instance basics 2026-10-19 14:29:28 +02:00
README.md docs: one repository for every app and shared package (ADR 0015) 2026-10-22 18:11:04 +02:00

halkyon_base

What every Halkyon server has, as a Serverpod module (halkyon_base_server, with its generated client halkyon_base_client): signing in, the account's ways in and its deletion, the pruning of what nobody can return to, and what an instance tells its apps before anyone signs in. It comes from Halkyon Learn's server, whose rules it keeps.

What it provides

Endpoints, under the nickname base in a host's client (client.base.emailCode):

Endpoint Methods Who may call
anonymousIdp login (Serverpod's anonymous sign-in, at most 30 new accounts per IP address and hour) anyone
gitHubUpgrade upgrade: links a GitHub account to the caller, who becomes identified signed in
gitHubSignIn signIn: a session for whoever has that GitHub account without a session
emailCode sendCode, verifyCode: a code by mail; a new address becomes the caller's, another person's gives a switch token signed in
emailSignIn signIn: a session for the person a switch token names without a session
account delete, signInWays signed in
instance basics: the app it serves, GitHub's client ID if any, whether email sign-in is on, the source link, the oldest app build, where data and backups are anyone
  • GitHub keeps only the numeric GitHub ID, in Serverpod's own table; email keeps only HMAC-SHA256 of the address, keyed with emailSignInPepper (email_identity), and its codes as Argon2id hashes (email_code_request). Codes are eight Crockford characters, valid 15 minutes, 3 tries; at most 3 an hour per address and 10 per network.
  • Refusals are GitHubSignInRefusedException and EmailSignInRefusedException, each with a reason.
  • For the host's own code, package:halkyon_base_server/base.dart: the configuration, tokenManagerBuilders and identityProviderBuilders, the identified scope and grantIdentified, deletePerson, the pruning (pruneAbandonedAnonymousPeople, forgetOldSignUpAttempts, forgetExpiredEmailCodes), short codes, sourceCodeUrl, dataLocation, backupLocation, and the models.

Taking it into a host

  1. Depend on both packages by path, within the repository's workspace (an app's packages sit in apps/<app>/):

    # the server's pubspec.yaml
    halkyon_base_server:
      path: ../../../packages/halkyon_base_server
    # the client's pubspec.yaml: the same with halkyon_base_client
    
  2. Name it in the server's config/generator.yaml:

    modules:
      halkyon_base:
        nickname: base
    
  3. Configure it once, before the pod starts, and run its authentication and pruning:

    final pod = Serverpod(args); // the host's generated Serverpod
    HalkyonBase.configure(
      pod,
      HalkyonBase(
        app: 'todo', // its apps accept only instances naming it; never change it
        appName: 'Halkyon Todo', // in sign-in mails
        upstreamSourceCodeUrl: 'https://code.halkyon.app/project-halkyon/halkyon_todo',
        minimumAppBuild: 1, // raise it in the commit that breaks older apps
        deletePersonData: [deleteTodosOf], // the host's own data of a person
      ),
    );
    pod.initializeAuthServices(
      tokenManagerBuilders: tokenManagerBuilders,
      identityProviderBuilders: identityProviderBuilders(pod),
    );
    

    Then schedule the pruning in a future call of the host's own: daily pruneAbandonedAnonymousPeople, hourly forgetOldSignUpAttempts and forgetExpiredEmailCodes.

  4. A host's deletion (PersonDataDeletion) runs inside deletePerson's transaction, after the person's sessions ended and before their identities and the person go, in the order given; it deletes the person's rows in the host's tables, and returns what has to wait until the deletion is committed (files outside the database), or null. Pruning deletes through the same function.

  5. Create the host's migration. A host taking the tables over from its own models (as Halkyon Learn does) finds no change once its copies are gone: Serverpod matches tables by name, and these are identical. create-migration --empty then records the module's version, with no SQL besides.

The passwords it reads: serverSideSessionKeyHashPepper; for GitHub githubClientId and githubClientSecret; for email emailSignInPepper and emailSmtpHost, emailSmtpPort, emailSmtpUser, emailSmtpPassword, emailFrom. Never change either pepper once people sign in: every session or address hash would be lost. The environment: SOURCE_URL, DATA_LOCATION, BACKUP_LOCATION: where the instance keeps people's data and its backups, each EU, a country's code (DE) or a subdivision's (ISO 3166-2, US-CA, DE-BY), the backups also none; where the disks are, not which law governs.

Developing it

serverpod --no-analytics generate here after changing a model or an endpoint, and serverpod --no-analytics create-migration after changing a table. Tests need no Docker: dart test here starts Serverpod's embedded PostgreSQL. They read config/passwords.yaml (git-ignored) with throwaway values under test: for database, serviceSecret, serverSideSessionKeyHashPepper and emailSignInPepper.

Licences

The server package is under the Server Side Public License 1.0, as the Halkyon servers are; the client package under the GNU Affero General Public License 3.0 or later, as the Halkyon apps are. So each combines with its counterpart in a host exactly as the host's own do.