feat(base)!: sign-ups as the operator says, waiting accounts, operators (ADR 0021, step 1)

SIGN_UP takes open (unset), approval or closed; anything else stops the
server as it starts. Closed refuses a SimpleLogin sign-up before its
handle is counted, an address sign-up only once the code has proved the
address, so nothing tells whether an address has an account. Approval
lets a new account wait, at most ten at once: no session is issued for a
waiting or suspended account, only a pass (32 random bytes, kept as its
SHA-256, 30 days, five per account) and four BIP-39 words the operator
reads out to match it. The pass looks, forgets, and deletes an account
that was never approved; nothing else.

Operators hold the scope operator, given first by SQL (D32), then named
in the app: InstanceOperatorEndpoint lists, approves and refuses waiting
accounts, finds accounts, suspends them (ending their sessions), and
names and takes back operators, never the last. account.operating()
counts the waiting for the badge. Members ask the register (five new
operations, a refusal kind signUp), and say closed while it is out of
reach. Never-approved accounts go after 14 days in the daily pruning.

The migration only adds the tables waiting_account and waiting_pass, so
the server before it keeps working on the new schema.

BREAKING CHANGE: a register and its members must run this version
together: a member asks the register operations an older one doesn't
know.

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-25 15:04:21 +01:00
commit a060fbc21d
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
11 changed files with 50068 additions and 37 deletions

View file

@ -0,0 +1,138 @@
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
/* To generate run: "serverpod generate" */
// ignore_for_file: implementation_imports
// ignore_for_file: library_private_types_in_public_api
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: public_member_api_docs
// ignore_for_file: type_literal_in_constant_pattern
// ignore_for_file: use_super_parameters
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:halkyon_base_client/src/protocol/protocol.dart' as _iwgbpyo5;
import 'package:serverpod_client/serverpod_client.dart' as _isc;
/// The account signed in to waits for an operator (Halkyon Learn's ADR
/// 0021, D23 and D31): a new one not approved yet, or one an operator
/// suspended. It gets no session; its device keeps [pass] for the wait
/// screen's calls (`waiting`), and shows [words].
abstract class AccountWaitingException
implements
_isc.SerializableException,
_isc.SerializableModel,
_isc.ProtocolSerialization {
AccountWaitingException._({
required this.pass,
required this.words,
this.handle,
required this.suspended,
});
factory AccountWaitingException({
required String pass,
required List<String> words,
String? handle,
required bool suspended,
}) = _AccountWaitingExceptionImpl;
factory AccountWaitingException.fromJson(
Map<String, dynamic> jsonSerialization,
) {
return AccountWaitingException(
pass: jsonSerialization['pass'] as String,
words: _iwgbpyo5.Protocol().deserialize<List<String>>(
jsonSerialization['words'],
),
handle: jsonSerialization['handle'] as String?,
suspended: _isc.BoolJsonExtension.fromJson(
jsonSerialization['suspended'],
),
);
}
/// The waiting pass: a random secret the instance keeps only the hash
/// of, for this device alone.
String pass;
/// The matching code, four words of BIP-39's English list (D33), which
/// the operator sees beside the handle and reads out.
List<String> words;
/// The account's handle; null for an account without one.
String? handle;
/// Whether an operator suspended the account (D22), which was approved
/// before; otherwise it is new.
bool suspended;
/// Returns a shallow copy of this [AccountWaitingException]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
AccountWaitingException copyWith({
String? pass,
List<String>? words,
String? handle,
bool? suspended,
});
@override
Map<String, dynamic> toJson() {
return {
'__className__': 'halkyon_base.AccountWaitingException',
'pass': pass,
'words': words.toJson(),
if (handle != null) 'handle': handle,
'suspended': suspended,
};
}
@override
Map<String, dynamic> toJsonForProtocol() {
return {
'__className__': 'halkyon_base.AccountWaitingException',
'pass': pass,
'words': words.toJson(),
if (handle != null) 'handle': handle,
'suspended': suspended,
};
}
@override
String toString() {
return 'AccountWaitingException(pass: $pass, words: $words, handle: $handle, suspended: $suspended)';
}
}
class _Undefined {}
class _AccountWaitingExceptionImpl extends AccountWaitingException {
_AccountWaitingExceptionImpl({
required String pass,
required List<String> words,
String? handle,
required bool suspended,
}) : super._(
pass: pass,
words: words,
handle: handle,
suspended: suspended,
);
/// Returns a shallow copy of this [AccountWaitingException]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
@override
AccountWaitingException copyWith({
String? pass,
List<String>? words,
Object? handle = _Undefined,
bool? suspended,
}) {
return AccountWaitingException(
pass: pass ?? this.pass,
words: words ?? this.words.map((e0) => e0).toList(),
handle: handle is String? ? handle : this.handle,
suspended: suspended ?? this.suspended,
);
}
}

View file

@ -0,0 +1,47 @@
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
/* To generate run: "serverpod generate" */
// ignore_for_file: implementation_imports
// ignore_for_file: library_private_types_in_public_api
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: public_member_api_docs
// ignore_for_file: type_literal_in_constant_pattern
// ignore_for_file: use_super_parameters
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:serverpod_client/serverpod_client.dart' as _isc;
/// Who may make an account on this instance (Halkyon Learn's ADR 0021,
/// D19), as its operator says (SIGN_UP).
enum SignUpMode implements _isc.SerializableModel {
/// Anyone: a new account signs in at once.
open,
/// Anyone, but a new account waits until an operator approves it.
approval,
/// Nobody new: only the accounts there are sign in.
closed;
static SignUpMode fromJson(String name) {
switch (name) {
case 'open':
return SignUpMode.open;
case 'approval':
return SignUpMode.approval;
case 'closed':
return SignUpMode.closed;
default:
throw ArgumentError(
'Value "$name" cannot be converted to "SignUpMode"',
);
}
}
@override
String toJson() => name;
@override
String toString() => name;
}

