feat(base)!: no anonymous accounts on the server (ADR 0020, D25)
All checks were successful
CI / check (push) Successful in 30m13s
CI / deploy (push) Has been skipped

The server's half of D25: every account has a way in, and the anonymous
accounts left from before go with the daily pruning.

- The module signs nobody in anonymously: `anonymousIdp` and
  `gitHubUpgrade` are gone, and so is linking GitHub at the register.
- A password is set only by someone whose address is theirs (a
  `firstPassword` code) or who signs in with GitHub alone (a fresh
  sign-in and a `signUp` code); an anonymous account is refused
  `wrongCredentials` before any code is used.
- SimpleLogin refuses to link onto a caller without `identified`
  (`wrongCaller`), the member's endpoint too, so nothing turns an
  anonymous account into one with a way in.
- `pruneAnonymousPeople` deletes every anonymous account left, however
  new, never one identified. Learn and Todo schedule it as
  `prune-accounts` and cancel the old `prune-abandoned-anonymous-people`
  entry once.
- Learn: someone without `identified` keeps no files;
  `FILE_LIMIT_ANONYMOUS_MB` is gone. `minimumAppBuild` is 23, as builds
  up to 22 start with an anonymous account.
- Learn's sync tests make their person and session by SQL in the
  development database, as Serverpod stores sessions.
- Docs, store listings and ADRs 0010, 0014, 0016, 0017 and 0019 amended.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-24 16:07:27 +02:00
commit 87671208df
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
2 changed files with 837 additions and 1353 deletions

View file

