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
|
|
|
import 'package:halkyon_base_server/base.dart';
|
|
|
|
|
import 'package:serverpod/serverpod.dart';
|
|
|
|
|
import 'package:serverpod_auth_idp_server/core.dart';
|
|
|
|
|
|
|
|
|
|
import '../test_tools/serverpod_test_tools.dart';
|
|
|
|
|
|
|
|
|
|
/// The app the tests' host is.
|
|
|
|
|
const testAppName = 'Halkyon Test';
|
|
|
|
|
|
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
|
|
|
/// The id of the app the tests' host serves.
|
|
|
|
|
const testApp = 'test';
|
|
|
|
|
|
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
|
|
|
/// Where the tests' host says its project publishes its source.
|
|
|
|
|
const testUpstreamSourceCodeUrl = 'https://code.example.org/halkyon_test';
|
|
|
|
|
|
|
|
|
|
/// The oldest app build the tests' host works with.
|
|
|
|
|
const testMinimumAppBuild = 7;
|
|
|
|
|
|
|
|
|
|
/// Sets the module up on the pod [sessionBuilder]'s sessions run on, as a
|
|
|
|
|
/// host does before its pod starts, with [deletePersonData] as its
|
|
|
|
|
/// deletions, and authentication exactly as hosts run it
|
|
|
|
|
/// (`auth_config.dart`). Not on `Serverpod.instance`: that is the pod made
|
|
|
|
|
/// last, and a test file with several `withServerpod` groups has a pod each.
|
|
|
|
|
void useBase(
|
|
|
|
|
TestSessionBuilder sessionBuilder, {
|
|
|
|
|
List<PersonDataDeletion> deletePersonData = const [],
|
|
|
|
|
}) {
|
|
|
|
|
final pod = sessionBuilder.build().serverpod;
|
|
|
|
|
HalkyonBase.configure(
|
|
|
|
|
pod,
|
|
|
|
|
HalkyonBase(
|
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
|
|
|
app: testApp,
|
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
|
|
|
appName: testAppName,
|
|
|
|
|
upstreamSourceCodeUrl: testUpstreamSourceCodeUrl,
|
|
|
|
|
minimumAppBuild: testMinimumAppBuild,
|
|
|
|
|
deletePersonData: deletePersonData,
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
AuthServices.set(
|
|
|
|
|
tokenManagerBuilders: tokenManagerBuilders,
|
|
|
|
|
identityProviderBuilders: identityProviderBuilders(pod),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Signs [person] in on a new device and returns the new session's id.
|
|
|
|
|
Future<UuidValue> signInNewDevice(
|
|
|
|
|
Session session,
|
|
|
|
|
UuidValue person, {
|
|
|
|
|
Set<Scope> scopes = const {},
|
|
|
|
|
}) async {
|
|
|
|
|
Future<Set<UuidValue>> sessionIds() async => {
|
|
|
|
|
for (final serverSideSession in await ServerSideSession.db.find(
|
|
|
|
|
session,
|
|
|
|
|
where: (t) => t.authUserId.equals(person),
|
|
|
|
|
))
|
|
|
|
|
serverSideSession.id!,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
final before = await sessionIds();
|
|
|
|
|
await AuthServices.instance.tokenManager.createToken(
|
|
|
|
|
session,
|
|
|
|
|
authUserId: person,
|
|
|
|
|
method: 'test',
|
|
|
|
|
scopes: scopes,
|
|
|
|
|
);
|
|
|
|
|
return (await sessionIds()).difference(before).single;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// A person as an app's first launch creates them: anonymous.
|
|
|
|
|
Future<UuidValue> newAnonymousPerson(
|
|
|
|
|
Session session, {
|
|
|
|
|
DateTime? createdAt,
|
|
|
|
|
}) async {
|
|
|
|
|
final person = (await AuthServices.instance.authUsers.create(session)).id;
|
|
|
|
|
await AnonymousAccount.db.insertRow(
|
|
|
|
|
session,
|
|
|
|
|
AnonymousAccount(authUserId: person, createdAt: createdAt),
|
|
|
|
|
);
|
|
|
|
|
return person;
|
|
|
|
|
}
|