View file

@ -0,0 +1,41 @@
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
/* To generate run: "serverpod generate" */
// ignore_for_file: implementation_imports
// ignore_for_file: library_private_types_in_public_api
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: public_member_api_docs
// ignore_for_file: type_literal_in_constant_pattern
// ignore_for_file: use_super_parameters
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:serverpod_client/serverpod_client.dart' as _isc;
/// Why a sign-up was refused (Halkyon Learn's ADR 0021, D19 and D25).
enum SignUpRefusal implements _isc.SerializableModel {
/// The instance takes no new accounts (SIGN_UP=closed).
closed,
/// As many new accounts wait for an operator as may wait at once.
tooManyWaiting;
static SignUpRefusal fromJson(String name) {
switch (name) {
case 'closed':
return SignUpRefusal.closed;
case 'tooManyWaiting':
return SignUpRefusal.tooManyWaiting;
default:
throw ArgumentError(
'Value "$name" cannot be converted to "SignUpRefusal"',
);
}
}
@override
String toJson() => name;
@override
String toString() => name;
}

View file

@ -0,0 +1,78 @@
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
/* To generate run: "serverpod generate" */
// ignore_for_file: implementation_imports
// ignore_for_file: library_private_types_in_public_api
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: public_member_api_docs
// ignore_for_file: type_literal_in_constant_pattern
// ignore_for_file: use_super_parameters
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:serverpod_client/serverpod_client.dart' as _isc;
import '../admission/sign_up_refusal.dart' as _ib0mzqlk;
/// A sign-up was refused before anything was made; the identity or the
/// code it came with stays as it was.
abstract class SignUpRefusedException
implements
_isc.SerializableException,
_isc.SerializableModel,
_isc.ProtocolSerialization {
SignUpRefusedException._({required this.reason});
factory SignUpRefusedException({required _ib0mzqlk.SignUpRefusal reason}) =
_SignUpRefusedExceptionImpl;
factory SignUpRefusedException.fromJson(
Map<String, dynamic> jsonSerialization,
) {
return SignUpRefusedException(
reason: _ib0mzqlk.SignUpRefusal.fromJson(
(jsonSerialization['reason'] as String),
),
);
}
/// Why it was refused.
_ib0mzqlk.SignUpRefusal reason;
/// Returns a shallow copy of this [SignUpRefusedException]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
SignUpRefusedException copyWith({_ib0mzqlk.SignUpRefusal? reason});
@override
Map<String, dynamic> toJson() {
return {
'__className__': 'halkyon_base.SignUpRefusedException',
'reason': reason.toJson(),
};
}
@override
Map<String, dynamic> toJsonForProtocol() {
return {
'__className__': 'halkyon_base.SignUpRefusedException',
'reason': reason.toJson(),
};
}
@override
String toString() {
return 'SignUpRefusedException(reason: $reason)';
}
}
class _SignUpRefusedExceptionImpl extends SignUpRefusedException {
_SignUpRefusedExceptionImpl({required _ib0mzqlk.SignUpRefusal reason})
: super._(reason: reason);
/// Returns a shallow copy of this [SignUpRefusedException]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
@override
SignUpRefusedException copyWith({_ib0mzqlk.SignUpRefusal? reason}) {
return SignUpRefusedException(reason: reason ?? this.reason);
}
}

View file

@ -0,0 +1,54 @@
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
/* To generate run: "serverpod generate" */
// ignore_for_file: implementation_imports
// ignore_for_file: library_private_types_in_public_api
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: public_member_api_docs
// ignore_for_file: type_literal_in_constant_pattern
// ignore_for_file: use_super_parameters
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:serverpod_client/serverpod_client.dart' as _isc;
/// Where a waiting account stands, as its wait screen asks with its pass
/// (Halkyon Learn's ADR 0021, D31).
enum WaitingState implements _isc.SerializableModel {
/// New, not approved yet.
waiting,
/// Suspended by an operator, waiting to be approved anew.
suspended,
/// Approved: the person signs in once more, as they signed in before,
/// which opens their keys. The pass is gone with this answer.
approved,
/// The pass is unknown: forgotten, past its time, or its account gone
/// (refused by an operator, unapproved for too long, or deleted).
unknown;
static WaitingState fromJson(String name) {
switch (name) {
case 'waiting':
return WaitingState.waiting;
case 'suspended':
return WaitingState.suspended;
case 'approved':
return WaitingState.approved;
case 'unknown':
return WaitingState.unknown;
default:
throw ArgumentError(
'Value "$name" cannot be converted to "WaitingState"',
);
}
}
@override
String toJson() => name;
@override
String toString() => name;
}

View file

