feat(auth): a password with TOTP on the register, and resets

The register's side of signing in with an address, a password that never
leaves the device (SRP-6a) and a code from an authenticator (ADR 0017),
with members forwarding every step through AccountRegister:

- password.set: salt, Argon2id's costs, SRP's verifier, the public keys
  and the private keys wrapped twice, all made on the device; with the
  address a mailed code proves, an anonymous person becomes identified
  under their ID. It signs in once an authenticator is confirmed.
- passwordSignIn.begin and complete: SRP's two steps with a TOTP or
  recovery code. An address nobody has is answered from a decoy key as
  an account would be; every wrong credential is refused alike.
- authenticator: secrets sealed under totpKey (XChaCha20-Poly1305, bound
  to their row), each code accepted once, ten recovery codes with the
  first, the last one kept.
- accountRecovery: a lost authenticator reset 72 hours after the
  password and a mailed code or the recovery phrase ask, a lost password
  set anew with the phrase's signature, or the account reset with new
  keys 72 hours after a mailed code asks; a mail at each start, a
  sign-in calling it off.
- An account with a password no longer signs in by a mailed code or
  GitHub; no new GitHub account is linked where passwords are offered,
  and from GITHUB_SIGN_IN_ENDS GitHub signs nobody in and its IDs go.

Five tables, added by the module's migration; deletePerson deletes
their rows.

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-20 13:32:12 +02:00
commit a3a8ed3890
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
96 changed files with 22557 additions and 60 deletions

View file

@ -2,8 +2,8 @@
/// (`HalkyonBase.configure`), how people authenticate, the account's
/// deletion and the pruning, email sign-in's helpers, the instance's
/// basics, people's public profiles and their pages, the instance's register
/// (ADR 0016), SRP's register side, TOTP and recovery codes (ADR 0017), and
/// the module's models.
/// (ADR 0016), signing in with a password and TOTP and getting back into an
/// account (ADR 0017), and the module's models.
library;
export 'src/auth/account.dart';
@ -11,6 +11,7 @@ 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/github_sign_in_endpoint.dart' show gitHubAccountToSignIn;
export 'src/auth/identified.dart';
export 'src/auth/scopes.dart';
export 'src/common/short_code.dart';
@ -18,6 +19,22 @@ export 'src/generated/protocol.dart' hide Protocol;
export 'src/halkyon_base.dart';
export 'src/instance/data_location.dart';
export 'src/instance/source_code.dart';
export 'src/password/authenticators.dart'
show
forgetUnconfirmedAuthenticators,
maxAuthenticators,
unconfirmedAuthenticatorLifetime;
export 'src/password/key_proof.dart' show keyProofMessage;
export 'src/password/password_config.dart';
export 'src/password/passwords.dart'
show
newPasswordCosts,
passwordGroup,
passwordMethod,
resetDelay,
signInChallengeLifetime,
signsInWithPassword,
wrappedKeysLength;
export 'src/profile/link_verification.dart';
export 'src/profile/profile_page.dart' show addProfilePageRoute, profilePage;
export 'src/profile/profile_rules.dart'

View file

