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>
This commit is contained in:
parent
80fa7b9628
commit
af1de799f4
1 changed files with 17704 additions and 3 deletions
109
packages/halkyon_base_server/README.md
Normal file
109
packages/halkyon_base_server/README.md
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
# 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`: 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, pinned to a signed commit of `halkyon_shared`:
|
||||
|
||||
```yaml
|
||||
# the server's pubspec.yaml
|
||||
halkyon_base_server:
|
||||
git:
|
||||
url: https://code.halkyon.app/project-halkyon/halkyon_shared.git
|
||||
path: packages/halkyon_base_server
|
||||
ref: <signed commit>
|
||||
# the client's pubspec.yaml: the same with halkyon_base_client
|
||||
```
|
||||
|
||||
2. Name it in the server's `config/generator.yaml`:
|
||||
|
||||
```yaml
|
||||
modules:
|
||||
halkyon_base:
|
||||
nickname: base
|
||||
```
|
||||
|
||||
3. Configure it once, before the pod starts, and run its authentication and pruning:
|
||||
|
||||
```dart
|
||||
final pod = Serverpod(args); // the host's generated Serverpod
|
||||
HalkyonBase.configure(
|
||||
pod,
|
||||
HalkyonBase(
|
||||
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`.
|
||||
|
||||
## 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.
|
||||
Loading…
Reference in a new issue