@ -12,6 +12,8 @@
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'dart:async' as _ida;
import 'dart:typed_data' as _idt;
import 'package:halkyon_base_client/src/protocol/admission/waiting_state.dart'
as _i1sx1z35;
import 'package:halkyon_base_client/src/protocol/auth/email_code_purpose.dart'
as _i6rtanuw;
import 'package:halkyon_base_client/src/protocol/auth/sign_in_ways.dart'
@ -30,6 +32,8 @@ import 'package:halkyon_base_client/src/protocol/keys/keys_proof.dart'
as _iznbn55x;
import 'package:halkyon_base_client/src/protocol/keys/keys_setup.dart'
as _iec2fqr0;
import 'package:halkyon_base_client/src/protocol/operator/account_overview.dart'
as _ijg5xrj3;
import 'package:halkyon_base_client/src/protocol/password/authenticator.dart'
as _ivfmflch;
import 'package:halkyon_base_client/src/protocol/password/authenticator_enrolment.dart'
@ -65,6 +69,46 @@ import 'package:serverpod_auth_core_client/serverpod_auth_core_client.dart'
as _iacc;
import 'package:serverpod_client/serverpod_client.dart' as _isc;
/// The wait screen's calls (Halkyon Learn's ADR 0021, D23 and D31), with
/// the pass a waiting account's device was handed in place of a session
/// (`AccountWaitingException`), and nothing else: no session opens them.
/// The instance's register keeps the passes (ADR 0016).
/// {@category Endpoint}
class EndpointWaiting extends _isc.EndpointRef {
EndpointWaiting(_isc.EndpointCaller caller) : super(caller);
@override
String get name => 'halkyon_base.waiting';
/// Where the account [pass] was handed out for stands: waiting, suspended,
/// approved (the pass gone with this answer: the person signs in once
/// more, as before), or unknown.
_ida.Future<_i1sx1z35.WaitingState> look({required String pass}) =>
caller.callServerEndpoint<_i1sx1z35.WaitingState>(
'halkyon_base.waiting',
'look',
{'pass': pass},
);
/// Deletes the new account [pass] was handed out for, with all it has;
/// whether it did: not a suspended one, whose deletion takes its owner's
/// password and a code (ADR 0017), nor for a pass unknown.
_ida.Future<bool> deleteAccount({required String pass}) =>
caller.callServerEndpoint<bool>(
'halkyon_base.waiting',
'deleteAccount',
{'pass': pass},
);
/// Forgets [pass], as its device no longer waits with it.
_ida.Future<void> forget({required String pass}) =>
caller.callServerEndpoint<void>(
'halkyon_base.waiting',
'forget',
{'pass': pass},
);
}
/// The signed-in person's account.
/// {@category Endpoint}
class EndpointAccount extends _isc.EndpointRef {
@ -105,6 +149,17 @@ class EndpointAccount extends _isc.EndpointRef {
'signInWays',
{},
);
/// How many accounts wait for an operator, for the badge on an operator's
/// Settings (Halkyon Learn's ADR 0021, D34); null where the caller isn't
/// one. It reads the caller's own role from their own session, which every
/// operator's call requires (`InstanceOperatorEndpoint`), so the app knows
/// whether to show the operator's card.
_ida.Future<int?> operating() => caller.callServerEndpoint<int?>(
'halkyon_base.account',
'operating',
{},
);
}
/// Codes by mail (ADR 0017): what proves an address for a password's setup,
@ -151,9 +206,10 @@ class EndpointInstance extends _isc.EndpointRef {
/// the register can mail codes (this server, or on a member the register,
/// ADR 0016), password sign-in where the register holds its keys,
/// SimpleLogin where the register has the instance's SimpleLogin app
/// (ADR 0020), each as its operator offers it, where its source is, the
/// oldest app build it works with, and where it keeps people's data and
/// its backups.
/// (ADR 0020), each as its operator offers it, who may make an account
/// (Halkyon Learn's ADR 0021, the register's operator's word), where its
/// source is, the oldest app build it works with, and where it keeps
/// people's data and its backups.
_ida.Future<_isnyur2i.InstanceBasics> basics() =>
caller.callServerEndpoint<_isnyur2i.InstanceBasics>(
'halkyon_base.instance',
@ -341,6 +397,93 @@ class EndpointKeys extends _isc.EndpointRef {
);
}
/// What an instance's operators do in the app (Halkyon Learn's ADR 0021,
/// D21, D22 and D34): the accounts that wait, approved or refused,
/// accounts found and suspended, and who else operates. The operator's
/// scope only the register's sessions carry, so a member refuses every
/// call. Refusals are `OperatorRefusedException`s.
/// {@category Endpoint}
class EndpointInstanceOperator extends _isc.EndpointRef {
EndpointInstanceOperator(_isc.EndpointCaller caller) : super(caller);
@override
String get name => 'halkyon_base.instanceOperator';
/// The accounts that wait, new or suspended, the longest waiting first,
/// each with its handle and matching code.
_ida.Future<List<_ijg5xrj3.AccountOverview>> waiting() =>
caller.callServerEndpoint<List<_ijg5xrj3.AccountOverview>>(
'halkyon_base.instanceOperator',
'waiting',
{},
);
/// Approves [account], new or suspended; refused for one that doesn't
/// wait (`OperatorRefusal.notFound`).
_ida.Future<void> approve({required _isc.UuidValue account}) =>
caller.callServerEndpoint<void>(
'halkyon_base.instanceOperator',
'approve',
{'account': account},
);
/// Refuses [account], new and waiting, deleting it; refused for any other
/// (`OperatorRefusal.notFound`).
_ida.Future<void> refuse({required _isc.UuidValue account}) =>
caller.callServerEndpoint<void>(
'halkyon_base.instanceOperator',
'refuse',
{'account': account},
);
/// The accounts whose handle starts with [startingWith], whatever its
/// case, by handle, at most 50.
_ida.Future<List<_ijg5xrj3.AccountOverview>> accounts({
required String startingWith,
}) => caller.callServerEndpoint<List<_ijg5xrj3.AccountOverview>>(
'halkyon_base.instanceOperator',
'accounts',
{'startingWith': startingWith},
);
/// Suspends [account]: its sessions end everywhere, and it waits until
/// approved anew. Refused for an operator (`OperatorRefusal.isOperator`)
/// and for one unknown or waiting already (`OperatorRefusal.notFound`).
_ida.Future<void> suspend({required _isc.UuidValue account}) =>
caller.callServerEndpoint<void>(
'halkyon_base.instanceOperator',
'suspend',
{'account': account},
);
/// The instance's operators, by handle.
_ida.Future<List<_ijg5xrj3.AccountOverview>> operators() =>
caller.callServerEndpoint<List<_ijg5xrj3.AccountOverview>>(
'halkyon_base.instanceOperator',
'operators',
{},
);
/// Names the account with [handle] an operator, approving it should it
/// wait; refused for a handle nobody has (`OperatorRefusal.notFound`).
_ida.Future<void> appoint({required String handle}) =>
caller.callServerEndpoint<void>(
'halkyon_base.instanceOperator',
'appoint',
{'handle': handle},
);
/// Takes the operator's role back from [account]; refused for one that
/// isn't an operator (`OperatorRefusal.notFound`) and for the last
/// (`OperatorRefusal.lastOperator`).
_ida.Future<void> takeBack({required _isc.UuidValue account}) =>
caller.callServerEndpoint<void>(
'halkyon_base.instanceOperator',
'takeBack',
{'account': account},
);
}
/// Getting back into an account (ADR 0017): a lost authenticator, a lost
/// password with the recovery phrase, a lost password without it. Called
/// without a session, as the person can't sign in. The instance's register
@ -1051,10 +1194,12 @@ class EndpointSimpleLogin extends _isc.EndpointRef {
class Caller extends _isc.ModuleEndpointCaller {
Caller(_isc.ServerpodClientShared client) : super(client) {
waiting = EndpointWaiting(this);
account = EndpointAccount(this);
emailCode = EndpointEmailCode(this);
instance = EndpointInstance(this);
keys = EndpointKeys(this);
instanceOperator = EndpointInstanceOperator(this);
accountRecovery = EndpointAccountRecovery(this);
authenticator = EndpointAuthenticator(this);
password = EndpointPassword(this);
@ -1064,6 +1209,8 @@ class Caller extends _isc.ModuleEndpointCaller {
simpleLogin = EndpointSimpleLogin(this);
}
late final EndpointWaiting waiting;
late final EndpointAccount account;
late final EndpointEmailCode emailCode;
@ -1072,6 +1219,8 @@ class Caller extends _isc.ModuleEndpointCaller {
late final EndpointKeys keys;
late final EndpointInstanceOperator instanceOperator;
late final EndpointAccountRecovery accountRecovery;
late final EndpointAuthenticator authenticator;
@ -1088,10 +1237,12 @@ class Caller extends _isc.ModuleEndpointCaller {
@override
Map<String, _isc.EndpointRef> get endpointRefLookup => {
'halkyon_base.waiting': waiting,
'halkyon_base.account': account,
'halkyon_base.emailCode': emailCode,
'halkyon_base.instance': instance,
'halkyon_base.keys': keys,
'halkyon_base.instanceOperator': instanceOperator,
'halkyon_base.accountRecovery': accountRecovery,
'halkyon_base.authenticator': authenticator,
'halkyon_base.password': password,

View file

@ -11,6 +11,7 @@
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:serverpod_client/serverpod_client.dart' as _isc;
import '../admission/sign_up_mode.dart' as _iypagcol;
/// What every Halkyon instance tells its apps, to anyone, before anyone signs
/// in: which app it serves, how people sign in here, where the source of what it runs is, the
@ -27,8 +28,10 @@ abstract class InstanceBasics
this.backupLocation,
bool? passwordSignIn,
bool? simpleLogin,
_iypagcol.SignUpMode? signUp,
}) : passwordSignIn = passwordSignIn ?? false,
simpleLogin = simpleLogin ?? false;
simpleLogin = simpleLogin ?? false,
signUp = signUp ?? _iypagcol.SignUpMode.open;
factory InstanceBasics({
required String app,
@ -39,6 +42,7 @@ abstract class InstanceBasics
String? backupLocation,
bool? passwordSignIn,
bool? simpleLogin,
_iypagcol.SignUpMode? signUp,
}) = _InstanceBasicsImpl;
factory InstanceBasics.fromJson(Map<String, dynamic> jsonSerialization) {
@ -59,6 +63,11 @@ abstract class InstanceBasics
simpleLogin: jsonSerialization['simpleLogin'] == null
? null
: _isc.BoolJsonExtension.fromJson(jsonSerialization['simpleLogin']),
signUp: jsonSerialization['signUp'] == null
? null
: _iypagcol.SignUpMode.fromJson(
(jsonSerialization['signUp'] as String),
),
);
}
@ -96,6 +105,11 @@ abstract class InstanceBasics
/// instance's register has a SimpleLogin app, and its operator offers it.
bool simpleLogin;
/// Who may make an account here (Halkyon Learn's ADR 0021, D19), as the
/// register's operator says (SIGN_UP): open where an older instance says
/// nothing.
_iypagcol.SignUpMode signUp;
/// Returns a shallow copy of this [InstanceBasics]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
@ -108,6 +122,7 @@ abstract class InstanceBasics
String? backupLocation,
bool? passwordSignIn,
bool? simpleLogin,
_iypagcol.SignUpMode? signUp,
});
@override
Map<String, dynamic> toJson() {
@ -121,6 +136,7 @@ abstract class InstanceBasics
if (backupLocation != null) 'backupLocation': backupLocation,
'passwordSignIn': passwordSignIn,
'simpleLogin': simpleLogin,
'signUp': signUp.toJson(),
};
}
@ -136,6 +152,7 @@ abstract class InstanceBasics
if (backupLocation != null) 'backupLocation': backupLocation,
'passwordSignIn': passwordSignIn,
'simpleLogin': simpleLogin,
'signUp': signUp.toJson(),
};
}
@ -157,6 +174,7 @@ class _InstanceBasicsImpl extends InstanceBasics {
String? backupLocation,
bool? passwordSignIn,
bool? simpleLogin,
_iypagcol.SignUpMode? signUp,
}) : super._(
app: app,
emailSignIn: emailSignIn,
@ -166,6 +184,7 @@ class _InstanceBasicsImpl extends InstanceBasics {
backupLocation: backupLocation,
passwordSignIn: passwordSignIn,
simpleLogin: simpleLogin,
signUp: signUp,
);
/// Returns a shallow copy of this [InstanceBasics]
@ -181,6 +200,7 @@ class _InstanceBasicsImpl extends InstanceBasics {
Object? backupLocation = _Undefined,
bool? passwordSignIn,
bool? simpleLogin,
_iypagcol.SignUpMode? signUp,
}) {
return InstanceBasics(
app: app ?? this.app,
@ -193,6 +213,7 @@ class _InstanceBasicsImpl extends InstanceBasics {
: this.backupLocation,
passwordSignIn: passwordSignIn ?? this.passwordSignIn,
simpleLogin: simpleLogin ?? this.simpleLogin,
signUp: signUp ?? this.signUp,
);
}
}