@ -12,8 +12,9 @@ import 'package:serverpod_auth_idp_server/core.dart';
/// this order: their sessions end first, since a device with a valid session
/// would upload everything again; then the host deletes their data in its
/// own tables ([HalkyonBase.deletePersonData], in the order given); then
/// their public profile with its avatar, their address's hash and the person
/// with their other identities. On the instance's register, an account (an
/// their public profile with its avatar, their address's hash, their
/// password with their keys, authenticators and recovery codes, and the
/// person with their other identities. On the instance's register, an account (an
/// identified person) going leaves a notice for each member in the same
/// transaction, and the members are told once it holds (ADR 0016). Once the
/// transaction holds, what the host's deletions left for afterwards runs.
@ -77,6 +78,38 @@ Future<void> deletePerson(Session session, UuidValue authUserId) async {
where: (t) => t.authUserId.equals(authUserId),
transaction: transaction,
);
// Their password with their keys, authenticators, recovery codes, resets
// and sign-ins begun (ADR 0017).
// DELETE FROM "account_password" WHERE "authUserId" = $1
await AccountPassword.db.deleteWhere(
session,
where: (t) => t.authUserId.equals(authUserId),
transaction: transaction,
);
// DELETE FROM "totp_authenticator" WHERE "authUserId" = $1
await TotpAuthenticator.db.deleteWhere(
session,
where: (t) => t.authUserId.equals(authUserId),
transaction: transaction,
);
// DELETE FROM "recovery_code" WHERE "authUserId" = $1
await RecoveryCode.db.deleteWhere(
session,
where: (t) => t.authUserId.equals(authUserId),
transaction: transaction,
);
// DELETE FROM "account_reset" WHERE "authUserId" = $1
await AccountReset.db.deleteWhere(
session,
where: (t) => t.authUserId.equals(authUserId),
transaction: transaction,
);
// DELETE FROM "sign_in_challenge" WHERE "authUserId" = $1
await SignInChallenge.db.deleteWhere(
session,
where: (t) => t.authUserId.equals(authUserId),
transaction: transaction,
);
// Their GitHub id and anonymous account go with them (ON DELETE CASCADE).
// Gone already on a member the register told first (ADR 0016).
// DELETE FROM "serverpod_auth_core_user" WHERE "id" = $1
@ -133,8 +166,9 @@ Future<int> pruneAbandonedAnonymousPeople(
/// rate limit looks back ([anonymousSignUpLimit]) at [now], and returns how
/// many: each holds the IP address it came from, which the instance keeps no
/// longer than the limit needs it. Serverpod itself deletes them only for its
/// own email sign-in, which Halkyon servers don't use. Email sign-in's notes
/// go too: none of its limits looks back further.
/// own email sign-in, which Halkyon servers don't use. Email sign-in's and
/// password sign-in's notes go too: none of their limits looks back
/// further.
Future<int> forgetOldSignUpAttempts(
Session session, {
required DateTime now,

View file

@ -1,3 +1,4 @@
import 'package:halkyon_base_server/src/generated/protocol.dart';
import 'package:mailer/mailer.dart' as mailer;
import 'package:mailer/smtp_server.dart';
import 'package:serverpod/serverpod.dart';
@ -5,9 +6,10 @@ import 'package:serverpod/serverpod.dart';
// The mail that carries an email sign-in code (Halkyon Learn's ADR 0010):
// plain text, the code in its subject so it shows in the notification and
// the system can offer it, no links, no tracking. Sent over SMTP, through any
// service the operator chooses (Proton's, for one).
// service the operator chooses (Proton's, for one). The same service tells
// an account's address of a reset asked for (ADR 0017).
/// Mails sign-in codes.
/// Mails sign-in codes, and notices of resets.
abstract interface class CodeMailer {
/// Mails [code] to [to], worded in [language] (`en` or `de`).
Future<void> send(
@ -16,6 +18,17 @@ abstract interface class CodeMailer {
required String code,
required String language,
});
/// Tells [to], worded in [language], that a reset of the account's [kind]
/// was asked for, taking effect at [takesEffectAt] unless its owner signs
/// in before ([resetMail]).
Future<void> sendResetNotice(
Session session, {
required String to,
required AccountResetKind kind,
required DateTime takesEffectAt,
required String language,
});
}
/// Writes codes into the server's log, for development, where nobody has a
@ -31,6 +44,17 @@ final class LogCodeMailer implements CodeMailer {
required String code,
required String language,
}) async => session.log('Email sign-in code for $to: $code');
@override
Future<void> sendResetNotice(
Session session, {
required String to,
required AccountResetKind kind,
required DateTime takesEffectAt,
required String language,
}) async => session.log(
'Reset notice for $to: ${kind.name}, ${_utc(takesEffectAt)}',
);
}
/// A code's mail, worded in [language] (`en`, else German for `de`): its
@ -74,6 +98,81 @@ final class LogCodeMailer implements CodeMailer {
),
};
/// A reset's notice (ADR 0017), worded in [language] (`en`, else German for
/// `de`): its subject and its body. [kind] says what goes at [takesEffectAt]
/// (shown in UTC), [app] and [instance] as in [codeMail]. It names no code
/// and no link: whoever asked must not be helped by it, and the owner needs
/// none to call it off, only to sign in.
({String subject, String body}) resetMail({
required AccountResetKind kind,
required DateTime takesEffectAt,
required String language,
required String app,
required String instance,
}) {
final when = _utc(takesEffectAt);
return switch ((language, kind)) {
('de', AccountResetKind.authenticator) => (
subject: 'Die Anmeldecodes deines $app-Kontos werden abgeschaltet',
body:
'Jemand hat mit deinem Passwort angefragt, die Codes deiner '
'Authenticator-App für dein Konto auf $instance abzuschalten. Das '
'geschieht am $when, außer du meldest dich vorher mit deinem '
'Passwort und einem Code deiner Authenticator-App an: Das bricht es '
'ab.\n'
'\n'
'Warst du es, meldest du dich danach mit deinem Passwort allein an '
'und richtest wieder eine Authenticator-App ein.\n'
'\n'
'Warst du es nicht, melde dich jetzt an: Wer gefragt hat, kennt dein '
'Passwort.\n',
),
('de', AccountResetKind.account) => (
subject: 'Dein $app-Konto wird zurückgesetzt',
body:
'Jemand hat mit einem Code an diese Adresse angefragt, dein Konto '
'auf $instance zurückzusetzen: ein neues Passwort und neue '
'Schlüssel. Was mit den alten Schlüsseln verschlüsselt ist, kann '
'danach niemand mehr lesen. Das geschieht frühestens am $when, außer '
'du meldest dich vorher mit deinem Passwort an: Das bricht es ab.\n'
'\n'
'Warst du es nicht, melde dich jetzt an.\n',
),
(_, AccountResetKind.authenticator) => (
subject: 'The sign-in codes of your $app account will be turned off',
body:
'Someone asked, with your password, to turn off the codes from your '
'authenticator app for your account on $instance. This happens on '
'$when, unless you sign in with your password and a code from your '
'authenticator app before then: that calls it off.\n'
'\n'
'If it was you, sign in with your password alone after that, and '
'set up an authenticator app again.\n'
'\n'
"If it wasn't you, sign in now: whoever asked knows your password.\n",
),
(_, AccountResetKind.account) => (
subject: 'Your $app account will be reset',
body:
'Someone asked, with a code mailed to this address, to reset your '
'account on $instance: a new password and new keys. What was '
'encrypted with the old keys can no longer be read by anyone. This '
'happens on $when at the earliest, unless you sign in with your '
'password before then: that calls it off.\n'
'\n'
"If it wasn't you, sign in now.\n",
),
};
}
/// [time] as a reset's notice names it: to the minute, in UTC.
String _utc(DateTime time) {
final utc = time.toUtc();
String two(int value) => value.toString().padLeft(2, '0');
return '${utc.year}-${two(utc.month)}-${two(utc.day)} '
'${two(utc.hour)}:${two(utc.minute)} UTC';
}
/// Mails codes through an SMTP service, over TLS only: directly on port
/// 465, upgraded with STARTTLS elsewhere (Proton's: `smtp.protonmail.ch`,
/// 587). It logs nothing of a mail, neither its address nor its code.
@ -134,19 +233,39 @@ final class SmtpCodeMailer implements CodeMailer {
required String language,
}) => mail(to: to, code: code, language: language);
@override
Future<void> sendResetNotice(
Session session, {
required String to,
required AccountResetKind kind,
required DateTime takesEffectAt,
required String language,
}) => _deliver(
to,
resetMail(
kind: kind,
takesEffectAt: takesEffectAt,
language: language,
app: _app,
instance: _instance,
),
);
/// Mails [code] to [to], worded in [language]; throws what the service
/// answered when it took no mail.
Future<void> mail({
required String to,
required String code,
required String language,
}) async {
final mail = codeMail(
code: code,
language: language,
app: _app,
instance: _instance,
);
}) => _deliver(
to,
codeMail(code: code, language: language, app: _app, instance: _instance),
);
Future<void> _deliver(
String to,
({String subject, String body}) mail,
) async {
await mailer.send(
mailer.Message()
..from = _from

View file

@ -7,6 +7,7 @@ import 'package:halkyon_base_server/src/auth/identified.dart';
import 'package:halkyon_base_server/src/common/short_code.dart';
import 'package:halkyon_base_server/src/generated/protocol.dart';
import 'package:halkyon_base_server/src/halkyon_base.dart';
import 'package:halkyon_base_server/src/password/passwords.dart';
import 'package:halkyon_base_server/src/register/people.dart';
import 'package:serverpod/serverpod.dart';
import 'package:serverpod_auth_idp_server/core.dart';
@ -197,11 +198,13 @@ Future<UuidValue> sendSignInCode(
/// have; a member's person becomes an account of the register under their
/// ID, ADR 0016); their own changes nothing; another person's changes nothing
/// here and gives a token that signs in as that person
/// ([completeEmailSwitch]).
/// ([completeEmailSwitch]), unless that person signs in with a password
/// ([signsInWithPassword], ADR 0017).
///
/// Refused for a wrong code, too many tries, a code no longer valid, or a
/// second address for someone who has one ([EmailSignInRefusedException]);
/// nothing changes then.
/// Refused for a wrong code, too many tries, a code no longer valid, a
/// second address for someone who has one, or another person's address who
/// signs in with a password ([EmailSignInRefusal.usePassword])
/// ([EmailSignInRefusedException]); nothing changes then.
Future<EmailCodeAccepted> acceptSignInCode(
Session session, {
required UuidValue authUserId,
@ -239,6 +242,15 @@ Future<EmailCodeAccepted> acceptSignInCode(
.where((identity) => identity.addressHash == accepted.addressHash)
.firstOrNull;
if (owner != null && owner.authUserId != authUserId) {
if (await signsInWithPassword(
session,
owner.authUserId,
transaction: transaction,
)) {
throw EmailSignInRefusedException(
reason: EmailSignInRefusal.usePassword,
);
}
// Kept until the token is used, or its time is up.
return EmailCodeAccepted(switchToken: switchToken);
}
@ -274,11 +286,45 @@ Future<EmailCodeAccepted> acceptSignInCode(
});
}
/// The keyed hash of the address [code] proves for [request], once: the
/// request goes as it is accepted. For what an address proves without
/// becoming anyone's way in: resets of an account (ADR 0017), whose register
/// compares the hash with the account's. Refusals as [acceptSignInCode]'s
/// ([EmailSignInRefusedException]).
Future<String> proveAddressCode(
Session session, {
required UuidValue request,
required String code,
}) async {
final pod = session.serverpod;
if (!emailSignInAvailable(pod)) {
throw EmailSignInRefusedException(reason: EmailSignInRefusal.unavailable);
}
return await session.db.transaction((transaction) async {
await _refusingAsEmail(
() => _challengesOf(pod).verifyChallenge(
session,
requestId: request,
verificationCode: normalizeShortCode(code),
transaction: transaction,
),
);
final proven = (await _requestById(
session,
request,
transaction: transaction,
))!;
await _forget(session, [proven], transaction: transaction);
return proven.addressHash;
});
}
/// The person whose address a code proved, from the token
/// [acceptSignInCode] gave; once only. A session for them is the caller's to
/// issue (`issueSession`): this server's own, or a member's (ADR 0016).
/// Refused once the token was used or its time is up, or for a token it
/// never gave ([EmailSignInRefusal.codeExpired]).
/// never gave ([EmailSignInRefusal.codeExpired]), and for a person who signs
/// in with a password since ([EmailSignInRefusal.usePassword]).
Future<UuidValue> completeEmailSwitch(
Session session, {
required String switchToken,
@ -308,6 +354,13 @@ Future<UuidValue> completeEmailSwitch(
reason: EmailSignInRefusal.codeExpired,
);
}
if (await signsInWithPassword(
session,
identity.authUserId,
transaction: transaction,
)) {
throw EmailSignInRefusedException(reason: EmailSignInRefusal.usePassword);
}
return identity.authUserId;
});
}

View file

@ -22,3 +22,6 @@ values:
- otherAddressLinked
### The mail couldn't be sent; nothing was kept of the request.
- mailFailed
### The account signs in with its password and an authenticator (ADR
### 0017): a code by mail no longer signs into it.
- usePassword

View file

@ -1,5 +1,9 @@
import 'dart:io';
import 'package:clock/clock.dart';
import 'package:halkyon_base_server/src/auth/identified.dart';
import 'package:halkyon_base_server/src/generated/protocol.dart';
import 'package:halkyon_base_server/src/password/password_config.dart';
import 'package:halkyon_base_server/src/register/people.dart';
import 'package:serverpod/serverpod.dart';
import 'package:serverpod_auth_idp_server/core.dart';
@ -22,7 +26,70 @@ bool gitHubSignInAvailable(Serverpod pod) =>
(pod.getPassword('githubClientId') ?? '').isNotEmpty &&
(pod.getPassword('githubClientSecret') ?? '').isNotEmpty;
/// When GitHub stops signing anyone in here (ADR 0017, D2: three months
/// after the release that brings passwords): the operator's
/// `GITHUB_SIGN_IN_ENDS`, a day (`2027-01-22`) from its start in UTC; null
/// while unset. From then on GitHub signs nobody in and links nothing, the
/// instance offers it no more, and pruning forgets every GitHub ID
/// ([forgetGitHubAccounts]). Throws a [StateError] for a value that is no
/// such day, which hosts check as they start (`addInstanceRoutes`).
DateTime? gitHubSignInEnds(Serverpod pod) =>
_endsOf[pod] ??
gitHubSignInEndsFrom(Platform.environment['GITHUB_SIGN_IN_ENDS']);
/// [value] (`GITHUB_SIGN_IN_ENDS`) as the moment GitHub sign-in ends: the
/// day's start in UTC; null for none (unset or empty, as Compose passes an
/// unset variable). Throws a [StateError] for anything but a day.
DateTime? gitHubSignInEndsFrom(String? value) {
final day = value?.trim() ?? '';
if (day.isEmpty) {
return null;
}
final parsed = RegExp(r'^\d{4}-\d{2}-\d{2}$').hasMatch(day)
? DateTime.tryParse('${day}T00:00:00Z')
: null;
if (parsed == null || parsed.toIso8601String().substring(0, 10) != day) {
throw StateError(
'GITHUB_SIGN_IN_ENDS is "$day": it must be a day, such as 2027-01-22.',
);
}
return parsed;
}
/// Whether GitHub sign-in has ended on [pod] ([gitHubSignInEnds]).
bool gitHubSignInEnded(Serverpod pod) => switch (gitHubSignInEnds(pod)) {
final ends? => !clock.now().isBefore(ends),
null => false,
};
/// Puts [ends] in place of the operator's `GITHUB_SIGN_IN_ENDS` on [pod], or
/// with null theirs back: tests do.
void useGitHubSignInEnds(Serverpod pod, DateTime? ends) => _endsOf[pod] = ends;
final _endsOf = Expando<DateTime>('GitHub sign-in ends');
/// Deletes every GitHub ID the instance keeps once GitHub sign-in has ended
/// at [now] ([gitHubSignInEnds]), and returns how many: nobody signs in with
/// them any more, so nothing is kept of them. Daily, with the pruning; on the
/// register, which keeps them (ADR 0016).
Future<int> forgetGitHubAccounts(
Session session, {
required DateTime now,
}) async {
final ends = gitHubSignInEnds(session.serverpod);
if (ends == null || now.isBefore(ends)) {
return 0;
}
// DELETE FROM "serverpod_auth_idp_github_account"
final forgotten = await GitHubAccount.db.deleteWhere(
session,
where: (t) => t.id.notEquals(null),
);
return forgotten.length;
}
/// The numeric GitHub ID that [code], from the app's GitHub sign-in, proves.
/// Refused once GitHub sign-in has ended ([GitHubSignInRefusal.ended]).
///
/// The code is exchanged here, with the client secret, which never leaves the
/// server. GitHub's answer also carries the name and possibly an email
@ -33,6 +100,9 @@ Future<String> verifiedGitHubUserId(
required String codeVerifier,
required String redirectUri,
}) async {
if (gitHubSignInEnded(session.serverpod)) {
throw GitHubSignInRefusedException(reason: GitHubSignInRefusal.ended);
}
if (!gitHubSignInAvailable(session.serverpod)) {
throw GitHubSignInRefusedException(
reason: GitHubSignInRefusal.unavailable,
@ -61,7 +131,9 @@ Future<String> verifiedGitHubUserId(
///
/// Linking the account the person already has changes nothing. Refused, with
/// nothing changed, when the account belongs to someone else or the person
/// has another GitHub account ([GitHubSignInRefusedException]).
/// has another GitHub account, and wherever people sign in with a password
/// (ADR 0017: GitHub is deprecated, [GitHubSignInRefusal.deprecated])
/// ([GitHubSignInRefusedException]).
Future<void> linkGitHubAccount(
Session session, {
required UuidValue authUserId,
@ -82,6 +154,11 @@ Future<void> linkGitHubAccount(
)) {
return;
}
if (passwordSignInAvailable(session.serverpod)) {
throw GitHubSignInRefusedException(
reason: GitHubSignInRefusal.deprecated,
);
}
if (accounts.any((account) => account.userIdentifier == gitHubUserId)) {
throw GitHubSignInRefusedException(
reason: GitHubSignInRefusal.takenByAnotherPerson,

View file

@ -9,9 +9,8 @@ import 'package:serverpod_auth_idp_server/core.dart';
/// to it on another device. Called without a session: Serverpod issues no
/// session for another person inside a signed-in call.
class GitHubSignInEndpoint extends Endpoint {
/// A session for the person with the GitHub account that [code] proves,
/// whom the instance's register knows by it; an account it doesn't know
/// is refused ([GitHubSignInRefusal.unknownAccount]).
/// A session for the person with the GitHub account that [code] proves;
/// refusals as [gitHubAccountToSignIn]'s.
Future<AuthSuccess> signIn(
Session session, {
required String code,
@ -24,13 +23,33 @@ class GitHubSignInEndpoint extends Endpoint {
codeVerifier: codeVerifier,
redirectUri: redirectUri,
);
final account =
await AccountRegister.of(
session.serverpod,
).accountWithGitHub(session, gitHubUserId) ??
(throw GitHubSignInRefusedException(
reason: GitHubSignInRefusal.unknownAccount,
));
final account = await gitHubAccountToSignIn(session, gitHubUserId);
return await issueSession(session, account, method: gitHubMethod);
}
}
/// The account the GitHub account [gitHubUserId] signs into, as the
/// instance's register knows it; an account it doesn't know is refused
/// ([GitHubSignInRefusal.unknownAccount]), and so is one that signs in with
/// a password ([GitHubSignInRefusal.usePassword], ADR 0017). GitHub is
/// deprecated: a person signed in so without a password sets an address, a
/// password and an authenticator next, as the app reads from their ways in
/// (`account.signInWays`).
Future<UuidValue> gitHubAccountToSignIn(
Session session,
String gitHubUserId,
) async {
final register = AccountRegister.of(session.serverpod);
final account =
await register.accountWithGitHub(session, gitHubUserId) ??
(throw GitHubSignInRefusedException(
reason: GitHubSignInRefusal.unknownAccount,
));
final ways = await register.signInWaysOf(session, account);
if (ways.password && ways.authenticator) {
throw GitHubSignInRefusedException(
reason: GitHubSignInRefusal.usePassword,
);
}
return account;
}

View file

@ -11,3 +11,12 @@ values:
- otherGitHubAccountLinked
### No person has this GitHub account; only linking one creates it.
- unknownAccount
### The account signs in with its password: GitHub is deprecated (ADR 0017),
### and can't unlock the account's keys.
- usePassword
### GitHub accounts are no longer linked (ADR 0017): an address and a
### password are the way in.
- deprecated
### GitHub signs nobody in any more here: its time ended
### (`GITHUB_SIGN_IN_ENDS`).
- ended

View file

@ -3,3 +3,7 @@ class: SignInWays
fields:
gitHub: bool
email: bool
### A password (ADR 0017), which an address needs for resets.
password: bool, default=false
### A confirmed authenticator: the password signs in only with one.
authenticator: bool, default=false

View file

@ -40,7 +40,11 @@ enum EmailSignInRefusal implements _is.SerializableModel {
otherAddressLinked,
/// The mail couldn't be sent; nothing was kept of the request.
mailFailed;
mailFailed,
/// The account signs in with its password and an authenticator (ADR
/// 0017): a code by mail no longer signs into it.
usePassword;
static EmailSignInRefusal fromJson(String name) {
switch (name) {
@ -60,6 +64,8 @@ enum EmailSignInRefusal implements _is.SerializableModel {
return EmailSignInRefusal.otherAddressLinked;
case 'mailFailed':
return EmailSignInRefusal.mailFailed;
case 'usePassword':
return EmailSignInRefusal.usePassword;
default:
throw ArgumentError(
'Value "$name" cannot be converted to "EmailSignInRefusal"',

View file

@ -25,7 +25,19 @@ enum GitHubSignInRefusal implements _is.SerializableModel {
otherGitHubAccountLinked,
/// No person has this GitHub account; only linking one creates it.
unknownAccount;
unknownAccount,
/// The account signs in with its password: GitHub is deprecated (ADR 0017),
/// and can't unlock the account's keys.
usePassword,
/// GitHub accounts are no longer linked (ADR 0017): an address and a
/// password are the way in.
deprecated,
/// GitHub signs nobody in any more here: its time ended
/// (`GITHUB_SIGN_IN_ENDS`).
ended;
static GitHubSignInRefusal fromJson(String name) {
switch (name) {
@ -37,6 +49,12 @@ enum GitHubSignInRefusal implements _is.SerializableModel {
return GitHubSignInRefusal.otherGitHubAccountLinked;
case 'unknownAccount':
return GitHubSignInRefusal.unknownAccount;
case 'usePassword':
return GitHubSignInRefusal.usePassword;
case 'deprecated':
return GitHubSignInRefusal.deprecated;
case 'ended':
return GitHubSignInRefusal.ended;
default:
throw ArgumentError(
'Value "$name" cannot be converted to "GitHubSignInRefusal"',

View file

@ -18,17 +18,28 @@ abstract class SignInWays
SignInWays._({
required this.gitHub,
required this.email,
});
bool? password,
bool? authenticator,
}) : password = password ?? false,
authenticator = authenticator ?? false;
factory SignInWays({
required bool gitHub,
required bool email,
bool? password,
bool? authenticator,
}) = _SignInWaysImpl;
factory SignInWays.fromJson(Map<String, dynamic> jsonSerialization) {
return SignInWays(
gitHub: _is.BoolJsonExtension.fromJson(jsonSerialization['gitHub']),
email: _is.BoolJsonExtension.fromJson(jsonSerialization['email']),
password: jsonSerialization['password'] == null
? null
: _is.BoolJsonExtension.fromJson(jsonSerialization['password']),
authenticator: jsonSerialization['authenticator'] == null
? null
: _is.BoolJsonExtension.fromJson(jsonSerialization['authenticator']),
);
}
@ -36,12 +47,20 @@ abstract class SignInWays
bool email;
/// A password (ADR 0017), which an address needs for resets.
bool password;
/// A confirmed authenticator: the password signs in only with one.
bool authenticator;
/// Returns a shallow copy of this [SignInWays]
/// with some or all fields replaced by the given arguments.
@_is.useResult
SignInWays copyWith({
bool? gitHub,
bool? email,
bool? password,
bool? authenticator,
});
@override
Map<String, dynamic> toJson() {
@ -49,6 +68,8 @@ abstract class SignInWays
'__className__': 'halkyon_base.SignInWays',
'gitHub': gitHub,
'email': email,
'password': password,
'authenticator': authenticator,
};
}
@ -58,6 +79,8 @@ abstract class SignInWays
'__className__': 'halkyon_base.SignInWays',
'gitHub': gitHub,
'email': email,
'password': password,
'authenticator': authenticator,
};
}
@ -71,9 +94,13 @@ class _SignInWaysImpl extends SignInWays {
_SignInWaysImpl({
required bool gitHub,
required bool email,
bool? password,
bool? authenticator,
}) : super._(
gitHub: gitHub,
email: email,
password: password,
authenticator: authenticator,
);
/// Returns a shallow copy of this [SignInWays]
@ -83,10 +110,14 @@ class _SignInWaysImpl extends SignInWays {
SignInWays copyWith({
bool? gitHub,
bool? email,
bool? password,
bool? authenticator,
}) {
return SignInWays(
gitHub: gitHub ?? this.gitHub,
email: email ?? this.email,
password: password ?? this.password,
authenticator: authenticator ?? this.authenticator,
);
}
}

View file

@ -11,6 +11,10 @@
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'dart:typed_data' as _idt;
import 'package:halkyon_base_server/src/generated/password/new_password.dart'
as _ijye8mcv;
import 'package:halkyon_base_server/src/generated/password/password_setup.dart'
as _ikhyee47;
import 'package:serverpod/serverpod.dart' as _is;
import 'package:serverpod_auth_core_server/serverpod_auth_core_server.dart'
as _iacs;
@ -23,6 +27,10 @@ import '../auth/email_sign_in_endpoint.dart' as _irqdog5c;
import '../auth/github_sign_in_endpoint.dart' as _iasw79m5;
import '../auth/github_upgrade_endpoint.dart' as _ivnugbf2;
import '../instance/instance_endpoint.dart' as _i3g8bfc2;
import '../password/account_recovery_endpoint.dart' as _ivnbjhab;
import '../password/authenticator_endpoint.dart' as _i3xqawxj;
import '../password/password_endpoint.dart' as _ig7fyg1b;
import '../password/password_sign_in_endpoint.dart' as _ik13m562;
import '../profile/profile_endpoint.dart' as _i6ky944g;
class Endpoints extends _is.EndpointDispatch {
@ -71,6 +79,30 @@ class Endpoints extends _is.EndpointDispatch {
'instance',
'halkyon_base',
),
'accountRecovery': _ivnbjhab.AccountRecoveryEndpoint()
..initialize(
server,
'accountRecovery',
'halkyon_base',
),
'authenticator': _i3xqawxj.AuthenticatorEndpoint()
..initialize(
server,
'authenticator',
'halkyon_base',
),
'password': _ig7fyg1b.PasswordEndpoint()
..initialize(
server,
'password',
'halkyon_base',
),
'passwordSignIn': _ik13m562.PasswordSignInEndpoint()
..initialize(
server,
'passwordSignIn',
'halkyon_base',
),
'profile': _i6ky944g.ProfileEndpoint()
..initialize(
server,
@ -311,6 +343,481 @@ class Endpoints extends _is.EndpointDispatch {
),
},
);
connectors['accountRecovery'] = _is.EndpointConnector(
name: 'accountRecovery',
endpoint: endpoints['accountRecovery']!,
methodConnectors: {
'sendCode': _is.MethodConnector(
name: 'sendCode',
params: {
'address': _is.ParameterDescription(
name: 'address',
type: _is.getType<String>(),
nullable: false,
),
'language': _is.ParameterDescription(
name: 'language',
type: _is.getType<String>(),
nullable: false,
),
},
call:
(
_is.Session session,
Map<String, dynamic> params,
) async =>
(endpoints['accountRecovery']
as _ivnbjhab.AccountRecoveryEndpoint)
.sendCode(
session,
address: params['address'],
language: params['language'],
),
),
'resetAuthenticator': _is.MethodConnector(
name: 'resetAuthenticator',
params: {
'challenge': _is.ParameterDescription(
name: 'challenge',
type: _is.getType<_is.UuidValue>(),
nullable: false,
),
'clientValue': _is.ParameterDescription(
name: 'clientValue',
type: _is.getType<_idt.ByteData>(),
nullable: false,
),
'clientProof': _is.ParameterDescription(
name: 'clientProof',
type: _is.getType<_idt.ByteData>(),
nullable: false,
),
'address': _is.ParameterDescription(
name: 'address',
type: _is.getType<String>(),
nullable: false,
),
'language': _is.ParameterDescription(
name: 'language',
type: _is.getType<String>(),
nullable: false,
),
'request': _is.ParameterDescription(
name: 'request',
type: _is.getType<_is.UuidValue?>(),
nullable: true,
),
'code': _is.ParameterDescription(
name: 'code',
type: _is.getType<String?>(),
nullable: true,
),
'keyChallenge': _is.ParameterDescription(
name: 'keyChallenge',
type: _is.getType<_is.UuidValue?>(),
nullable: true,
),
'signature': _is.ParameterDescription(
name: 'signature',
type: _is.getType<_idt.ByteData?>(),
nullable: true,
),
},
call:
(
_is.Session session,
Map<String, dynamic> params,
) async =>
(endpoints['accountRecovery']
as _ivnbjhab.AccountRecoveryEndpoint)
.resetAuthenticator(
session,
challenge: params['challenge'],
clientValue: params['clientValue'],
clientProof: params['clientProof'],
address: params['address'],
language: params['language'],
request: params['request'],
code: params['code'],
keyChallenge: params['keyChallenge'],
signature: params['signature'],
),
),
'beginKeyProof': _is.MethodConnector(
name: 'beginKeyProof',
params: {
'address': _is.ParameterDescription(
name: 'address',
type: _is.getType<String>(),
nullable: false,
),
},
call:
(
_is.Session session,
Map<String, dynamic> params,
) async =>
(endpoints['accountRecovery']
as _ivnbjhab.AccountRecoveryEndpoint)
.beginKeyProof(
session,
address: params['address'],
),
),
'resetPassword': _is.MethodConnector(
name: 'resetPassword',
params: {
'challenge': _is.ParameterDescription(
name: 'challenge',
type: _is.getType<_is.UuidValue>(),
nullable: false,
),
'signature': _is.ParameterDescription(
name: 'signature',
type: _is.getType<_idt.ByteData>(),
nullable: false,
),
'password': _is.ParameterDescription(
name: 'password',
type: _is.getType<_ijye8mcv.NewPassword>(),
nullable: false,
),
'totpCode': _is.ParameterDescription(
name: 'totpCode',
type: _is.getType<String?>(),
nullable: true,
),
'recoveryCode': _is.ParameterDescription(
name: 'recoveryCode',
type: _is.getType<String?>(),
nullable: true,
),
},
call:
(
_is.Session session,
Map<String, dynamic> params,
) async =>
(endpoints['accountRecovery']
as _ivnbjhab.AccountRecoveryEndpoint)
.resetPassword(
session,
challenge: params['challenge'],
signature: params['signature'],
password: params['password'],
totpCode: params['totpCode'],
recoveryCode: params['recoveryCode'],
),
),
'startAccountReset': _is.MethodConnector(
name: 'startAccountReset',
params: {
'address': _is.ParameterDescription(
name: 'address',
type: _is.getType<String>(),
nullable: false,
),
'language': _is.ParameterDescription(
name: 'language',
type: _is.getType<String>(),
nullable: false,
),
'request': _is.ParameterDescription(
name: 'request',
type: _is.getType<_is.UuidValue>(),
nullable: false,
),
'code': _is.ParameterDescription(
name: 'code',
type: _is.getType<String>(),
nullable: false,
),
},
call:
(
_is.Session session,
Map<String, dynamic> params,
) async =>
(endpoints['accountRecovery']
as _ivnbjhab.AccountRecoveryEndpoint)
.startAccountReset(
session,
address: params['address'],
language: params['language'],
request: params['request'],
code: params['code'],
),
),
'completeAccountReset': _is.MethodConnector(
name: 'completeAccountReset',
params: {
'address': _is.ParameterDescription(
name: 'address',
type: _is.getType<String>(),
nullable: false,
),
'request': _is.ParameterDescription(
name: 'request',
type: _is.getType<_is.UuidValue>(),
nullable: false,
),
'code': _is.ParameterDescription(
name: 'code',
type: _is.getType<String>(),
nullable: false,
),
'setup': _is.ParameterDescription(
name: 'setup',
type: _is.getType<_ikhyee47.PasswordSetup>(),
nullable: false,
),
},
call:
(
_is.Session session,
Map<String, dynamic> params,
) async =>
(endpoints['accountRecovery']
as _ivnbjhab.AccountRecoveryEndpoint)
.completeAccountReset(
session,
address: params['address'],
request: params['request'],
code: params['code'],
setup: params['setup'],
),
),
},
);
connectors['authenticator'] = _is.EndpointConnector(
name: 'authenticator',
endpoint: endpoints['authenticator']!,
methodConnectors: {
'enrol': _is.MethodConnector(
name: 'enrol',
params: {
'name': _is.ParameterDescription(
name: 'name',
type: _is.getType<String>(),
nullable: false,
),
'label': _is.ParameterDescription(
name: 'label',
type: _is.getType<String>(),
nullable: false,
),
},
call:
(
_is.Session session,
Map<String, dynamic> params,
) async =>
(endpoints['authenticator']
as _i3xqawxj.AuthenticatorEndpoint)
.enrol(
session,
name: params['name'],
label: params['label'],
),
),
'confirm': _is.MethodConnector(
name: 'confirm',
params: {
'authenticator': _is.ParameterDescription(
name: 'authenticator',
type: _is.getType<_is.UuidValue>(),
nullable: false,
),
'code': _is.ParameterDescription(
name: 'code',
type: _is.getType<String>(),
nullable: false,
),
},
call:
(
_is.Session session,
Map<String, dynamic> params,
) async =>
(endpoints['authenticator']
as _i3xqawxj.AuthenticatorEndpoint)
.confirm(
session,
authenticator: params['authenticator'],
code: params['code'],
),
),
'list': _is.MethodConnector(
name: 'list',
params: {},
call:
(
_is.Session session,
Map<String, dynamic> params,
) async =>
(endpoints['authenticator']
as _i3xqawxj.AuthenticatorEndpoint)
.list(session),
),
'remove': _is.MethodConnector(
name: 'remove',
params: {
'authenticator': _is.ParameterDescription(
name: 'authenticator',
type: _is.getType<_is.UuidValue>(),
nullable: false,
),
'code': _is.ParameterDescription(
name: 'code',
type: _is.getType<String?>(),
nullable: true,
),
},
call:
(
_is.Session session,
Map<String, dynamic> params,
) async =>
(endpoints['authenticator']
as _i3xqawxj.AuthenticatorEndpoint)
.remove(
session,
authenticator: params['authenticator'],
code: params['code'],
),
),
'newRecoveryCodes': _is.MethodConnector(
name: 'newRecoveryCodes',
params: {
'code': _is.ParameterDescription(
name: 'code',
type: _is.getType<String>(),
nullable: false,
),
},
call:
(
_is.Session session,
Map<String, dynamic> params,
) async =>
(endpoints['authenticator']
as _i3xqawxj.AuthenticatorEndpoint)
.newRecoveryCodes(
session,
code: params['code'],
),
),
},
);
connectors['password'] = _is.EndpointConnector(
name: 'password',
endpoint: endpoints['password']!,
methodConnectors: {
'set': _is.MethodConnector(
name: 'set',
params: {
'setup': _is.ParameterDescription(
name: 'setup',
type: _is.getType<_ikhyee47.PasswordSetup>(),
nullable: false,
),
'request': _is.ParameterDescription(
name: 'request',
type: _is.getType<_is.UuidValue?>(),
nullable: true,
),
'code': _is.ParameterDescription(
name: 'code',
type: _is.getType<String?>(),
nullable: true,
),
},
call:
(
_is.Session session,
Map<String, dynamic> params,
) async =>
(endpoints['password'] as _ig7fyg1b.PasswordEndpoint).set(
session,
setup: params['setup'],
request: params['request'],
code: params['code'],
),
),
},
);
connectors['passwordSignIn'] = _is.EndpointConnector(
name: 'passwordSignIn',
endpoint: endpoints['passwordSignIn']!,
methodConnectors: {
'begin': _is.MethodConnector(
name: 'begin',
params: {
'address': _is.ParameterDescription(
name: 'address',
type: _is.getType<String>(),
nullable: false,
),
},
call:
(
_is.Session session,
Map<String, dynamic> params,
) async =>
(endpoints['passwordSignIn']
as _ik13m562.PasswordSignInEndpoint)
.begin(
session,
address: params['address'],
),
),
'complete': _is.MethodConnector(
name: 'complete',
params: {
'challenge': _is.ParameterDescription(
name: 'challenge',
type: _is.getType<_is.UuidValue>(),
nullable: false,
),
'clientValue': _is.ParameterDescription(
name: 'clientValue',
type: _is.getType<_idt.ByteData>(),
nullable: false,
),
'clientProof': _is.ParameterDescription(
name: 'clientProof',
type: _is.getType<_idt.ByteData>(),
nullable: false,
),
'totpCode': _is.ParameterDescription(
name: 'totpCode',
type: _is.getType<String?>(),
nullable: true,
),
'recoveryCode': _is.ParameterDescription(
name: 'recoveryCode',
type: _is.getType<String?>(),
nullable: true,
),
},
call:
(
_is.Session session,
Map<String, dynamic> params,
) async =>
(endpoints['passwordSignIn']
as _ik13m562.PasswordSignInEndpoint)
.complete(
session,
challenge: params['challenge'],
clientValue: params['clientValue'],
clientProof: params['clientProof'],
totpCode: params['totpCode'],
recoveryCode: params['recoveryCode'],
),
),
},
);
connectors['profile'] = _is.EndpointConnector(
name: 'profile',
endpoint: endpoints['profile']!,

View file

@ -26,7 +26,8 @@ abstract class InstanceBasics
required this.minimumAppBuild,
this.dataLocation,
this.backupLocation,
});
bool? passwordSignIn,
}) : passwordSignIn = passwordSignIn ?? false;
factory InstanceBasics({
required String app,
@ -36,6 +37,7 @@ abstract class InstanceBasics
required int minimumAppBuild,
String? dataLocation,
String? backupLocation,
bool? passwordSignIn,
}) = _InstanceBasicsImpl;
factory InstanceBasics.fromJson(Map<String, dynamic> jsonSerialization) {
@ -49,6 +51,9 @@ abstract class InstanceBasics
minimumAppBuild: jsonSerialization['minimumAppBuild'] as int,
dataLocation: jsonSerialization['dataLocation'] as String?,
backupLocation: jsonSerialization['backupLocation'] as String?,
passwordSignIn: jsonSerialization['passwordSignIn'] == null
? null
: _is.BoolJsonExtension.fromJson(jsonSerialization['passwordSignIn']),
);
}
@ -82,6 +87,10 @@ abstract class InstanceBasics
/// `none` where it keeps none. Null where the operator says nothing.
String? backupLocation;
/// Whether people sign in with a password and TOTP here (ADR 0017): the
/// operator set the keys it needs.
bool passwordSignIn;
/// Returns a shallow copy of this [InstanceBasics]
/// with some or all fields replaced by the given arguments.
@_is.useResult
@ -93,6 +102,7 @@ abstract class InstanceBasics
int? minimumAppBuild,
String? dataLocation,
String? backupLocation,
bool? passwordSignIn,
});
@override
Map<String, dynamic> toJson() {
@ -105,6 +115,7 @@ abstract class InstanceBasics
'minimumAppBuild': minimumAppBuild,
if (dataLocation != null) 'dataLocation': dataLocation,
if (backupLocation != null) 'backupLocation': backupLocation,
'passwordSignIn': passwordSignIn,
};
}
@ -119,6 +130,7 @@ abstract class InstanceBasics
'minimumAppBuild': minimumAppBuild,
if (dataLocation != null) 'dataLocation': dataLocation,
if (backupLocation != null) 'backupLocation': backupLocation,
'passwordSignIn': passwordSignIn,
};
}
@ -139,6 +151,7 @@ class _InstanceBasicsImpl extends InstanceBasics {
required int minimumAppBuild,
String? dataLocation,
String? backupLocation,
bool? passwordSignIn,
}) : super._(
app: app,
gitHubClientId: gitHubClientId,
@ -147,6 +160,7 @@ class _InstanceBasicsImpl extends InstanceBasics {
minimumAppBuild: minimumAppBuild,
dataLocation: dataLocation,
backupLocation: backupLocation,
passwordSignIn: passwordSignIn,
);
/// Returns a shallow copy of this [InstanceBasics]
@ -161,6 +175,7 @@ class _InstanceBasicsImpl extends InstanceBasics {
int? minimumAppBuild,
Object? dataLocation = _Undefined,
Object? backupLocation = _Undefined,
bool? passwordSignIn,
}) {
return InstanceBasics(
app: app ?? this.app,
@ -174,6 +189,7 @@ class _InstanceBasicsImpl extends InstanceBasics {
backupLocation: backupLocation is String?
? backupLocation
: this.backupLocation,
passwordSignIn: passwordSignIn ?? this.passwordSignIn,
);
}
}

View file

@ -0,0 +1,946 @@
/* 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 'dart:typed_data' as _idt;
import 'package:serverpod/serverpod.dart' as _is;
/// An account's password as the register keeps it (ADR 0017): nothing that
/// signs in or decrypts in the password's place. The salt and Argon2id's
/// costs the device derives the password's keys with, SRP's verifier
/// (v = g^x mod N, PAD to N's length), the account's public keys, and its
/// private keys wrapped under the password's key and again under the
/// recovery phrase's, which only a device can open.
abstract class AccountPassword
implements _is.TableRow<int?>, _is.ProtocolSerialization {
AccountPassword._({
this.id,
required this.authUserId,
required this.salt,
required this.kdfAlgorithm,
required this.kdfOpsLimit,
required this.kdfMemLimit,
required this.verifier,
required this.encryptionPublicKey,
required this.signingPublicKey,
required this.passwordKeysNonce,
required this.passwordKeysCipherText,
required this.recoveryKeysNonce,
required this.recoveryKeysCipherText,
required this.createdAt,
required this.updatedAt,
});
factory AccountPassword({
int? id,
required _is.UuidValue authUserId,
required _idt.ByteData salt,
required String kdfAlgorithm,
required int kdfOpsLimit,
required int kdfMemLimit,
required _idt.ByteData verifier,
required _idt.ByteData encryptionPublicKey,
required _idt.ByteData signingPublicKey,
required _idt.ByteData passwordKeysNonce,
required _idt.ByteData passwordKeysCipherText,
required _idt.ByteData recoveryKeysNonce,
required _idt.ByteData recoveryKeysCipherText,
required DateTime createdAt,
required DateTime updatedAt,
}) = _AccountPasswordImpl;
factory AccountPassword.fromJson(Map<String, dynamic> jsonSerialization) {
return AccountPassword(
id: jsonSerialization['id'] as int?,
authUserId: _is.UuidValueJsonExtension.fromJson(
jsonSerialization['authUserId'],
),
salt: _is.ByteDataJsonExtension.fromJson(jsonSerialization['salt']),
kdfAlgorithm: jsonSerialization['kdfAlgorithm'] as String,
kdfOpsLimit: jsonSerialization['kdfOpsLimit'] as int,
kdfMemLimit: jsonSerialization['kdfMemLimit'] as int,
verifier: _is.ByteDataJsonExtension.fromJson(
jsonSerialization['verifier'],
),
encryptionPublicKey: _is.ByteDataJsonExtension.fromJson(
jsonSerialization['encryptionPublicKey'],
),
signingPublicKey: _is.ByteDataJsonExtension.fromJson(
jsonSerialization['signingPublicKey'],
),
passwordKeysNonce: _is.ByteDataJsonExtension.fromJson(
jsonSerialization['passwordKeysNonce'],
),
passwordKeysCipherText: _is.ByteDataJsonExtension.fromJson(
jsonSerialization['passwordKeysCipherText'],
),
recoveryKeysNonce: _is.ByteDataJsonExtension.fromJson(
jsonSerialization['recoveryKeysNonce'],
),
recoveryKeysCipherText: _is.ByteDataJsonExtension.fromJson(
jsonSerialization['recoveryKeysCipherText'],
),
createdAt: _is.DateTimeJsonExtension.fromJson(
jsonSerialization['createdAt'],
),
updatedAt: _is.DateTimeJsonExtension.fromJson(
jsonSerialization['updatedAt'],
),
);
}
static final t = AccountPasswordTable();
static const db = AccountPasswordRepository._();
@override
int? id;
/// The account: its person's ID (deleted with them, `deletePerson`).
_is.UuidValue authUserId;
/// Argon2id's salt (16 bytes).
_idt.ByteData salt;
/// Argon2id's version as libsodium names it (`argon2id13`).
String kdfAlgorithm;
/// Argon2id's passes.
int kdfOpsLimit;
/// Argon2id's memory, in bytes.
int kdfMemLimit;
/// SRP's verifier, big-endian, as long as N.
_idt.ByteData verifier;
/// The account's X25519 public key, which data keys are sealed to.
_idt.ByteData encryptionPublicKey;
/// The account's Ed25519 public key, which its signatures check against.
_idt.ByteData signingPublicKey;
/// The private keys under the password's key: XChaCha20-Poly1305's nonce.
_idt.ByteData passwordKeysNonce;
/// …and what it sealed.
_idt.ByteData passwordKeysCipherText;
/// The private keys under the recovery phrase's key: the nonce.
_idt.ByteData recoveryKeysNonce;
/// …and what it sealed.
_idt.ByteData recoveryKeysCipherText;
DateTime createdAt;
DateTime updatedAt;
@override
_is.Table<int?> get table => t;
/// Returns a shallow copy of this [AccountPassword]
/// with some or all fields replaced by the given arguments.
@_is.useResult
AccountPassword copyWith({
int? id,
_is.UuidValue? authUserId,
_idt.ByteData? salt,
String? kdfAlgorithm,
int? kdfOpsLimit,
int? kdfMemLimit,
_idt.ByteData? verifier,
_idt.ByteData? encryptionPublicKey,
_idt.ByteData? signingPublicKey,
_idt.ByteData? passwordKeysNonce,
_idt.ByteData? passwordKeysCipherText,
_idt.ByteData? recoveryKeysNonce,
_idt.ByteData? recoveryKeysCipherText,
DateTime? createdAt,
DateTime? updatedAt,
});
@override
Map<String, dynamic> toJson() {
return {
'__className__': 'halkyon_base.AccountPassword',
if (id != null) 'id': id,
'authUserId': authUserId.toJson(),
'salt': salt.toJson(),
'kdfAlgorithm': kdfAlgorithm,
'kdfOpsLimit': kdfOpsLimit,
'kdfMemLimit': kdfMemLimit,
'verifier': verifier.toJson(),
'encryptionPublicKey': encryptionPublicKey.toJson(),
'signingPublicKey': signingPublicKey.toJson(),
'passwordKeysNonce': passwordKeysNonce.toJson(),
'passwordKeysCipherText': passwordKeysCipherText.toJson(),
'recoveryKeysNonce': recoveryKeysNonce.toJson(),
'recoveryKeysCipherText': recoveryKeysCipherText.toJson(),
'createdAt': createdAt.toJson(),
'updatedAt': updatedAt.toJson(),
};
}
@override
Map<String, dynamic> toJsonForProtocol() {
return {};
}
static AccountPasswordInclude include() {
return AccountPasswordInclude._();
}
static AccountPasswordIncludeList includeList({
_is.WhereExpressionBuilder<AccountPasswordTable>? where,
int? limit,
int? offset,
_is.OrderByBuilder<AccountPasswordTable>? orderBy,
_is.OrderByListBuilder<AccountPasswordTable>? orderByList,
AccountPasswordInclude? include,
}) {
return AccountPasswordIncludeList._(
where: where,
limit: limit,
offset: offset,
orderBy: orderBy?.call(AccountPassword.t),
orderByList: orderByList?.call(AccountPassword.t),
include: include,
);
}
@override
String toString() {
return _is.SerializationManager.encode(this);
}
}
class _Undefined {}
class _AccountPasswordImpl extends AccountPassword {
_AccountPasswordImpl({
int? id,
required _is.UuidValue authUserId,
required _idt.ByteData salt,
required String kdfAlgorithm,
required int kdfOpsLimit,
required int kdfMemLimit,
required _idt.ByteData verifier,
required _idt.ByteData encryptionPublicKey,
required _idt.ByteData signingPublicKey,
required _idt.ByteData passwordKeysNonce,
required _idt.ByteData passwordKeysCipherText,
required _idt.ByteData recoveryKeysNonce,
required _idt.ByteData recoveryKeysCipherText,
required DateTime createdAt,
required DateTime updatedAt,
}) : super._(
id: id,
authUserId: authUserId,
salt: salt,
kdfAlgorithm: kdfAlgorithm,
kdfOpsLimit: kdfOpsLimit,
kdfMemLimit: kdfMemLimit,
verifier: verifier,
encryptionPublicKey: encryptionPublicKey,
signingPublicKey: signingPublicKey,
passwordKeysNonce: passwordKeysNonce,
passwordKeysCipherText: passwordKeysCipherText,
recoveryKeysNonce: recoveryKeysNonce,
recoveryKeysCipherText: recoveryKeysCipherText,
createdAt: createdAt,
updatedAt: updatedAt,
);
/// Returns a shallow copy of this [AccountPassword]
/// with some or all fields replaced by the given arguments.
@_is.useResult
@override
AccountPassword copyWith({
Object? id = _Undefined,
_is.UuidValue? authUserId,
_idt.ByteData? salt,
String? kdfAlgorithm,
int? kdfOpsLimit,
int? kdfMemLimit,
_idt.ByteData? verifier,
_idt.ByteData? encryptionPublicKey,
_idt.ByteData? signingPublicKey,
_idt.ByteData? passwordKeysNonce,
_idt.ByteData? passwordKeysCipherText,
_idt.ByteData? recoveryKeysNonce,
_idt.ByteData? recoveryKeysCipherText,
DateTime? createdAt,
DateTime? updatedAt,
}) {
return AccountPassword(
id: id is int? ? id : this.id,
authUserId: authUserId ?? this.authUserId,
salt: salt ?? this.salt.clone(),
kdfAlgorithm: kdfAlgorithm ?? this.kdfAlgorithm,
kdfOpsLimit: kdfOpsLimit ?? this.kdfOpsLimit,
kdfMemLimit: kdfMemLimit ?? this.kdfMemLimit,
verifier: verifier ?? this.verifier.clone(),
encryptionPublicKey:
encryptionPublicKey ?? this.encryptionPublicKey.clone(),
signingPublicKey: signingPublicKey ?? this.signingPublicKey.clone(),
passwordKeysNonce: passwordKeysNonce ?? this.passwordKeysNonce.clone(),
passwordKeysCipherText:
passwordKeysCipherText ?? this.passwordKeysCipherText.clone(),
recoveryKeysNonce: recoveryKeysNonce ?? this.recoveryKeysNonce.clone(),
recoveryKeysCipherText:
recoveryKeysCipherText ?? this.recoveryKeysCipherText.clone(),
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
);
}
}
class AccountPasswordUpdateTable extends _is.UpdateTable<AccountPasswordTable> {
AccountPasswordUpdateTable(super.table);
_is.ColumnValue<_is.UuidValue, _is.UuidValue> authUserId(
_is.UuidValue value,
) => _is.ColumnValue(
table.authUserId,
value,
);
_is.ColumnValue<_idt.ByteData, _idt.ByteData> salt(_idt.ByteData value) =>
_is.ColumnValue(
table.salt,
value,
);
_is.ColumnValue<String, String> kdfAlgorithm(String value) => _is.ColumnValue(
table.kdfAlgorithm,
value,
);
_is.ColumnValue<int, int> kdfOpsLimit(int value) => _is.ColumnValue(
table.kdfOpsLimit,
value,
);
_is.ColumnValue<int, int> kdfMemLimit(int value) => _is.ColumnValue(
table.kdfMemLimit,
value,
);
_is.ColumnValue<_idt.ByteData, _idt.ByteData> verifier(_idt.ByteData value) =>
_is.ColumnValue(
table.verifier,
value,
);
_is.ColumnValue<_idt.ByteData, _idt.ByteData> encryptionPublicKey(
_idt.ByteData value,
) => _is.ColumnValue(
table.encryptionPublicKey,
value,
);
_is.ColumnValue<_idt.ByteData, _idt.ByteData> signingPublicKey(
_idt.ByteData value,
) => _is.ColumnValue(
table.signingPublicKey,
value,
);
_is.ColumnValue<_idt.ByteData, _idt.ByteData> passwordKeysNonce(
_idt.ByteData value,
) => _is.ColumnValue(
table.passwordKeysNonce,
value,
);
_is.ColumnValue<_idt.ByteData, _idt.ByteData> passwordKeysCipherText(
_idt.ByteData value,
) => _is.ColumnValue(
table.passwordKeysCipherText,
value,
);
_is.ColumnValue<_idt.ByteData, _idt.ByteData> recoveryKeysNonce(
_idt.ByteData value,
) => _is.ColumnValue(
table.recoveryKeysNonce,
value,
);
_is.ColumnValue<_idt.ByteData, _idt.ByteData> recoveryKeysCipherText(
_idt.ByteData value,
) => _is.ColumnValue(
table.recoveryKeysCipherText,
value,
);
_is.ColumnValue<DateTime, DateTime> createdAt(DateTime value) =>
_is.ColumnValue(
table.createdAt,
value,
);
_is.ColumnValue<DateTime, DateTime> updatedAt(DateTime value) =>
_is.ColumnValue(
table.updatedAt,
value,
);
}
class AccountPasswordTable extends _is.Table<int?> {
AccountPasswordTable({super.tableRelation})
: super(tableName: 'account_password') {
updateTable = AccountPasswordUpdateTable(this);
authUserId = _is.ColumnUuid(
'authUserId',
this,
);
salt = _is.ColumnByteData(
'salt',
this,
);
kdfAlgorithm = _is.ColumnString(
'kdfAlgorithm',
this,
);
kdfOpsLimit = _is.ColumnInt(
'kdfOpsLimit',
this,
);
kdfMemLimit = _is.ColumnInt(
'kdfMemLimit',
this,
);
verifier = _is.ColumnByteData(
'verifier',
this,
);
encryptionPublicKey = _is.ColumnByteData(
'encryptionPublicKey',
this,
);
signingPublicKey = _is.ColumnByteData(
'signingPublicKey',
this,
);
passwordKeysNonce = _is.ColumnByteData(
'passwordKeysNonce',
this,
);
passwordKeysCipherText = _is.ColumnByteData(
'passwordKeysCipherText',
this,
);
recoveryKeysNonce = _is.ColumnByteData(
'recoveryKeysNonce',
this,
);
recoveryKeysCipherText = _is.ColumnByteData(
'recoveryKeysCipherText',
this,
);
createdAt = _is.ColumnDateTime(
'createdAt',
this,
);
updatedAt = _is.ColumnDateTime(
'updatedAt',
this,
);
}
late final AccountPasswordUpdateTable updateTable;
/// The account: its person's ID (deleted with them, `deletePerson`).
late final _is.ColumnUuid authUserId;
/// Argon2id's salt (16 bytes).
late final _is.ColumnByteData salt;
/// Argon2id's version as libsodium names it (`argon2id13`).
late final _is.ColumnString kdfAlgorithm;
/// Argon2id's passes.
late final _is.ColumnInt kdfOpsLimit;
/// Argon2id's memory, in bytes.
late final _is.ColumnInt kdfMemLimit;
/// SRP's verifier, big-endian, as long as N.
late final _is.ColumnByteData verifier;
/// The account's X25519 public key, which data keys are sealed to.
late final _is.ColumnByteData encryptionPublicKey;
/// The account's Ed25519 public key, which its signatures check against.
late final _is.ColumnByteData signingPublicKey;
/// The private keys under the password's key: XChaCha20-Poly1305's nonce.
late final _is.ColumnByteData passwordKeysNonce;
/// …and what it sealed.
late final _is.ColumnByteData passwordKeysCipherText;
/// The private keys under the recovery phrase's key: the nonce.
late final _is.ColumnByteData recoveryKeysNonce;
/// …and what it sealed.
late final _is.ColumnByteData recoveryKeysCipherText;
late final _is.ColumnDateTime createdAt;
late final _is.ColumnDateTime updatedAt;
@override
List<_is.Column> get columns => [
id,
authUserId,
salt,
kdfAlgorithm,
kdfOpsLimit,
kdfMemLimit,
verifier,
encryptionPublicKey,
signingPublicKey,
passwordKeysNonce,
passwordKeysCipherText,
recoveryKeysNonce,
recoveryKeysCipherText,
createdAt,
updatedAt,
];
}
class AccountPasswordInclude extends _is.IncludeObject {
AccountPasswordInclude._();
@override
Map<String, _is.Include?> get includes => {};
@override
_is.Table<int?> get table => AccountPassword.t;
}
class AccountPasswordIncludeList extends _is.IncludeList {
AccountPasswordIncludeList._({
_is.WhereExpressionBuilder<AccountPasswordTable>? where,
super.limit,
super.offset,
super.orderBy,
super.orderByList,
super.include,
}) {
super.where = where?.call(AccountPassword.t);
}
@override
Map<String, _is.Include?> get includes => include?.includes ?? {};
@override
_is.Table<int?> get table => AccountPassword.t;
}
class AccountPasswordRepository {
const AccountPasswordRepository._();
/// Returns a list of [AccountPassword]s matching the given query parameters.
///
/// Use [where] to specify which items to include in the return value.
/// If none is specified, all items will be returned.
///
/// To specify the order of the items use [orderBy] or [orderByList]
/// when sorting by multiple columns.
///
/// The maximum number of items can be set by [limit]. If no limit is set,
/// all items matching the query will be returned.
///
/// [offset] defines how many items to skip, after which [limit] (or all)
/// items are read from the database.
///
/// ```dart
/// var persons = await Persons.db.find(
/// session,
/// where: (t) => t.lastName.equals('Jones'),
/// orderBy: (t) => t.firstName,
/// limit: 100,
/// );
/// ```
Future<List<AccountPassword>> find(
_is.DatabaseSession session, {
_is.WhereExpressionBuilder<AccountPasswordTable>? where,
int? limit,
int? offset,
_is.OrderByBuilder<AccountPasswordTable>? orderBy,
_is.OrderByListBuilder<AccountPasswordTable>? orderByList,
_is.Transaction? transaction,
_is.LockMode? lockMode,
_is.LockBehavior? lockBehavior,
}) async {
return session.db.find<AccountPassword>(
where: where?.call(AccountPassword.t),
orderBy: orderBy?.call(AccountPassword.t),
orderByList: orderByList?.call(AccountPassword.t),
limit: limit,
offset: offset,
transaction: transaction,
lockMode: lockMode,
lockBehavior: lockBehavior,
);
}
/// Returns the first matching [AccountPassword] matching the given query parameters.
///
/// Use [where] to specify which items to include in the return value.
/// If none is specified, all items will be returned.
///
/// To specify the order use [orderBy] or [orderByList]
/// when sorting by multiple columns.
///
/// [offset] defines how many items to skip, after which the next one will be picked.
///
/// ```dart
/// var youngestPerson = await Persons.db.findFirstRow(
/// session,
/// where: (t) => t.lastName.equals('Jones'),
/// orderBy: (t) => t.age,
/// );
/// ```
Future<AccountPassword?> findFirstRow(
_is.DatabaseSession session, {
_is.WhereExpressionBuilder<AccountPasswordTable>? where,
int? offset,
_is.OrderByBuilder<AccountPasswordTable>? orderBy,
_is.OrderByListBuilder<AccountPasswordTable>? orderByList,
_is.Transaction? transaction,
_is.LockMode? lockMode,
_is.LockBehavior? lockBehavior,
}) async {
return session.db.findFirstRow<AccountPassword>(
where: where?.call(AccountPassword.t),
orderBy: orderBy?.call(AccountPassword.t),
orderByList: orderByList?.call(AccountPassword.t),
offset: offset,
transaction: transaction,
lockMode: lockMode,
lockBehavior: lockBehavior,
);
}
/// Finds a single [AccountPassword] by its [id] or null if no such row exists.
Future<AccountPassword?> findById(
_is.DatabaseSession session,
int id, {
_is.Transaction? transaction,
_is.LockMode? lockMode,
_is.LockBehavior? lockBehavior,
}) async {
return session.db.findById<AccountPassword>(
id,
transaction: transaction,
lockMode: lockMode,
lockBehavior: lockBehavior,
);
}
/// Inserts all [AccountPassword]s in the list and returns the inserted rows.
///
/// The returned [AccountPassword]s will have their `id` fields set.
///
/// This is an atomic operation, meaning that if one of the rows fails to
/// insert, none of the rows will be inserted.
///
/// If [ignoreConflicts] is set to `true`, rows that conflict with existing
/// rows are silently skipped, and only the successfully inserted rows are
/// returned.
///
/// If [noReturn] is set to `true`, the inserted rows are not read back from
/// the database and an empty list is returned. This avoids the overhead of
/// transferring and deserializing the rows when the result is not needed.
Future<List<AccountPassword>> insert(
_is.DatabaseSession session,
List<AccountPassword> rows, {
_is.Transaction? transaction,
bool ignoreConflicts = false,
bool noReturn = false,
}) async {
return session.db.insert<AccountPassword>(
rows,
transaction: transaction,
ignoreConflicts: ignoreConflicts,
noReturn: noReturn,
);
}
/// Inserts a single [AccountPassword] and returns the inserted row.
///
/// The returned [AccountPassword] will have its `id` field set.
Future<AccountPassword> insertRow(
_is.DatabaseSession session,
AccountPassword row, {
_is.Transaction? transaction,
}) async {
return session.db.insertRow<AccountPassword>(
row,
transaction: transaction,
);
}
/// Upserts all [AccountPassword]s in the list and returns the resulting rows.
///
/// If a row conflicts on the given [conflictColumns], the existing row is
/// updated with the new values. Otherwise, a new row is inserted.
///
/// If [updateColumns] is provided, only those columns will be updated on
/// conflict. If null, all non-conflict, non-id columns are updated.
///
/// If [updateWhere] is provided, the update only applies to rows matching the
/// given expression. Conflicting rows that don't match are skipped and not
/// returned, so the resulting list may be shorter than [rows].
///
/// The returned [AccountPassword]s will have their `id` fields set.
///
/// This is an atomic operation, meaning that if one of the rows fails,
/// none of the rows will be affected.
///
/// If [noReturn] is set to `true`, the resulting rows are not read back from
/// the database and an empty list is returned. This avoids the overhead of
/// transferring and deserializing the rows when the result is not needed.
Future<List<AccountPassword>> upsert(
_is.DatabaseSession session,
List<AccountPassword> rows, {
required _is.ColumnSelections<AccountPasswordTable> conflictColumns,
_is.ColumnSelections<AccountPasswordTable>? updateColumns,
_is.WhereExpressionBuilder<AccountPasswordTable>? updateWhere,
_is.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.upsert<AccountPassword>(
rows,
conflictColumns: conflictColumns(AccountPassword.t),
updateColumns: updateColumns?.call(AccountPassword.t),
updateWhere: updateWhere?.call(AccountPassword.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Upserts a single [AccountPassword] and returns the resulting row.
///
/// If the row conflicts on the given [conflictColumns], the existing row is
/// updated. Otherwise, a new row is inserted.
///
/// If [updateColumns] is provided, only those columns will be updated on
/// conflict. If null, all non-conflict, non-id columns are updated.
///
/// If [updateWhere] is provided, the update only applies when the existing
/// row matches the expression. Returns `null` if no row was affected — for
/// example when [updateWhere] does not match the conflicting row.
///
/// The returned [AccountPassword] will have its `id` field set.
Future<AccountPassword?> upsertRow(
_is.DatabaseSession session,
AccountPassword row, {
required _is.ColumnSelections<AccountPasswordTable> conflictColumns,
_is.ColumnSelections<AccountPasswordTable>? updateColumns,
_is.WhereExpressionBuilder<AccountPasswordTable>? updateWhere,
_is.Transaction? transaction,
}) async {
return session.db.upsertRow<AccountPassword>(
row,
conflictColumns: conflictColumns(AccountPassword.t),
updateColumns: updateColumns?.call(AccountPassword.t),
updateWhere: updateWhere?.call(AccountPassword.t),
transaction: transaction,
);
}
/// Updates all [AccountPassword]s in the list and returns the updated rows. If
/// [columns] is provided, only those columns will be updated. Defaults to
/// all columns.
/// This is an atomic operation, meaning that if one of the rows fails to
/// update, none of the rows will be updated.
///
/// If [noReturn] is set to `true`, the updated rows are not read back from
/// the database and an empty list is returned. This avoids the overhead of
/// transferring and deserializing the rows when the result is not needed.
Future<List<AccountPassword>> update(
_is.DatabaseSession session,
List<AccountPassword> rows, {
_is.ColumnSelections<AccountPasswordTable>? columns,
_is.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.update<AccountPassword>(
rows,
columns: columns?.call(AccountPassword.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Updates a single [AccountPassword]. The row needs to have its id set.
/// Optionally, a list of [columns] can be provided to only update those
/// columns. Defaults to all columns.
Future<AccountPassword> updateRow(
_is.DatabaseSession session,
AccountPassword row, {
_is.ColumnSelections<AccountPasswordTable>? columns,
_is.Transaction? transaction,
}) async {
return session.db.updateRow<AccountPassword>(
row,
columns: columns?.call(AccountPassword.t),
transaction: transaction,
);
}
/// Updates a single [AccountPassword] by its [id] with the specified [columnValues].
/// Returns the updated row or null if no row with the given id exists.
Future<AccountPassword?> updateById(
_is.DatabaseSession session,
int id, {
required _is.ColumnValueListBuilder<AccountPasswordUpdateTable>
columnValues,
_is.Transaction? transaction,
}) async {
return session.db.updateById<AccountPassword>(
id,
columnValues: columnValues(AccountPassword.t.updateTable),
transaction: transaction,
);
}
/// Updates all [AccountPassword]s matching the [where] expression with the specified [columnValues].
/// Returns the list of updated rows.
///
/// If [noReturn] is set to `true`, the updated rows are not read back from
/// the database and an empty list is returned. This avoids the overhead of
/// transferring and deserializing the rows when the result is not needed.
Future<List<AccountPassword>> updateWhere(
_is.DatabaseSession session, {
required _is.ColumnValueListBuilder<AccountPasswordUpdateTable>
columnValues,
required _is.WhereExpressionBuilder<AccountPasswordTable> where,
int? limit,
int? offset,
_is.OrderByBuilder<AccountPasswordTable>? orderBy,
_is.OrderByListBuilder<AccountPasswordTable>? orderByList,
_is.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.updateWhere<AccountPassword>(
columnValues: columnValues(AccountPassword.t.updateTable),
where: where(AccountPassword.t),
limit: limit,
offset: offset,
orderBy: orderBy?.call(AccountPassword.t),
orderByList: orderByList?.call(AccountPassword.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Deletes all [AccountPassword]s in the list and returns the deleted rows.
///
/// To specify the order of the returned rows use [orderBy] or [orderByList]
/// when sorting by multiple columns.
///
/// This is an atomic operation, meaning that if one of the rows fail to
/// be deleted, none of the rows will be deleted.
///
/// If [noReturn] is set to `true`, the deleted rows are not read back from
/// the database and an empty list is returned. This avoids the overhead of
/// transferring and deserializing the rows when the result is not needed.
Future<List<AccountPassword>> delete(
_is.DatabaseSession session,
List<AccountPassword> rows, {
_is.OrderByBuilder<AccountPasswordTable>? orderBy,
_is.OrderByListBuilder<AccountPasswordTable>? orderByList,
_is.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.delete<AccountPassword>(
rows,
orderBy: orderBy?.call(AccountPassword.t),
orderByList: orderByList?.call(AccountPassword.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Deletes a single [AccountPassword].
Future<AccountPassword> deleteRow(
_is.DatabaseSession session,
AccountPassword row, {
_is.Transaction? transaction,
}) async {
return session.db.deleteRow<AccountPassword>(
row,
transaction: transaction,
);
}
/// Deletes all rows matching the [where] expression.
///
/// To specify the order of the returned rows use [orderBy] or [orderByList]
/// when sorting by multiple columns.
///
/// If [noReturn] is set to `true`, the deleted rows are not read back from
/// the database and an empty list is returned. This avoids the overhead of
/// transferring and deserializing the rows when the result is not needed.
Future<List<AccountPassword>> deleteWhere(
_is.DatabaseSession session, {
required _is.WhereExpressionBuilder<AccountPasswordTable> where,
_is.OrderByBuilder<AccountPasswordTable>? orderBy,
_is.OrderByListBuilder<AccountPasswordTable>? orderByList,
_is.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.deleteWhere<AccountPassword>(
where: where(AccountPassword.t),
orderBy: orderBy?.call(AccountPassword.t),
orderByList: orderByList?.call(AccountPassword.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Counts the number of rows matching the [where] expression. If omitted,
/// will return the count of all rows in the table.
Future<int> count(
_is.DatabaseSession session, {
_is.WhereExpressionBuilder<AccountPasswordTable>? where,
int? limit,
_is.Transaction? transaction,
}) async {
return session.db.count<AccountPassword>(
where: where?.call(AccountPassword.t),
limit: limit,
transaction: transaction,
);
}
/// Acquires row-level locks on [AccountPassword] rows matching the [where] expression.
Future<void> lockRows(
_is.DatabaseSession session, {
required _is.WhereExpressionBuilder<AccountPasswordTable> where,
required _is.LockMode lockMode,
required _is.Transaction transaction,
_is.LockBehavior lockBehavior = _is.LockBehavior.wait,
}) async {
return session.db.lockRows<AccountPassword>(
where: where(AccountPassword.t),
lockMode: lockMode,
lockBehavior: lockBehavior,
transaction: transaction,
);
}
}

View file

@ -0,0 +1,666 @@
/* 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/serverpod.dart' as _is;
import '../password/account_reset_kind.dart' as _itrk9fro;
/// A reset asked for that takes effect only later (ADR 0017), so the
/// account's owner, told by mail, can call it off by signing in meanwhile:
/// the second factor's, or the whole account's (new keys).
abstract class AccountReset
implements _is.TableRow<int?>, _is.ProtocolSerialization {
AccountReset._({
this.id,
required this.authUserId,
required this.kind,
required this.takesEffectAt,
required this.createdAt,
});
factory AccountReset({
int? id,
required _is.UuidValue authUserId,
required _itrk9fro.AccountResetKind kind,
required DateTime takesEffectAt,
required DateTime createdAt,
}) = _AccountResetImpl;
factory AccountReset.fromJson(Map<String, dynamic> jsonSerialization) {
return AccountReset(
id: jsonSerialization['id'] as int?,
authUserId: _is.UuidValueJsonExtension.fromJson(
jsonSerialization['authUserId'],
),
kind: _itrk9fro.AccountResetKind.fromJson(
(jsonSerialization['kind'] as String),
),
takesEffectAt: _is.DateTimeJsonExtension.fromJson(
jsonSerialization['takesEffectAt'],
),
createdAt: _is.DateTimeJsonExtension.fromJson(
jsonSerialization['createdAt'],
),
);
}
static final t = AccountResetTable();
static const db = AccountResetRepository._();
@override
int? id;
/// The account: its person's ID (deleted with them, `deletePerson`).
_is.UuidValue authUserId;
/// Which.
_itrk9fro.AccountResetKind kind;
/// When it may be carried out.
DateTime takesEffectAt;
DateTime createdAt;
@override
_is.Table<int?> get table => t;
/// Returns a shallow copy of this [AccountReset]
/// with some or all fields replaced by the given arguments.
@_is.useResult
AccountReset copyWith({
int? id,
_is.UuidValue? authUserId,
_itrk9fro.AccountResetKind? kind,
DateTime? takesEffectAt,
DateTime? createdAt,
});
@override
Map<String, dynamic> toJson() {
return {
'__className__': 'halkyon_base.AccountReset',
if (id != null) 'id': id,
'authUserId': authUserId.toJson(),
'kind': kind.toJson(),
'takesEffectAt': takesEffectAt.toJson(),
'createdAt': createdAt.toJson(),
};
}
@override
Map<String, dynamic> toJsonForProtocol() {
return {};
}
static AccountResetInclude include() {
return AccountResetInclude._();
}
static AccountResetIncludeList includeList({
_is.WhereExpressionBuilder<AccountResetTable>? where,
int? limit,
int? offset,
_is.OrderByBuilder<AccountResetTable>? orderBy,
_is.OrderByListBuilder<AccountResetTable>? orderByList,
AccountResetInclude? include,
}) {
return AccountResetIncludeList._(
where: where,
limit: limit,
offset: offset,
orderBy: orderBy?.call(AccountReset.t),
orderByList: orderByList?.call(AccountReset.t),
include: include,
);
}
@override
String toString() {
return _is.SerializationManager.encode(this);
}
}
class _Undefined {}
class _AccountResetImpl extends AccountReset {
_AccountResetImpl({
int? id,
required _is.UuidValue authUserId,
required _itrk9fro.AccountResetKind kind,
required DateTime takesEffectAt,
required DateTime createdAt,
}) : super._(
id: id,
authUserId: authUserId,
kind: kind,
takesEffectAt: takesEffectAt,
createdAt: createdAt,
);
/// Returns a shallow copy of this [AccountReset]
/// with some or all fields replaced by the given arguments.
@_is.useResult
@override
AccountReset copyWith({
Object? id = _Undefined,
_is.UuidValue? authUserId,
_itrk9fro.AccountResetKind? kind,
DateTime? takesEffectAt,
DateTime? createdAt,
}) {
return AccountReset(
id: id is int? ? id : this.id,
authUserId: authUserId ?? this.authUserId,
kind: kind ?? this.kind,
takesEffectAt: takesEffectAt ?? this.takesEffectAt,
createdAt: createdAt ?? this.createdAt,
);
}
}
class AccountResetUpdateTable extends _is.UpdateTable<AccountResetTable> {
AccountResetUpdateTable(super.table);
_is.ColumnValue<_is.UuidValue, _is.UuidValue> authUserId(
_is.UuidValue value,
) => _is.ColumnValue(
table.authUserId,
value,
);
_is.ColumnValue<_itrk9fro.AccountResetKind, _itrk9fro.AccountResetKind> kind(
_itrk9fro.AccountResetKind value,
) => _is.ColumnValue(
table.kind,
value,
);
_is.ColumnValue<DateTime, DateTime> takesEffectAt(DateTime value) =>
_is.ColumnValue(
table.takesEffectAt,
value,
);
_is.ColumnValue<DateTime, DateTime> createdAt(DateTime value) =>
_is.ColumnValue(
table.createdAt,
value,
);
}
class AccountResetTable extends _is.Table<int?> {
AccountResetTable({super.tableRelation}) : super(tableName: 'account_reset') {
updateTable = AccountResetUpdateTable(this);
authUserId = _is.ColumnUuid(
'authUserId',
this,
);
kind = _is.ColumnEnum(
'kind',
this,
_is.EnumSerialization.byName,
);
takesEffectAt = _is.ColumnDateTime(
'takesEffectAt',
this,
);
createdAt = _is.ColumnDateTime(
'createdAt',
this,
);
}
late final AccountResetUpdateTable updateTable;
/// The account: its person's ID (deleted with them, `deletePerson`).
late final _is.ColumnUuid authUserId;
/// Which.
late final _is.ColumnEnum<_itrk9fro.AccountResetKind> kind;
/// When it may be carried out.
late final _is.ColumnDateTime takesEffectAt;
late final _is.ColumnDateTime createdAt;
@override
List<_is.Column> get columns => [
id,
authUserId,
kind,
takesEffectAt,
createdAt,
];
}
class AccountResetInclude extends _is.IncludeObject {
AccountResetInclude._();
@override
Map<String, _is.Include?> get includes => {};
@override
_is.Table<int?> get table => AccountReset.t;
}
class AccountResetIncludeList extends _is.IncludeList {
AccountResetIncludeList._({
_is.WhereExpressionBuilder<AccountResetTable>? where,
super.limit,
super.offset,
super.orderBy,
super.orderByList,
super.include,
}) {
super.where = where?.call(AccountReset.t);
}
@override
Map<String, _is.Include?> get includes => include?.includes ?? {};
@override
_is.Table<int?> get table => AccountReset.t;
}
class AccountResetRepository {
const AccountResetRepository._();
/// Returns a list of [AccountReset]s matching the given query parameters.
///
/// Use [where] to specify which items to include in the return value.
/// If none is specified, all items will be returned.
///
/// To specify the order of the items use [orderBy] or [orderByList]
/// when sorting by multiple columns.
///
/// The maximum number of items can be set by [limit]. If no limit is set,
/// all items matching the query will be returned.
///
/// [offset] defines how many items to skip, after which [limit] (or all)
/// items are read from the database.
///
/// ```dart
/// var persons = await Persons.db.find(
/// session,
/// where: (t) => t.lastName.equals('Jones'),
/// orderBy: (t) => t.firstName,
/// limit: 100,
/// );
/// ```
Future<List<AccountReset>> find(
_is.DatabaseSession session, {
_is.WhereExpressionBuilder<AccountResetTable>? where,
int? limit,
int? offset,
_is.OrderByBuilder<AccountResetTable>? orderBy,
_is.OrderByListBuilder<AccountResetTable>? orderByList,
_is.Transaction? transaction,
_is.LockMode? lockMode,
_is.LockBehavior? lockBehavior,
}) async {
return session.db.find<AccountReset>(
where: where?.call(AccountReset.t),
orderBy: orderBy?.call(AccountReset.t),
orderByList: orderByList?.call(AccountReset.t),
limit: limit,
offset: offset,
transaction: transaction,
lockMode: lockMode,
lockBehavior: lockBehavior,
);
}
/// Returns the first matching [AccountReset] matching the given query parameters.
///
/// Use [where] to specify which items to include in the return value.
/// If none is specified, all items will be returned.
///
/// To specify the order use [orderBy] or [orderByList]
/// when sorting by multiple columns.
///
/// [offset] defines how many items to skip, after which the next one will be picked.
///
/// ```dart
/// var youngestPerson = await Persons.db.findFirstRow(
/// session,
/// where: (t) => t.lastName.equals('Jones'),
/// orderBy: (t) => t.age,
/// );
/// ```
Future<AccountReset?> findFirstRow(
_is.DatabaseSession session, {
_is.WhereExpressionBuilder<AccountResetTable>? where,
int? offset,
_is.OrderByBuilder<AccountResetTable>? orderBy,
_is.OrderByListBuilder<AccountResetTable>? orderByList,
_is.Transaction? transaction,
_is.LockMode? lockMode,
_is.LockBehavior? lockBehavior,
}) async {
return session.db.findFirstRow<AccountReset>(
where: where?.call(AccountReset.t),
orderBy: orderBy?.call(AccountReset.t),
orderByList: orderByList?.call(AccountReset.t),
offset: offset,
transaction: transaction,
lockMode: lockMode,
lockBehavior: lockBehavior,
);
}
/// Finds a single [AccountReset] by its [id] or null if no such row exists.
Future<AccountReset?> findById(
_is.DatabaseSession session,
int id, {
_is.Transaction? transaction,
_is.LockMode? lockMode,
_is.LockBehavior? lockBehavior,
}) async {
return session.db.findById<AccountReset>(
id,
transaction: transaction,
lockMode: lockMode,
lockBehavior: lockBehavior,
);
}
/// Inserts all [AccountReset]s in the list and returns the inserted rows.
///
/// The returned [AccountReset]s will have their `id` fields set.
///
/// This is an atomic operation, meaning that if one of the rows fails to
/// insert, none of the rows will be inserted.
///
/// If [ignoreConflicts] is set to `true`, rows that conflict with existing
/// rows are silently skipped, and only the successfully inserted rows are
/// returned.
///
/// If [noReturn] is set to `true`, the inserted rows are not read back from
/// the database and an empty list is returned. This avoids the overhead of
/// transferring and deserializing the rows when the result is not needed.
Future<List<AccountReset>> insert(
_is.DatabaseSession session,
List<AccountReset> rows, {
_is.Transaction? transaction,
bool ignoreConflicts = false,
bool noReturn = false,
}) async {
return session.db.insert<AccountReset>(
rows,
transaction: transaction,
ignoreConflicts: ignoreConflicts,
noReturn: noReturn,
);
}
/// Inserts a single [AccountReset] and returns the inserted row.
///
/// The returned [AccountReset] will have its `id` field set.
Future<AccountReset> insertRow(
_is.DatabaseSession session,
AccountReset row, {
_is.Transaction? transaction,
}) async {
return session.db.insertRow<AccountReset>(
row,
transaction: transaction,
);
}
/// Upserts all [AccountReset]s in the list and returns the resulting rows.
///
/// If a row conflicts on the given [conflictColumns], the existing row is
/// updated with the new values. Otherwise, a new row is inserted.
///
/// If [updateColumns] is provided, only those columns will be updated on
/// conflict. If null, all non-conflict, non-id columns are updated.
///
/// If [updateWhere] is provided, the update only applies to rows matching the
/// given expression. Conflicting rows that don't match are skipped and not
/// returned, so the resulting list may be shorter than [rows].
///
/// The returned [AccountReset]s will have their `id` fields set.
///
/// This is an atomic operation, meaning that if one of the rows fails,
/// none of the rows will be affected.
///
/// If [noReturn] is set to `true`, the resulting rows are not read back from
/// the database and an empty list is returned. This avoids the overhead of
/// transferring and deserializing the rows when the result is not needed.
Future<List<AccountReset>> upsert(
_is.DatabaseSession session,
List<AccountReset> rows, {
required _is.ColumnSelections<AccountResetTable> conflictColumns,
_is.ColumnSelections<AccountResetTable>? updateColumns,
_is.WhereExpressionBuilder<AccountResetTable>? updateWhere,
_is.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.upsert<AccountReset>(
rows,
conflictColumns: conflictColumns(AccountReset.t),
updateColumns: updateColumns?.call(AccountReset.t),
updateWhere: updateWhere?.call(AccountReset.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Upserts a single [AccountReset] and returns the resulting row.
///
/// If the row conflicts on the given [conflictColumns], the existing row is
/// updated. Otherwise, a new row is inserted.
///
/// If [updateColumns] is provided, only those columns will be updated on
/// conflict. If null, all non-conflict, non-id columns are updated.
///
/// If [updateWhere] is provided, the update only applies when the existing
/// row matches the expression. Returns `null` if no row was affected — for
/// example when [updateWhere] does not match the conflicting row.
///
/// The returned [AccountReset] will have its `id` field set.
Future<AccountReset?> upsertRow(
_is.DatabaseSession session,
AccountReset row, {
required _is.ColumnSelections<AccountResetTable> conflictColumns,
_is.ColumnSelections<AccountResetTable>? updateColumns,
_is.WhereExpressionBuilder<AccountResetTable>? updateWhere,
_is.Transaction? transaction,
}) async {
return session.db.upsertRow<AccountReset>(
row,
conflictColumns: conflictColumns(AccountReset.t),
updateColumns: updateColumns?.call(AccountReset.t),
updateWhere: updateWhere?.call(AccountReset.t),
transaction: transaction,
);
}
/// Updates all [AccountReset]s in the list and returns the updated rows. If
/// [columns] is provided, only those columns will be updated. Defaults to
/// all columns.
/// This is an atomic operation, meaning that if one of the rows fails to
/// update, none of the rows will be updated.
///
/// If [noReturn] is set to `true`, the updated rows are not read back from
/// the database and an empty list is returned. This avoids the overhead of
/// transferring and deserializing the rows when the result is not needed.
Future<List<AccountReset>> update(
_is.DatabaseSession session,
List<AccountReset> rows, {
_is.ColumnSelections<AccountResetTable>? columns,
_is.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.update<AccountReset>(
rows,
columns: columns?.call(AccountReset.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Updates a single [AccountReset]. The row needs to have its id set.
/// Optionally, a list of [columns] can be provided to only update those
/// columns. Defaults to all columns.
Future<AccountReset> updateRow(
_is.DatabaseSession session,
AccountReset row, {
_is.ColumnSelections<AccountResetTable>? columns,
_is.Transaction? transaction,
}) async {
return session.db.updateRow<AccountReset>(
row,
columns: columns?.call(AccountReset.t),
transaction: transaction,
);
}
/// Updates a single [AccountReset] by its [id] with the specified [columnValues].
/// Returns the updated row or null if no row with the given id exists.
Future<AccountReset?> updateById(
_is.DatabaseSession session,
int id, {
required _is.ColumnValueListBuilder<AccountResetUpdateTable> columnValues,
_is.Transaction? transaction,
}) async {
return session.db.updateById<AccountReset>(
id,
columnValues: columnValues(AccountReset.t.updateTable),
transaction: transaction,
);
}
/// Updates all [AccountReset]s matching the [where] expression with the specified [columnValues].
/// Returns the list of updated rows.
///
/// If [noReturn] is set to `true`, the updated rows are not read back from
/// the database and an empty list is returned. This avoids the overhead of
/// transferring and deserializing the rows when the result is not needed.
Future<List<AccountReset>> updateWhere(
_is.DatabaseSession session, {
required _is.ColumnValueListBuilder<AccountResetUpdateTable> columnValues,
required _is.WhereExpressionBuilder<AccountResetTable> where,
int? limit,
int? offset,
_is.OrderByBuilder<AccountResetTable>? orderBy,
_is.OrderByListBuilder<AccountResetTable>? orderByList,
_is.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.updateWhere<AccountReset>(
columnValues: columnValues(AccountReset.t.updateTable),
where: where(AccountReset.t),
limit: limit,
offset: offset,
orderBy: orderBy?.call(AccountReset.t),
orderByList: orderByList?.call(AccountReset.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Deletes all [AccountReset]s in the list and returns the deleted rows.
///
/// To specify the order of the returned rows use [orderBy] or [orderByList]
/// when sorting by multiple columns.
///
/// This is an atomic operation, meaning that if one of the rows fail to
/// be deleted, none of the rows will be deleted.
///
/// If [noReturn] is set to `true`, the deleted rows are not read back from
/// the database and an empty list is returned. This avoids the overhead of
/// transferring and deserializing the rows when the result is not needed.
Future<List<AccountReset>> delete(
_is.DatabaseSession session,
List<AccountReset> rows, {
_is.OrderByBuilder<AccountResetTable>? orderBy,
_is.OrderByListBuilder<AccountResetTable>? orderByList,
_is.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.delete<AccountReset>(
rows,
orderBy: orderBy?.call(AccountReset.t),
orderByList: orderByList?.call(AccountReset.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Deletes a single [AccountReset].
Future<AccountReset> deleteRow(
_is.DatabaseSession session,
AccountReset row, {
_is.Transaction? transaction,
}) async {
return session.db.deleteRow<AccountReset>(
row,
transaction: transaction,
);
}
/// Deletes all rows matching the [where] expression.
///
/// To specify the order of the returned rows use [orderBy] or [orderByList]
/// when sorting by multiple columns.
///
/// If [noReturn] is set to `true`, the deleted rows are not read back from
/// the database and an empty list is returned. This avoids the overhead of
/// transferring and deserializing the rows when the result is not needed.
Future<List<AccountReset>> deleteWhere(
_is.DatabaseSession session, {
required _is.WhereExpressionBuilder<AccountResetTable> where,
_is.OrderByBuilder<AccountResetTable>? orderBy,
_is.OrderByListBuilder<AccountResetTable>? orderByList,
_is.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.deleteWhere<AccountReset>(
where: where(AccountReset.t),
orderBy: orderBy?.call(AccountReset.t),
orderByList: orderByList?.call(AccountReset.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Counts the number of rows matching the [where] expression. If omitted,
/// will return the count of all rows in the table.
Future<int> count(
_is.DatabaseSession session, {
_is.WhereExpressionBuilder<AccountResetTable>? where,
int? limit,
_is.Transaction? transaction,
}) async {
return session.db.count<AccountReset>(
where: where?.call(AccountReset.t),
limit: limit,
transaction: transaction,
);
}
/// Acquires row-level locks on [AccountReset] rows matching the [where] expression.
Future<void> lockRows(
_is.DatabaseSession session, {
required _is.WhereExpressionBuilder<AccountResetTable> where,
required _is.LockMode lockMode,
required _is.Transaction transaction,
_is.LockBehavior lockBehavior = _is.LockBehavior.wait,
}) async {
return session.db.lockRows<AccountReset>(
where: where(AccountReset.t),
lockMode: lockMode,
lockBehavior: lockBehavior,
transaction: transaction,
);
}
}

View file

@ -0,0 +1,42 @@
/* 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/serverpod.dart' as _is;
/// Which reset an account waits for.
enum AccountResetKind implements _is.SerializableModel {
/// Its authenticators and recovery codes go: a lost second factor.
authenticator,
/// Its password and keys are made anew: a lost password without the
/// recovery phrase, which loses what was encrypted under the old keys.
account;
static AccountResetKind fromJson(String name) {
switch (name) {
case 'authenticator':
return AccountResetKind.authenticator;
case 'account':
return AccountResetKind.account;
default:
throw ArgumentError(
'Value "$name" cannot be converted to "AccountResetKind"',
);
}
}
@override
String toJson() => name;
@override
String toString() => name;
}

View file

@ -0,0 +1,120 @@
/* 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/serverpod.dart' as _is;
/// One of the account's authenticators, as its owner sees them: never its
/// secret.
abstract class Authenticator
implements _is.SerializableModel, _is.ProtocolSerialization {
Authenticator._({
required this.id,
required this.name,
required this.confirmed,
required this.createdAt,
});
factory Authenticator({
required _is.UuidValue id,
required String name,
required bool confirmed,
required DateTime createdAt,
}) = _AuthenticatorImpl;
factory Authenticator.fromJson(Map<String, dynamic> jsonSerialization) {
return Authenticator(
id: _is.UuidValueJsonExtension.fromJson(jsonSerialization['id']),
name: jsonSerialization['name'] as String,
confirmed: _is.BoolJsonExtension.fromJson(jsonSerialization['confirmed']),
createdAt: _is.DateTimeJsonExtension.fromJson(
jsonSerialization['createdAt'],
),
);
}
_is.UuidValue id;
String name;
/// Whether a code from it was accepted; one that wasn't counts for nothing.
bool confirmed;
DateTime createdAt;
/// Returns a shallow copy of this [Authenticator]
/// with some or all fields replaced by the given arguments.
@_is.useResult
Authenticator copyWith({
_is.UuidValue? id,
String? name,
bool? confirmed,
DateTime? createdAt,
});
@override
Map<String, dynamic> toJson() {
return {
'__className__': 'halkyon_base.Authenticator',
'id': id.toJson(),
'name': name,
'confirmed': confirmed,
'createdAt': createdAt.toJson(),
};
}
@override
Map<String, dynamic> toJsonForProtocol() {
return {
'__className__': 'halkyon_base.Authenticator',
'id': id.toJson(),
'name': name,
'confirmed': confirmed,
'createdAt': createdAt.toJson(),
};
}
@override
String toString() {
return _is.SerializationManager.encode(this);
}
}
class _AuthenticatorImpl extends Authenticator {
_AuthenticatorImpl({
required _is.UuidValue id,
required String name,
required bool confirmed,
required DateTime createdAt,
}) : super._(
id: id,
name: name,
confirmed: confirmed,
createdAt: createdAt,
);
/// Returns a shallow copy of this [Authenticator]
/// with some or all fields replaced by the given arguments.
@_is.useResult
@override
Authenticator copyWith({
_is.UuidValue? id,
String? name,
bool? confirmed,
DateTime? createdAt,
}) {
return Authenticator(
id: id ?? this.id,
name: name ?? this.name,
confirmed: confirmed ?? this.confirmed,
createdAt: createdAt ?? this.createdAt,
);
}
}

View file

@ -0,0 +1,97 @@
/* 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/serverpod.dart' as _is;
/// A new authenticator (ADR 0017): the `otpauth://` URI its app reads from a
/// QR code, shown this once, and the ID a code from it confirms.
abstract class AuthenticatorEnrolment
implements _is.SerializableModel, _is.ProtocolSerialization {
AuthenticatorEnrolment._({
required this.authenticator,
required this.uri,
});
factory AuthenticatorEnrolment({
required _is.UuidValue authenticator,
required String uri,
}) = _AuthenticatorEnrolmentImpl;
factory AuthenticatorEnrolment.fromJson(
Map<String, dynamic> jsonSerialization,
) {
return AuthenticatorEnrolment(
authenticator: _is.UuidValueJsonExtension.fromJson(
jsonSerialization['authenticator'],
),
uri: jsonSerialization['uri'] as String,
);
}
_is.UuidValue authenticator;
String uri;
/// Returns a shallow copy of this [AuthenticatorEnrolment]
/// with some or all fields replaced by the given arguments.
@_is.useResult
AuthenticatorEnrolment copyWith({
_is.UuidValue? authenticator,
String? uri,
});
@override
Map<String, dynamic> toJson() {
return {
'__className__': 'halkyon_base.AuthenticatorEnrolment',
'authenticator': authenticator.toJson(),
'uri': uri,
};
}
@override
Map<String, dynamic> toJsonForProtocol() {
return {
'__className__': 'halkyon_base.AuthenticatorEnrolment',
'authenticator': authenticator.toJson(),
'uri': uri,
};
}
@override
String toString() {
return _is.SerializationManager.encode(this);
}
}
class _AuthenticatorEnrolmentImpl extends AuthenticatorEnrolment {
_AuthenticatorEnrolmentImpl({
required _is.UuidValue authenticator,
required String uri,
}) : super._(
authenticator: authenticator,
uri: uri,
);
/// Returns a shallow copy of this [AuthenticatorEnrolment]
/// with some or all fields replaced by the given arguments.
@_is.useResult
@override
AuthenticatorEnrolment copyWith({
_is.UuidValue? authenticator,
String? uri,
}) {
return AuthenticatorEnrolment(
authenticator: authenticator ?? this.authenticator,
uri: uri ?? this.uri,
);
}
}

View file

@ -0,0 +1,144 @@
/* 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 'dart:typed_data' as _idt;
import 'package:halkyon_base_server/src/generated/protocol.dart' as _ibewtqgw;
import 'package:serverpod/serverpod.dart' as _is;
import '../password/wrapped_keys.dart' as _i2rxumte;
/// What a device needs to prove the recovery phrase (ADR 0017): the
/// account's private keys wrapped under the phrase's key and its public
/// keys, which the device opens with the phrase, and a nonce it signs with
/// the signing key so opened, within two minutes, once. Alike for an address
/// nobody has.
abstract class KeyProofChallenge
implements _is.SerializableModel, _is.ProtocolSerialization {
KeyProofChallenge._({
required this.challenge,
required this.nonce,
required this.encryptionPublicKey,
required this.signingPublicKey,
required this.recoveryKeys,
});
factory KeyProofChallenge({
required _is.UuidValue challenge,
required _idt.ByteData nonce,
required _idt.ByteData encryptionPublicKey,
required _idt.ByteData signingPublicKey,
required _i2rxumte.WrappedKeys recoveryKeys,
}) = _KeyProofChallengeImpl;
factory KeyProofChallenge.fromJson(Map<String, dynamic> jsonSerialization) {
return KeyProofChallenge(
challenge: _is.UuidValueJsonExtension.fromJson(
jsonSerialization['challenge'],
),
nonce: _is.ByteDataJsonExtension.fromJson(jsonSerialization['nonce']),
encryptionPublicKey: _is.ByteDataJsonExtension.fromJson(
jsonSerialization['encryptionPublicKey'],
),
signingPublicKey: _is.ByteDataJsonExtension.fromJson(
jsonSerialization['signingPublicKey'],
),
recoveryKeys: _ibewtqgw.Protocol().deserialize<_i2rxumte.WrappedKeys>(
jsonSerialization['recoveryKeys'],
),
);
}
_is.UuidValue challenge;
_idt.ByteData nonce;
_idt.ByteData encryptionPublicKey;
_idt.ByteData signingPublicKey;
_i2rxumte.WrappedKeys recoveryKeys;
/// Returns a shallow copy of this [KeyProofChallenge]
/// with some or all fields replaced by the given arguments.
@_is.useResult
KeyProofChallenge copyWith({
_is.UuidValue? challenge,
_idt.ByteData? nonce,
_idt.ByteData? encryptionPublicKey,
_idt.ByteData? signingPublicKey,
_i2rxumte.WrappedKeys? recoveryKeys,
});
@override
Map<String, dynamic> toJson() {
return {
'__className__': 'halkyon_base.KeyProofChallenge',
'challenge': challenge.toJson(),
'nonce': nonce.toJson(),
'encryptionPublicKey': encryptionPublicKey.toJson(),
'signingPublicKey': signingPublicKey.toJson(),
'recoveryKeys': recoveryKeys.toJson(),
};
}
@override
Map<String, dynamic> toJsonForProtocol() {
return {
'__className__': 'halkyon_base.KeyProofChallenge',
'challenge': challenge.toJson(),
'nonce': nonce.toJson(),
'encryptionPublicKey': encryptionPublicKey.toJson(),
'signingPublicKey': signingPublicKey.toJson(),
'recoveryKeys': recoveryKeys.toJsonForProtocol(),
};
}
@override
String toString() {
return _is.SerializationManager.encode(this);
}
}
class _KeyProofChallengeImpl extends KeyProofChallenge {
_KeyProofChallengeImpl({
required _is.UuidValue challenge,
required _idt.ByteData nonce,
required _idt.ByteData encryptionPublicKey,
required _idt.ByteData signingPublicKey,
required _i2rxumte.WrappedKeys recoveryKeys,
}) : super._(
challenge: challenge,
nonce: nonce,
encryptionPublicKey: encryptionPublicKey,
signingPublicKey: signingPublicKey,
recoveryKeys: recoveryKeys,
);
/// Returns a shallow copy of this [KeyProofChallenge]
/// with some or all fields replaced by the given arguments.
@_is.useResult
@override
KeyProofChallenge copyWith({
_is.UuidValue? challenge,
_idt.ByteData? nonce,
_idt.ByteData? encryptionPublicKey,
_idt.ByteData? signingPublicKey,
_i2rxumte.WrappedKeys? recoveryKeys,
}) {
return KeyProofChallenge(
challenge: challenge ?? this.challenge,
nonce: nonce ?? this.nonce.clone(),
encryptionPublicKey:
encryptionPublicKey ?? this.encryptionPublicKey.clone(),
signingPublicKey: signingPublicKey ?? this.signingPublicKey.clone(),
recoveryKeys: recoveryKeys ?? this.recoveryKeys.copyWith(),
);
}
}

Some files were not shown because too many files have changed in this diff Show more