halkyon_base: a Serverpod module for sign-in, accounts and instance basics
What every Halkyon server has, taken from Halkyon Learn's: anonymous,
GitHub and email-code sign-in with their endpoints, an account's ways
in and its deletion, the pruning of abandoned anonymous people, of
sign-up attempts and of expired codes, and the instance's basics (the
sign-in ways offered, the source link, the minimum app build, where
people's data and its backups are). Endpoints sit under the nickname
`base`. A host configures it in one place before its pod starts: its
name for the mails, its source link, its minimum build, and hooks that
delete its own data with a person.
The tables keep Learn's names, columns and indexes, so a host moving
onto the module migrates nothing but version rows. Server package under
the SSPL, client under the AGPL, as Learn's are.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-19 14:28:09 +02:00
|
|
|
/// halkyon_base for a host's own code: its configuration
|
|
|
|
|
/// (`HalkyonBase.configure`), how people authenticate, the account's
|
|
|
|
|
/// deletion and the pruning, email sign-in's helpers, the instance's
|
refactor(profile): handles and profiles move into halkyon_base
People's public profiles are the instance's register's (ADR 0016), so
the module now holds them: the public_profile table (unchanged, so
Learn's migration records versions only; devices get the table empty,
as they hold every module's tables), the profile and profiles
endpoints under `base`, the handle rules, the link checks and the page
at /@handle, whose foot names the app as the host's configuration says
(profilePageNote). Deleting a person deletes their profile in the
module's core.
Hosts read profiles through the module (profileOf, profilesOf,
personWithHandle, fixHandle; saveProfile for their tests), behind an
AccountRegister that is this server's own database for now. Learn's
feedback, discussions and publishing ask it; publishing fixes the
handle just before it writes the release, outside its transaction.
Explore still joins the table in its search, so Learn holds the
register wherever it runs. Learn's app calls the module's endpoints;
a profile not found is ProfileNotFoundException. Todo's database gets
the table too (it was never deployed).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-19 22:20:27 +02:00
|
|
|
/// basics, people's public profiles and their pages, the instance's register
|
feat(crypto): TOTP and recovery codes
ADR 0017's second factor, in halkyon_base_server (base.dart):
- RFC 6238 with HMAC-SHA1, 6 digits, 30-second steps, one step either
way. checkTotp takes the account's last accepted step as a required
argument and refuses a code of that step or before, so no code is
accepted twice; every step of the window is computed and compared.
- 160-bit secrets in RFC 4648 Base32 (strict decoding); the otpauth
URI with issuer and account escaped.
- Ten recovery codes of eight Crockford characters (short_code.dart),
kept as SHA-256 hashes, each used once, every hash compared in
constant time.
- Tests: RFC 6238 Appendix B (8 digits, SHA-1, SHA-256, SHA-512), the
window, replay, RFC 4648's Base32 vectors, the recovery codes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-20 12:01:52 +02:00
|
|
|
/// (ADR 0016), SRP's register side, TOTP and recovery codes (ADR 0017), and
|
|
|
|
|
/// the module's models.
|
halkyon_base: a Serverpod module for sign-in, accounts and instance basics
What every Halkyon server has, taken from Halkyon Learn's: anonymous,
GitHub and email-code sign-in with their endpoints, an account's ways
in and its deletion, the pruning of abandoned anonymous people, of
sign-up attempts and of expired codes, and the instance's basics (the
sign-in ways offered, the source link, the minimum app build, where
people's data and its backups are). Endpoints sit under the nickname
`base`. A host configures it in one place before its pod starts: its
name for the mails, its source link, its minimum build, and hooks that
delete its own data with a person.
The tables keep Learn's names, columns and indexes, so a host moving
onto the module migrates nothing but version rows. Server package under
the SSPL, client under the AGPL, as Learn's are.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-19 14:28:09 +02:00
|
|
|
library;
|
|
|
|
|
|
|
|
|
|
export 'src/auth/account.dart';
|
|
|
|
|
export 'src/auth/auth_config.dart';
|
|
|
|
|
export 'src/auth/code_mail.dart';
|
|
|
|
|
export 'src/auth/email.dart';
|
|
|
|
|
export 'src/auth/github.dart';
|
|
|
|
|
export 'src/auth/identified.dart';
|
|
|
|
|
export 'src/auth/scopes.dart';
|
|
|
|
|
export 'src/common/short_code.dart';
|
|
|
|
|
export 'src/generated/protocol.dart' hide Protocol;
|
|
|
|
|
export 'src/halkyon_base.dart';
|
|
|
|
|
export 'src/instance/data_location.dart';
|
|
|
|
|
export 'src/instance/source_code.dart';
|
refactor(profile): handles and profiles move into halkyon_base
People's public profiles are the instance's register's (ADR 0016), so
the module now holds them: the public_profile table (unchanged, so
Learn's migration records versions only; devices get the table empty,
as they hold every module's tables), the profile and profiles
endpoints under `base`, the handle rules, the link checks and the page
at /@handle, whose foot names the app as the host's configuration says
(profilePageNote). Deleting a person deletes their profile in the
module's core.
Hosts read profiles through the module (profileOf, profilesOf,
personWithHandle, fixHandle; saveProfile for their tests), behind an
AccountRegister that is this server's own database for now. Learn's
feedback, discussions and publishing ask it; publishing fixes the
handle just before it writes the release, outside its transaction.
Explore still joins the table in its search, so Learn holds the
register wherever it runs. Learn's app calls the module's endpoints;
a profile not found is ProfileNotFoundException. Todo's database gets
the table too (it was never deployed).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-19 22:20:27 +02:00
|
|
|
export 'src/profile/link_verification.dart';
|
|
|
|
|
export 'src/profile/profile_page.dart' show addProfilePageRoute, profilePage;
|
|
|
|
|
export 'src/profile/profile_rules.dart'
|
|
|
|
|
show
|
|
|
|
|
accountDomain,
|
|
|
|
|
avatarPath,
|
|
|
|
|
avatarStorage,
|
|
|
|
|
isHandle,
|
|
|
|
|
maxAvatarBytes,
|
|
|
|
|
operatorAccountDomain,
|
|
|
|
|
profilePageUrl,
|
|
|
|
|
reservedHandles;
|
|
|
|
|
export 'src/profile/profiles.dart';
|
|
|
|
|
export 'src/register/account_register.dart';
|
2026-10-19 23:10:08 +02:00
|
|
|
export 'src/register/accounts_mode.dart';
|
|
|
|
|
export 'src/register/deletion_notices.dart'
|
|
|
|
|
show MemberNotifier, deliverDeletionNotices;
|
|
|
|
|
export 'src/register/instance_routes.dart';
|
|
|
|
|
export 'src/register/member_pruning.dart';
|
|
|
|
|
export 'src/register/people.dart' show issueSession;
|
feat(crypto): SRP-6a, the register's half and the device's
ADR 0017's step 1. Generic over group and hash (SrpGroup); production
takes RFC 5054's 3072-bit group with SHA-256. k = H(N | PAD(g)),
u = H(PAD(A) | PAD(B)), K = H(PAD(S)),
M1 = H(H(N) xor H(g) | H(I) | s | PAD(A) | PAD(B) | K),
M2 = H(PAD(A) | M1 | K). A and B must lie in 1..N-1 and u must not be
0; a and b are 256 bits from Random.secure(); proofs are compared in
constant time. x comes from the caller (halkyon_keys makes it); RFC
5054's own x is there for the vectors only.
- The register's half in halkyon_base_server (base.dart: srpChallenge,
srpVerify), the device's written by hand in halkyon_base_client
(srp.dart: SrpClient, srpVerifier). Serverpod's generator cleans and
writes only a client's lib/src/protocol/ (serverpod_cli 4.0.4's
_getDirectoriesRequiringCleaning; a generate leaves srp/ untouched).
- Tests: RFC 5054 Appendix B on both halves; the client against the
register, succeeding and every refusal.
- The module's tests depend on its client, and pub resolves every
member's dev dependencies, so Learn's server image takes the module's
client into its workspace too.
- The format check covers the hand-written half.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-20 12:01:52 +02:00
|
|
|
export 'src/srp/srp_group.dart';
|
|
|
|
|
export 'src/srp/srp_server.dart'
|
|
|
|
|
show
|
|
|
|
|
SrpAccepted,
|
|
|
|
|
SrpChallenge,
|
|
|
|
|
SrpRefusal,
|
|
|
|
|
SrpRefused,
|
|
|
|
|
SrpVerification,
|
|
|
|
|
srpChallenge,
|
|
|
|
|
srpVerify;
|
feat(crypto): TOTP and recovery codes
ADR 0017's second factor, in halkyon_base_server (base.dart):
- RFC 6238 with HMAC-SHA1, 6 digits, 30-second steps, one step either
way. checkTotp takes the account's last accepted step as a required
argument and refuses a code of that step or before, so no code is
accepted twice; every step of the window is computed and compared.
- 160-bit secrets in RFC 4648 Base32 (strict decoding); the otpauth
URI with issuer and account escaped.
- Ten recovery codes of eight Crockford characters (short_code.dart),
kept as SHA-256 hashes, each used once, every hash compared in
constant time.
- Tests: RFC 6238 Appendix B (8 digits, SHA-1, SHA-256, SHA-512), the
window, replay, RFC 4648's Base32 vectors, the recovery codes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-20 12:01:52 +02:00
|
|
|
export 'src/totp/base32.dart';
|
|
|
|
|
export 'src/totp/recovery_codes.dart';
|
|
|
|
|
export 'src/totp/totp.dart';
|