View file

@ -0,0 +1,181 @@
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
/* To generate run: "serverpod generate" */
// ignore_for_file: implementation_imports
// ignore_for_file: library_private_types_in_public_api
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: public_member_api_docs
// ignore_for_file: type_literal_in_constant_pattern
// ignore_for_file: use_super_parameters
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:halkyon_base_client/src/protocol/protocol.dart' as _iwgbpyo5;
import 'package:serverpod_client/serverpod_client.dart' as _isc;
/// An account as an operator sees it (Halkyon Learn's ADR 0021, point 3):
/// what the instance knows of it, never its content.
abstract class AccountOverview
implements _isc.SerializableModel, _isc.ProtocolSerialization {
AccountOverview._({
required this.account,
this.handle,
required this.since,
required this.isOperator,
this.waitingSince,
this.words,
required this.suspended,
});
factory AccountOverview({
required _isc.UuidValue account,
String? handle,
required DateTime since,
required bool isOperator,
DateTime? waitingSince,
List<String>? words,
required bool suspended,
}) = _AccountOverviewImpl;
factory AccountOverview.fromJson(Map<String, dynamic> jsonSerialization) {
return AccountOverview(
account: _isc.UuidValueJsonExtension.fromJson(
jsonSerialization['account'],
),
handle: jsonSerialization['handle'] as String?,
since: _isc.DateTimeJsonExtension.fromJson(jsonSerialization['since']),
isOperator: _isc.BoolJsonExtension.fromJson(
jsonSerialization['isOperator'],
),
waitingSince: jsonSerialization['waitingSince'] == null
? null
: _isc.DateTimeJsonExtension.fromJson(
jsonSerialization['waitingSince'],
),
words: jsonSerialization['words'] == null
? null
: _iwgbpyo5.Protocol().deserialize<List<String>>(
jsonSerialization['words'],
),
suspended: _isc.BoolJsonExtension.fromJson(
jsonSerialization['suspended'],
),
);
}
/// The account's ID, which the operator's calls name it by.
_isc.UuidValue account;
/// Its handle; null for an account without one.
String? handle;
/// When it was made.
DateTime since;
/// Whether it is an operator.
bool isOperator;
/// Whether it waits (new or suspended), and since when.
DateTime? waitingSince;
/// Its matching code while it waits (D33), which the operator reads out.
List<String>? words;
/// Whether it waits suspended, approved before.
bool suspended;
/// Returns a shallow copy of this [AccountOverview]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
AccountOverview copyWith({
_isc.UuidValue? account,
String? handle,
DateTime? since,
bool? isOperator,
DateTime? waitingSince,
List<String>? words,
bool? suspended,
});
@override
Map<String, dynamic> toJson() {
return {
'__className__': 'halkyon_base.AccountOverview',
'account': account.toJson(),
if (handle != null) 'handle': handle,
'since': since.toJson(),
'isOperator': isOperator,
if (waitingSince != null) 'waitingSince': waitingSince?.toJson(),
if (words != null) 'words': words?.toJson(),
'suspended': suspended,
};
}
@override
Map<String, dynamic> toJsonForProtocol() {
return {
'__className__': 'halkyon_base.AccountOverview',
'account': account.toJson(),
if (handle != null) 'handle': handle,
'since': since.toJson(),
'isOperator': isOperator,
if (waitingSince != null) 'waitingSince': waitingSince?.toJson(),
if (words != null) 'words': words?.toJson(),
'suspended': suspended,
};
}
@override
String toString() {
return _isc.SerializationManager.encode(this);
}
}
class _Undefined {}
class _AccountOverviewImpl extends AccountOverview {
_AccountOverviewImpl({
required _isc.UuidValue account,
String? handle,
required DateTime since,
required bool isOperator,
DateTime? waitingSince,
List<String>? words,
required bool suspended,
}) : super._(
account: account,
handle: handle,
since: since,
isOperator: isOperator,
waitingSince: waitingSince,
words: words,
suspended: suspended,
);
/// Returns a shallow copy of this [AccountOverview]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
@override
AccountOverview copyWith({
_isc.UuidValue? account,
Object? handle = _Undefined,
DateTime? since,
bool? isOperator,
Object? waitingSince = _Undefined,
Object? words = _Undefined,
bool? suspended,
}) {
return AccountOverview(
account: account ?? this.account,
handle: handle is String? ? handle : this.handle,
since: since ?? this.since,
isOperator: isOperator ?? this.isOperator,
waitingSince: waitingSince is DateTime?
? waitingSince
: this.waitingSince,
words: words is List<String>?
? words
: this.words?.map((e0) => e0).toList(),
suspended: suspended ?? this.suspended,
);
}
}