@ -12,7 +12,7 @@
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:serverpod_client/serverpod_client.dart' as _isc;
/// The ways a person signs in, besides an anonymous account's session.
/// The ways a person signs in, besides their sessions.
abstract class SignInWays
implements _isc.SerializableModel, _isc.ProtocolSerialization {
SignInWays._({

View file

@ -63,8 +63,6 @@ import 'package:halkyon_base_client/src/protocol/simplelogin/simplelogin_return.
import 'package:halkyon_oidc_client/halkyon_oidc_client.dart' as _ibkpwzgl;
import 'package:serverpod_auth_core_client/serverpod_auth_core_client.dart'
as _iacc;
import 'package:serverpod_auth_idp_client/serverpod_auth_idp_client.dart'
as _iaic;
import 'package:serverpod_client/serverpod_client.dart' as _isc;
/// The signed-in person's account.
@ -76,8 +74,8 @@ class EndpointAccount extends _isc.EndpointRef {
String get name => 'halkyon_base.account';
/// Deletes the caller with everything they have (see [deletePerson]). The
/// app also calls it when an anonymous account is given up (decided
/// 2026-09-30). Their other devices learn at once that their sessions
/// app also calls it for an anonymous account left from before, as it
/// starts (ADR 0020, D25). Their other devices learn at once that their sessions
/// ended. An account deletes everywhere (ADR 0016): on a member, the
/// register deletes it and tells every member, this one deleting its own
/// data at once; an anonymous person is this service's alone. An account
@ -99,8 +97,8 @@ class EndpointAccount extends _isc.EndpointRef {
},
);
/// How the caller signs in besides their sessions: with GitHub, with an
/// email address, both, or neither (anonymous).
/// How the caller signs in besides their sessions ([SignInWays]): with
/// SimpleLogin, a password, an email address or GitHub.
_ida.Future<_ifld8s4r.SignInWays> signInWays() =>
caller.callServerEndpoint<_ifld8s4r.SignInWays>(
'halkyon_base.account',
@ -109,28 +107,6 @@ class EndpointAccount extends _isc.EndpointRef {
);
}
/// Signs people in anonymously; the app calls it on first launch.
/// {@category Endpoint}
class EndpointAnonymousIdp extends _iaic.EndpointAnonymousIdpBase {
EndpointAnonymousIdp(_isc.EndpointCaller caller) : super(caller);
@override
String get name => 'halkyon_base.anonymousIdp';
/// Creates a new anonymous account and returns its session: at most 30 an
/// hour from one network (`anonymousSignUpLimit`), counted by the
/// visitor's address (`networkOf`) rather than as Serverpod's own limit
/// counts them, by the connection's, which behind a proxy is the proxy's
/// for everyone.
@override
_ida.Future<_iacc.AuthSuccess> login({String? token}) =>
caller.callServerEndpoint<_iacc.AuthSuccess>(
'halkyon_base.anonymousIdp',
'login',
{'token': token},
);
}
/// Codes by mail (ADR 0017): what proves an address for a password's setup,
/// a sign-up, a setup never finished and the resets. A code signs nobody in
/// by itself. Without a session, as signing up and getting back in have
@ -189,34 +165,6 @@ class EndpointGitHubSignIn extends _isc.EndpointRef {
);
}
/// Links a GitHub account to the signed-in person: the lossless upgrade from
/// an anonymous account. The person and everything they have stay
/// where they are; only the way they sign in changes. The instance's
/// register keeps the GitHub ID (ADR 0016).
/// {@category Endpoint}
class EndpointGitHubUpgrade extends _isc.EndpointRef {
EndpointGitHubUpgrade(_isc.EndpointCaller caller) : super(caller);
@override
String get name => 'halkyon_base.gitHubUpgrade';
/// Links the GitHub account that [code] proves to the caller; refusals as in
/// [linkGitHubAccount].
_ida.Future<void> upgrade({
required String code,
required String codeVerifier,
required String redirectUri,
}) => caller.callServerEndpoint<void>(
'halkyon_base.gitHubUpgrade',
'upgrade',
{
'code': code,
'codeVerifier': codeVerifier,
'redirectUri': redirectUri,
},
);
}
/// What every Halkyon instance tells its apps. Public: an app asks before
/// anyone is signed in.
/// {@category Endpoint}
@ -577,9 +525,8 @@ class EndpointAuthenticator extends _isc.EndpointRef {
);
}
/// Giving the signed-in person a password (ADR 0017): the way an anonymous
/// person becomes identified, keeping everything they have, and the way
/// someone who signs in with a code by mail or GitHub moves to a password.
/// Giving the signed-in person a password (ADR 0017): the way someone who
/// signs in with a code by mail or GitHub moves to a password.
/// The instance's register keeps it (ADR 0016).
/// {@category Endpoint}
class EndpointPassword extends _isc.EndpointRef {
@ -904,8 +851,9 @@ class EndpointSimpleLogin extends _isc.EndpointRef {
/// Begins a sign-in from this server's app, coming [back] by the loopback
/// or the app's scheme: the attempt, whose ID the device keeps to itself,
/// and SimpleLogin's address to open in the browser, which comes back
/// through the forwarding page with a code for [complete]. Refusals as
/// `beginSimpleLogin`'s.
/// through the forwarding page with a code for [complete]. Refused for a
/// caller signed in without `identified` (`OidcRefusal.wrongCaller`), and
/// as `beginSimpleLogin` refuses.
_ida.Future<_ibkpwzgl.OidcStart> begin({
required _ihc3njmr.SimpleLoginReturn back,
}) => caller.callServerEndpoint<_ibkpwzgl.OidcStart>(
@ -922,8 +870,8 @@ class EndpointSimpleLogin extends _isc.EndpointRef {
/// switched to another account. For an account, whether it has its keys
/// (`keys.unlock` opens them; one without makes them) and its handle; and
/// for ten minutes the attempt's ID stands for this sign-in, for a change
/// the device asks for (ADR 0020, D20). Refusals as
/// `completeSimpleLogin`'s.
/// the device asks for (ADR 0020, D20). Refused as [begin] refuses its
/// caller, and as `completeSimpleLogin` refuses.
_ida.Future<_ianfx7qj.SimpleLoginCompleted> complete({
required _isc.UuidValue attempt,
required String code,
@ -1027,10 +975,8 @@ class EndpointSimpleLogin extends _isc.EndpointRef {
class Caller extends _isc.ModuleEndpointCaller {
Caller(_isc.ServerpodClientShared client) : super(client) {
account = EndpointAccount(this);
anonymousIdp = EndpointAnonymousIdp(this);
emailCode = EndpointEmailCode(this);
gitHubSignIn = EndpointGitHubSignIn(this);
gitHubUpgrade = EndpointGitHubUpgrade(this);
instance = EndpointInstance(this);
keys = EndpointKeys(this);
accountRecovery = EndpointAccountRecovery(this);
@ -1044,14 +990,10 @@ class Caller extends _isc.ModuleEndpointCaller {
late final EndpointAccount account;
late final EndpointAnonymousIdp anonymousIdp;
late final EndpointEmailCode emailCode;
late final EndpointGitHubSignIn gitHubSignIn;
late final EndpointGitHubUpgrade gitHubUpgrade;
late final EndpointInstance instance;
late final EndpointKeys keys;
@ -1073,10 +1015,8 @@ class Caller extends _isc.ModuleEndpointCaller {
@override
Map<String, _isc.EndpointRef> get endpointRefLookup => {
'halkyon_base.account': account,
'halkyon_base.anonymousIdp': anonymousIdp,
'halkyon_base.emailCode': emailCode,
'halkyon_base.gitHubSignIn': gitHubSignIn,
'halkyon_base.gitHubUpgrade': gitHubUpgrade,
'halkyon_base.instance': instance,
'halkyon_base.keys': keys,
'halkyon_base.accountRecovery': accountRecovery,