View file

@ -0,0 +1,48 @@
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
/* To generate run: "serverpod generate" */
// ignore_for_file: implementation_imports
// ignore_for_file: library_private_types_in_public_api
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: public_member_api_docs
// ignore_for_file: type_literal_in_constant_pattern
// ignore_for_file: use_super_parameters
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:serverpod_client/serverpod_client.dart' as _isc;
/// Why an operator's call was refused (Halkyon Learn's ADR 0021, D21 and
/// D22); nothing changed.
enum OperatorRefusal implements _isc.SerializableModel {
/// No such account, or not as the call needs it: approving one that
/// doesn't wait, refusing one that isn't new, suspending one that waits.
notFound,
/// An operator is never suspended: take their role back first.
isOperator,
/// The last operator keeps the role.
lastOperator;
static OperatorRefusal fromJson(String name) {
switch (name) {
case 'notFound':
return OperatorRefusal.notFound;
case 'isOperator':
return OperatorRefusal.isOperator;
case 'lastOperator':
return OperatorRefusal.lastOperator;
default:
throw ArgumentError(
'Value "$name" cannot be converted to "OperatorRefusal"',
);
}
}
@override
String toJson() => name;
@override
String toString() => name;
}

View file

@ -0,0 +1,78 @@
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
/* To generate run: "serverpod generate" */
// ignore_for_file: implementation_imports
// ignore_for_file: library_private_types_in_public_api
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: public_member_api_docs
// ignore_for_file: type_literal_in_constant_pattern
// ignore_for_file: use_super_parameters
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:serverpod_client/serverpod_client.dart' as _isc;
import '../operator/operator_refusal.dart' as _i39615vp;
/// An operator's call was refused; nothing changed.
abstract class OperatorRefusedException
implements
_isc.SerializableException,
_isc.SerializableModel,
_isc.ProtocolSerialization {
OperatorRefusedException._({required this.reason});
factory OperatorRefusedException({
required _i39615vp.OperatorRefusal reason,
}) = _OperatorRefusedExceptionImpl;
factory OperatorRefusedException.fromJson(
Map<String, dynamic> jsonSerialization,
) {
return OperatorRefusedException(
reason: _i39615vp.OperatorRefusal.fromJson(
(jsonSerialization['reason'] as String),
),
);
}
/// Why it was refused.
_i39615vp.OperatorRefusal reason;
/// Returns a shallow copy of this [OperatorRefusedException]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
OperatorRefusedException copyWith({_i39615vp.OperatorRefusal? reason});
@override
Map<String, dynamic> toJson() {
return {
'__className__': 'halkyon_base.OperatorRefusedException',
'reason': reason.toJson(),
};
}
@override
Map<String, dynamic> toJsonForProtocol() {
return {
'__className__': 'halkyon_base.OperatorRefusedException',
'reason': reason.toJson(),
};
}
@override
String toString() {
return 'OperatorRefusedException(reason: $reason)';
}
}
class _OperatorRefusedExceptionImpl extends OperatorRefusedException {
_OperatorRefusedExceptionImpl({required _i39615vp.OperatorRefusal reason})
: super._(reason: reason);
/// Returns a shallow copy of this [OperatorRefusedException]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
@override
OperatorRefusedException copyWith({_i39615vp.OperatorRefusal? reason}) {
return OperatorRefusedException(reason: reason ?? this.reason);
}
}

View file

@ -11,6 +11,8 @@
// ignore_for_file: dead_code, unnecessary_type_check
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:halkyon_base_client/src/protocol/operator/account_overview.dart'
as _ijg5xrj3;
import 'package:halkyon_base_client/src/protocol/password/authenticator.dart'
as _ivfmflch;
import 'package:halkyon_oidc_client/halkyon_oidc_client.dart' as _ibkpwzgl;
@ -19,6 +21,11 @@ import 'package:serverpod_auth_core_client/serverpod_auth_core_client.dart'
import 'package:serverpod_auth_idp_client/serverpod_auth_idp_client.dart'
as _iaic;
import 'package:serverpod_client/serverpod_client.dart' as _isc;
import 'admission/account_waiting_exception.dart' as _ija8zqhs;
import 'admission/sign_up_mode.dart' as _iws42x5t;
import 'admission/sign_up_refusal.dart' as _i3fmyy9u;
import 'admission/sign_up_refused_exception.dart' as _ihsvd1xl;
import 'admission/waiting_state.dart' as _ia0s7l9v;
import 'auth/email_code_purpose.dart' as _ifpka2on;
import 'auth/email_sign_in_refusal.dart' as _ieyvcag1;
import 'auth/email_sign_in_refused_exception.dart' as _iztfgag5;
@ -30,6 +37,9 @@ import 'keys/keys_owner_proof.dart' as _iis161ma;
import 'keys/keys_password.dart' as _i70xpmnz;
import 'keys/keys_proof.dart' as _iviximyh;
import 'keys/keys_setup.dart' as _iqdhlx3t;
import 'operator/account_overview.dart' as _i80faifx;
import 'operator/operator_refusal.dart' as _i3wnysru;
import 'operator/operator_refused_exception.dart' as _iicg8rjf;
import 'password/account_reset_kind.dart' as _i91ka1it;
import 'password/authenticator.dart' as _icpzuvvi;
import 'password/authenticator_enrolment.dart' as _iv7tra7v;
@ -54,6 +64,11 @@ import 'profile/profile_refused_exception.dart' as _idvc5e7p;
import 'simplelogin/simplelogin_completed.dart' as _i51ry5s2;
import 'simplelogin/simplelogin_outcome.dart' as _i5ar1hzl;
import 'simplelogin/simplelogin_return.dart' as _i8dcjd27;
export 'admission/account_waiting_exception.dart';
export 'admission/sign_up_mode.dart';
export 'admission/sign_up_refusal.dart';
export 'admission/sign_up_refused_exception.dart';
export 'admission/waiting_state.dart';
export 'auth/email_code_purpose.dart';
export 'auth/email_sign_in_refusal.dart';
export 'auth/email_sign_in_refused_exception.dart';
@ -65,6 +80,9 @@ export 'keys/keys_owner_proof.dart';
export 'keys/keys_password.dart';
export 'keys/keys_proof.dart';
export 'keys/keys_setup.dart';
export 'operator/account_overview.dart';
export 'operator/operator_refusal.dart';
export 'operator/operator_refused_exception.dart';
export 'password/account_reset_kind.dart';
export 'password/authenticator.dart';
export 'password/authenticator_enrolment.dart';
@ -136,6 +154,21 @@ class Protocol extends _isc.SerializationManager {
}
}
if (t == _ija8zqhs.AccountWaitingException) {
return _ija8zqhs.AccountWaitingException.fromJson(data) as T;
}
if (t == _iws42x5t.SignUpMode) {
return _iws42x5t.SignUpMode.fromJson(data) as T;
}
if (t == _i3fmyy9u.SignUpRefusal) {
return _i3fmyy9u.SignUpRefusal.fromJson(data) as T;
}
if (t == _ihsvd1xl.SignUpRefusedException) {
return _ihsvd1xl.SignUpRefusedException.fromJson(data) as T;
}
if (t == _ia0s7l9v.WaitingState) {
return _ia0s7l9v.WaitingState.fromJson(data) as T;
}
if (t == _ifpka2on.EmailCodePurpose) {
return _ifpka2on.EmailCodePurpose.fromJson(data) as T;
}
@ -169,6 +202,15 @@ class Protocol extends _isc.SerializationManager {
if (t == _iqdhlx3t.KeysSetup) {
return _iqdhlx3t.KeysSetup.fromJson(data) as T;
}
if (t == _i80faifx.AccountOverview) {
return _i80faifx.AccountOverview.fromJson(data) as T;
}
if (t == _i3wnysru.OperatorRefusal) {
return _i3wnysru.OperatorRefusal.fromJson(data) as T;
}
if (t == _iicg8rjf.OperatorRefusedException) {
return _iicg8rjf.OperatorRefusedException.fromJson(data) as T;
}
if (t == _i91ka1it.AccountResetKind) {
return _i91ka1it.AccountResetKind.fromJson(data) as T;
}
@ -241,6 +283,28 @@ class Protocol extends _isc.SerializationManager {
if (t == _i8dcjd27.SimpleLoginReturn) {
return _i8dcjd27.SimpleLoginReturn.fromJson(data) as T;
}
if (t == _isc.getType<_ija8zqhs.AccountWaitingException?>()) {
return (data != null
? _ija8zqhs.AccountWaitingException.fromJson(data)
: null)
as T;
}
if (t == _isc.getType<_iws42x5t.SignUpMode?>()) {
return (data != null ? _iws42x5t.SignUpMode.fromJson(data) : null) as T;
}
if (t == _isc.getType<_i3fmyy9u.SignUpRefusal?>()) {
return (data != null ? _i3fmyy9u.SignUpRefusal.fromJson(data) : null)
as T;
}
if (t == _isc.getType<_ihsvd1xl.SignUpRefusedException?>()) {
return (data != null
? _ihsvd1xl.SignUpRefusedException.fromJson(data)
: null)
as T;
}
if (t == _isc.getType<_ia0s7l9v.WaitingState?>()) {
return (data != null ? _ia0s7l9v.WaitingState.fromJson(data) : null) as T;
}
if (t == _isc.getType<_ifpka2on.EmailCodePurpose?>()) {
return (data != null ? _ifpka2on.EmailCodePurpose.fromJson(data) : null)
as T;
@ -282,6 +346,20 @@ class Protocol extends _isc.SerializationManager {
if (t == _isc.getType<_iqdhlx3t.KeysSetup?>()) {
return (data != null ? _iqdhlx3t.KeysSetup.fromJson(data) : null) as T;
}
if (t == _isc.getType<_i80faifx.AccountOverview?>()) {
return (data != null ? _i80faifx.AccountOverview.fromJson(data) : null)
as T;
}
if (t == _isc.getType<_i3wnysru.OperatorRefusal?>()) {
return (data != null ? _i3wnysru.OperatorRefusal.fromJson(data) : null)
as T;
}
if (t == _isc.getType<_iicg8rjf.OperatorRefusedException?>()) {
return (data != null
? _iicg8rjf.OperatorRefusedException.fromJson(data)
: null)
as T;
}
if (t == _isc.getType<_i91ka1it.AccountResetKind?>()) {
return (data != null ? _i91ka1it.AccountResetKind.fromJson(data) : null)
as T;
@ -385,9 +463,6 @@ class Protocol extends _isc.SerializationManager {
return (data != null ? _i8dcjd27.SimpleLoginReturn.fromJson(data) : null)
as T;
}
if (t == Set<String>) {
return (data as List).map((e) => deserialize<String>(e)).toSet() as T;
}
if (t == List<String>) {
return (data as List).map((e) => deserialize<String>(e)).toList() as T;
}
@ -397,6 +472,15 @@ class Protocol extends _isc.SerializationManager {
: null)
as T;
}
if (t == Set<String>) {
return (data as List).map((e) => deserialize<String>(e)).toSet() as T;
}
if (t == List<_ijg5xrj3.AccountOverview>) {
return (data as List)
.map((e) => deserialize<_ijg5xrj3.AccountOverview>(e))
.toList()
as T;
}
if (t == List<String>) {
return (data as List).map((e) => deserialize<String>(e)).toList() as T;
}
@ -420,6 +504,11 @@ class Protocol extends _isc.SerializationManager {
static String? getClassNameForType(Type type) {
return switch (type) {
_ija8zqhs.AccountWaitingException => 'AccountWaitingException',
_iws42x5t.SignUpMode => 'SignUpMode',
_i3fmyy9u.SignUpRefusal => 'SignUpRefusal',
_ihsvd1xl.SignUpRefusedException => 'SignUpRefusedException',
_ia0s7l9v.WaitingState => 'WaitingState',
_ifpka2on.EmailCodePurpose => 'EmailCodePurpose',
_ieyvcag1.EmailSignInRefusal => 'EmailSignInRefusal',
_iztfgag5.EmailSignInRefusedException => 'EmailSignInRefusedException',
@ -431,6 +520,9 @@ class Protocol extends _isc.SerializationManager {
_i70xpmnz.KeysPassword => 'KeysPassword',
_iviximyh.KeysProof => 'KeysProof',
_iqdhlx3t.KeysSetup => 'KeysSetup',
_i80faifx.AccountOverview => 'AccountOverview',
_i3wnysru.OperatorRefusal => 'OperatorRefusal',
_iicg8rjf.OperatorRefusedException => 'OperatorRefusedException',
_i91ka1it.AccountResetKind => 'AccountResetKind',
_icpzuvvi.Authenticator => 'Authenticator',
_iv7tra7v.AuthenticatorEnrolment => 'AuthenticatorEnrolment',
@ -472,6 +564,16 @@ class Protocol extends _isc.SerializationManager {
}
switch (data) {
case _ija8zqhs.AccountWaitingException():
return 'AccountWaitingException';
case _iws42x5t.SignUpMode():
return 'SignUpMode';
case _i3fmyy9u.SignUpRefusal():
return 'SignUpRefusal';
case _ihsvd1xl.SignUpRefusedException():
return 'SignUpRefusedException';
case _ia0s7l9v.WaitingState():
return 'WaitingState';
case _ifpka2on.EmailCodePurpose():
return 'EmailCodePurpose';
case _ieyvcag1.EmailSignInRefusal():
@ -494,6 +596,12 @@ class Protocol extends _isc.SerializationManager {
return 'KeysProof';
case _iqdhlx3t.KeysSetup():
return 'KeysSetup';
case _i80faifx.AccountOverview():
return 'AccountOverview';
case _i3wnysru.OperatorRefusal():
return 'OperatorRefusal';
case _iicg8rjf.OperatorRefusedException():
return 'OperatorRefusedException';
case _i91ka1it.AccountResetKind():
return 'AccountResetKind';
case _icpzuvvi.Authenticator():
@ -552,6 +660,21 @@ class Protocol extends _isc.SerializationManager {
if (dataClassName is! String) {
return super.deserializeByClassName(data);
}
if (dataClassName == 'AccountWaitingException') {
return deserialize<_ija8zqhs.AccountWaitingException>(data['data']);
}
if (dataClassName == 'SignUpMode') {
return deserialize<_iws42x5t.SignUpMode>(data['data']);
}
if (dataClassName == 'SignUpRefusal') {
return deserialize<_i3fmyy9u.SignUpRefusal>(data['data']);
}
if (dataClassName == 'SignUpRefusedException') {
return deserialize<_ihsvd1xl.SignUpRefusedException>(data['data']);
}
if (dataClassName == 'WaitingState') {
return deserialize<_ia0s7l9v.WaitingState>(data['data']);
}
if (dataClassName == 'EmailCodePurpose') {
return deserialize<_ifpka2on.EmailCodePurpose>(data['data']);
}
@ -585,6 +708,15 @@ class Protocol extends _isc.SerializationManager {
if (dataClassName == 'KeysSetup') {
return deserialize<_iqdhlx3t.KeysSetup>(data['data']);
}
if (dataClassName == 'AccountOverview') {
return deserialize<_i80faifx.AccountOverview>(data['data']);
}
if (dataClassName == 'OperatorRefusal') {
return deserialize<_i3wnysru.OperatorRefusal>(data['data']);
}
if (dataClassName == 'OperatorRefusedException') {
return deserialize<_iicg8rjf.OperatorRefusedException>(data['data']);
}
if (dataClassName == 'AccountResetKind') {
return deserialize<_i91ka1it.AccountResetKind>(data['data']);
}