fix(auth)!: the recovery phrase is proved by a key of its own

A key proof was a signature by the account's signing key, which every
signed-in device keeps in its keyring: whoever could read one, with a
code from its authenticator, set a new password without the password or
the phrase (the second reading's H1). The phrase now proves itself with
an Ed25519 key derived from its bits alone (halkyon_keys'
recoveryProofKeysOf, KDF context hlkrprf1, a sibling of the recovery
key), whose public half every setup, new phrase and account reset sends
and the register keeps (account_password.recoveryProofKey); the device
derives the secret half only while the phrase is typed. An account
without one (none exist outside development) proves no phrase until it
sets a new one; decoys check against a decoy proof key alike.

Migration (module, Learn's and Todo's servers, Learn's device schema):
ALTER TABLE "account_password" ADD COLUMN "recoveryProofKey" bytea;
nullable, so rows made before keep working and no data is touched.

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 21:03:15 +02:00
commit aea0c447fc
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
28 changed files with 40952 additions and 93 deletions

View file

@ -209,8 +209,10 @@ Built on the three parts above, on the register (members forward every step thro
- **Resets:** a lost authenticator: SRP's proof plus a mailed code or the recovery
phrase's signature asks for it; it takes effect 72 hours later, a mail tells the
address at once, and signing in with a code calls it off; then the password alone
signs in until a new authenticator is confirmed. A lost password with the phrase: the
account's Ed25519 signature over a nonce (`keyProofMessage`) and a code set a new
signs in until a new authenticator is confirmed. A lost password with the phrase: a
signature over a nonce (`keyProofMessage`) by the phrase's own proof key (Ed25519,
derived from the phrase alone, its public half kept as `recoveryProofKey`; never the
account's signing key, which every signed-in device keeps) and a code set a new
password, the keys staying. A lost password without it: a mailed code asks, 72 hours
pass (a mail at the start, a sign-in calls it off), a new code carries it out: new
password, new keys, no authenticators.

View file

@ -759,6 +759,11 @@ class Endpoints extends _is.EndpointDispatch {
type: _is.getType<_iji0wmu8.WrappedKeys>(),
nullable: false,
),
'recoveryProofKey': _is.ParameterDescription(
name: 'recoveryProofKey',
type: _is.getType<_idt.ByteData>(),
nullable: false,
),
},
call:
(
@ -773,6 +778,7 @@ class Endpoints extends _is.EndpointDispatch {
totpCode: params['totpCode'],
recoveryCode: params['recoveryCode'],
recoveryKeys: params['recoveryKeys'],
recoveryProofKey: params['recoveryProofKey'],
),
),
},

View file

@ -35,6 +35,7 @@ abstract class AccountPassword
required this.passwordKeysCipherText,
required this.recoveryKeysNonce,
required this.recoveryKeysCipherText,
this.recoveryProofKey,
required this.createdAt,
required this.updatedAt,
});
@ -53,6 +54,7 @@ abstract class AccountPassword
required _idt.ByteData passwordKeysCipherText,
required _idt.ByteData recoveryKeysNonce,
required _idt.ByteData recoveryKeysCipherText,
_idt.ByteData? recoveryProofKey,
required DateTime createdAt,
required DateTime updatedAt,
}) = _AccountPasswordImpl;
@ -88,6 +90,11 @@ abstract class AccountPassword
recoveryKeysCipherText: _is.ByteDataJsonExtension.fromJson(
jsonSerialization['recoveryKeysCipherText'],
),
recoveryProofKey: jsonSerialization['recoveryProofKey'] == null
? null
: _is.ByteDataJsonExtension.fromJson(
jsonSerialization['recoveryProofKey'],
),
createdAt: _is.DateTimeJsonExtension.fromJson(
jsonSerialization['createdAt'],
),
@ -140,6 +147,13 @@ abstract class AccountPassword
/// …and what it sealed.
_idt.ByteData recoveryKeysCipherText;
/// The public half of the recovery phrase's own Ed25519 key, derived
/// from the phrase alone (never the account's signing key, which every
/// signed-in device keeps): what proves the phrase. Null for a password
/// set up before it was kept: such an account proves no phrase until it
/// sets a new one.
_idt.ByteData? recoveryProofKey;
DateTime createdAt;
DateTime updatedAt;
@ -164,6 +178,7 @@ abstract class AccountPassword
_idt.ByteData? passwordKeysCipherText,
_idt.ByteData? recoveryKeysNonce,
_idt.ByteData? recoveryKeysCipherText,
_idt.ByteData? recoveryProofKey,
DateTime? createdAt,
DateTime? updatedAt,
});
@ -184,6 +199,8 @@ abstract class AccountPassword
'passwordKeysCipherText': passwordKeysCipherText.toJson(),
'recoveryKeysNonce': recoveryKeysNonce.toJson(),
'recoveryKeysCipherText': recoveryKeysCipherText.toJson(),
if (recoveryProofKey != null)
'recoveryProofKey': recoveryProofKey?.toJson(),
'createdAt': createdAt.toJson(),
'updatedAt': updatedAt.toJson(),
};
@ -239,6 +256,7 @@ class _AccountPasswordImpl extends AccountPassword {
required _idt.ByteData passwordKeysCipherText,
required _idt.ByteData recoveryKeysNonce,
required _idt.ByteData recoveryKeysCipherText,
_idt.ByteData? recoveryProofKey,
required DateTime createdAt,
required DateTime updatedAt,
}) : super._(
@ -255,6 +273,7 @@ class _AccountPasswordImpl extends AccountPassword {
passwordKeysCipherText: passwordKeysCipherText,
recoveryKeysNonce: recoveryKeysNonce,
recoveryKeysCipherText: recoveryKeysCipherText,
recoveryProofKey: recoveryProofKey,
createdAt: createdAt,
updatedAt: updatedAt,
);
@ -277,6 +296,7 @@ class _AccountPasswordImpl extends AccountPassword {
_idt.ByteData? passwordKeysCipherText,
_idt.ByteData? recoveryKeysNonce,
_idt.ByteData? recoveryKeysCipherText,
Object? recoveryProofKey = _Undefined,
DateTime? createdAt,
DateTime? updatedAt,
}) {
@ -297,6 +317,9 @@ class _AccountPasswordImpl extends AccountPassword {
recoveryKeysNonce: recoveryKeysNonce ?? this.recoveryKeysNonce.clone(),
recoveryKeysCipherText:
recoveryKeysCipherText ?? this.recoveryKeysCipherText.clone(),
recoveryProofKey: recoveryProofKey is _idt.ByteData?
? recoveryProofKey
: this.recoveryProofKey?.clone(),
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
);
@ -382,6 +405,13 @@ class AccountPasswordUpdateTable extends _is.UpdateTable<AccountPasswordTable> {
value,
);
_is.ColumnValue<_idt.ByteData, _idt.ByteData> recoveryProofKey(
_idt.ByteData? value,
) => _is.ColumnValue(
table.recoveryProofKey,
value,
);
_is.ColumnValue<DateTime, DateTime> createdAt(DateTime value) =>
_is.ColumnValue(
table.createdAt,
@ -447,6 +477,10 @@ class AccountPasswordTable extends _is.Table<int?> {
'recoveryKeysCipherText',
this,
);
recoveryProofKey = _is.ColumnByteData(
'recoveryProofKey',
this,
);
createdAt = _is.ColumnDateTime(
'createdAt',
this,
@ -495,6 +529,13 @@ class AccountPasswordTable extends _is.Table<int?> {
/// …and what it sealed.
late final _is.ColumnByteData recoveryKeysCipherText;
/// The public half of the recovery phrase's own Ed25519 key, derived
/// from the phrase alone (never the account's signing key, which every
/// signed-in device keeps): what proves the phrase. Null for a password
/// set up before it was kept: such an account proves no phrase until it
/// sets a new one.
late final _is.ColumnByteData recoveryProofKey;
late final _is.ColumnDateTime createdAt;
late final _is.ColumnDateTime updatedAt;
@ -514,6 +555,7 @@ class AccountPasswordTable extends _is.Table<int?> {
passwordKeysCipherText,
recoveryKeysNonce,
recoveryKeysCipherText,
recoveryProofKey,
createdAt,
updatedAt,
];

View file

@ -18,8 +18,8 @@ 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.
/// the phrase's own proof key, within two minutes, once. Alike for an
/// address nobody has.
abstract class KeyProofChallenge
implements _is.SerializableModel, _is.ProtocolSerialization {
KeyProofChallenge._({

View file

@ -31,6 +31,7 @@ abstract class PasswordSetup
required this.signingPublicKey,
required this.passwordKeys,
required this.recoveryKeys,
required this.recoveryProofKey,
});
factory PasswordSetup({
@ -41,6 +42,7 @@ abstract class PasswordSetup
required _idt.ByteData signingPublicKey,
required _i2rxumte.WrappedKeys passwordKeys,
required _i2rxumte.WrappedKeys recoveryKeys,
required _idt.ByteData recoveryProofKey,
}) = _PasswordSetupImpl;
factory PasswordSetup.fromJson(Map<String, dynamic> jsonSerialization) {
@ -64,6 +66,9 @@ abstract class PasswordSetup
recoveryKeys: _ibewtqgw.Protocol().deserialize<_i2rxumte.WrappedKeys>(
jsonSerialization['recoveryKeys'],
),
recoveryProofKey: _is.ByteDataJsonExtension.fromJson(
jsonSerialization['recoveryProofKey'],
),
);
}
@ -82,6 +87,10 @@ abstract class PasswordSetup
_i2rxumte.WrappedKeys recoveryKeys;
/// The public half of the recovery phrase's proof key (Ed25519, from the
/// phrase alone).
_idt.ByteData recoveryProofKey;
/// Returns a shallow copy of this [PasswordSetup]
/// with some or all fields replaced by the given arguments.
@_is.useResult
@ -93,6 +102,7 @@ abstract class PasswordSetup
_idt.ByteData? signingPublicKey,
_i2rxumte.WrappedKeys? passwordKeys,
_i2rxumte.WrappedKeys? recoveryKeys,
_idt.ByteData? recoveryProofKey,
});
@override
Map<String, dynamic> toJson() {
@ -105,6 +115,7 @@ abstract class PasswordSetup
'signingPublicKey': signingPublicKey.toJson(),
'passwordKeys': passwordKeys.toJson(),
'recoveryKeys': recoveryKeys.toJson(),
'recoveryProofKey': recoveryProofKey.toJson(),
};
}
@ -119,6 +130,7 @@ abstract class PasswordSetup
'signingPublicKey': signingPublicKey.toJson(),
'passwordKeys': passwordKeys.toJsonForProtocol(),
'recoveryKeys': recoveryKeys.toJsonForProtocol(),
'recoveryProofKey': recoveryProofKey.toJson(),
};
}
@ -137,6 +149,7 @@ class _PasswordSetupImpl extends PasswordSetup {
required _idt.ByteData signingPublicKey,
required _i2rxumte.WrappedKeys passwordKeys,
required _i2rxumte.WrappedKeys recoveryKeys,
required _idt.ByteData recoveryProofKey,
}) : super._(
salt: salt,
costs: costs,
@ -145,6 +158,7 @@ class _PasswordSetupImpl extends PasswordSetup {
signingPublicKey: signingPublicKey,
passwordKeys: passwordKeys,
recoveryKeys: recoveryKeys,
recoveryProofKey: recoveryProofKey,
);
/// Returns a shallow copy of this [PasswordSetup]
@ -159,6 +173,7 @@ class _PasswordSetupImpl extends PasswordSetup {
_idt.ByteData? signingPublicKey,
_i2rxumte.WrappedKeys? passwordKeys,
_i2rxumte.WrappedKeys? recoveryKeys,
_idt.ByteData? recoveryProofKey,
}) {
return PasswordSetup(
salt: salt ?? this.salt.clone(),
@ -169,6 +184,7 @@ class _PasswordSetupImpl extends PasswordSetup {
signingPublicKey: signingPublicKey ?? this.signingPublicKey.clone(),
passwordKeys: passwordKeys ?? this.passwordKeys.copyWith(),
recoveryKeys: recoveryKeys ?? this.recoveryKeys.copyWith(),
recoveryProofKey: recoveryProofKey ?? this.recoveryProofKey.clone(),
);
}
}

View file

@ -251,6 +251,12 @@ class Protocol extends _is.DatabaseSerializationManager {
isNullable: false,
dartType: 'dart:typed_data:ByteData',
),
_isp.ColumnDefinition(
name: 'recoveryProofKey',
columnType: _isp.ColumnType.bytea,
isNullable: true,
dartType: 'dart:typed_data:ByteData?',
),
_isp.ColumnDefinition(
name: 'createdAt',
columnType: _isp.ColumnType.timestampWithoutTimeZone,

View file

@ -32,6 +32,12 @@ fields:
recoveryKeysNonce: ByteData
### …and what it sealed.
recoveryKeysCipherText: ByteData
### The public half of the recovery phrase's own Ed25519 key, derived
### from the phrase alone (never the account's signing key, which every
### signed-in device keeps): what proves the phrase. Null for a password
### set up before it was kept: such an account proves no phrase until it
### sets a new one.
recoveryProofKey: ByteData?
createdAt: DateTime
updatedAt: DateTime
indexes:

View file

@ -1,8 +1,8 @@
### 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.
### the phrase's own proof key, within two minutes, once. Alike for an
### address nobody has.
class: KeyProofChallenge
fields:
challenge: UuidValue

View file

@ -84,12 +84,14 @@ Future<void> changePassword(
/// Replaces the wrapping of [person]'s keys under their recovery phrase
/// with [recoveryKeys] (a new phrase, made on the device, which holds the
/// keys): the old password's proof for [challenge] (`beginPasswordChange`)
/// keys) and the phrase's proof key with [recoveryProofKey] (its public
/// half): the old password's proof for [challenge] (`beginPasswordChange`)
/// and a [totpCode] or [recoveryCode] from the account, as changing the
/// password takes. The old phrase opens nothing any more.
/// password takes. The old phrase opens and proves nothing any more.
///
/// Refused ([PasswordRefusedException]) for wrapped keys out of range
/// ([PasswordRefusal.invalidSetup]), and otherwise as [changePassword].
/// Refused ([PasswordRefusedException]) for wrapped keys or a proof key out
/// of range ([PasswordRefusal.invalidSetup]), and otherwise as
/// [changePassword].
/// Nothing changes then.
Future<void> setRecoveryKeys(
Session session, {
@ -100,9 +102,10 @@ Future<void> setRecoveryKeys(
String? totpCode,
String? recoveryCode,
required WrappedKeys recoveryKeys,
required ByteData recoveryProofKey,
}) async {
final keys = requirePasswordKeys(session.serverpod);
if (!isWrappedKeys(recoveryKeys)) {
if (!isWrappedKeys(recoveryKeys) || !isPublicKey(recoveryProofKey)) {
throw refusedFor(PasswordRefusal.invalidSetup);
}
final row = await _proveOwner(
@ -116,12 +119,14 @@ Future<void> setRecoveryKeys(
recoveryCode: recoveryCode,
);
// UPDATE "account_password" SET "recoveryKeysNonce" = $1,
// "recoveryKeysCipherText" = $2, "updatedAt" = $3 WHERE "id" = $4
// "recoveryKeysCipherText" = $2, "recoveryProofKey" = $3,
// "updatedAt" = $4 WHERE "id" = $5
await AccountPassword.db.updateRow(
session,
row.copyWith(
recoveryKeysNonce: recoveryKeys.nonce,
recoveryKeysCipherText: recoveryKeys.cipherText,
recoveryProofKey: recoveryProofKey,
updatedAt: clock.now().toUtc(),
),
);

View file

@ -79,7 +79,8 @@ class PasswordEndpoint extends Endpoint {
}
/// A new recovery phrase: [recoveryKeys], the caller's keys wrapped under
/// it on the device, replace the old wrapping, with the password's proof
/// it on the device, replace the old wrapping, and [recoveryProofKey], the
/// public half of its proof key, the old one, with the password's proof
/// for [challenge] ([beginChange]) and a [totpCode] or [recoveryCode], as
/// [change] takes. Refusals as `setRecoveryKeys`'.
Future<void> setRecoveryKeys(
@ -90,6 +91,7 @@ class PasswordEndpoint extends Endpoint {
String? totpCode,
String? recoveryCode,
required WrappedKeys recoveryKeys,
required ByteData recoveryProofKey,
}) => AccountRegister.of(session.serverpod).setRecoveryKeys(
session,
person: session.authenticated!.authUserId,
@ -99,5 +101,6 @@ class PasswordEndpoint extends Endpoint {
totpCode: totpCode,
recoveryCode: recoveryCode,
recoveryKeys: recoveryKeys,
recoveryProofKey: recoveryProofKey,
);
}

View file

@ -13,3 +13,6 @@ fields:
signingPublicKey: ByteData
passwordKeys: WrappedKeys
recoveryKeys: WrappedKeys
### The public half of the recovery phrase's proof key (Ed25519, from the
### phrase alone).
recoveryProofKey: ByteData

View file

@ -202,6 +202,9 @@ bool _areWrappedKeys(WrappedKeys keys) =>
/// a nonce and both keys sealed, of their lengths.
bool isWrappedKeys(WrappedKeys keys) => _areWrappedKeys(keys);
/// Whether [key] is as long as an X25519 or Ed25519 public key.
bool isPublicKey(ByteData key) => key.lengthInBytes == _publicKeyLength;
/// Whether [verifier] is SRP's v, padded to N's length: 1 < v < N. Outside,
/// it would make every password right, or none.
bool _isVerifier(ByteData verifier) {
@ -220,7 +223,8 @@ bool isPasswordSetup(PasswordSetup setup) =>
setup.encryptionPublicKey.lengthInBytes == _publicKeyLength &&
setup.signingPublicKey.lengthInBytes == _publicKeyLength &&
_areWrappedKeys(setup.passwordKeys) &&
_areWrappedKeys(setup.recoveryKeys);
_areWrappedKeys(setup.recoveryKeys) &&
setup.recoveryProofKey.lengthInBytes == _publicKeyLength;
/// Whether [password] is a new password this register takes.
bool isNewPassword(NewPassword password) =>
@ -250,6 +254,7 @@ AccountPassword passwordRowOf(
passwordKeysCipherText: setup.passwordKeys.cipherText,
recoveryKeysNonce: setup.recoveryKeys.nonce,
recoveryKeysCipherText: setup.recoveryKeys.cipherText,
recoveryProofKey: setup.recoveryProofKey,
createdAt: row?.createdAt ?? now,
updatedAt: now,
);
@ -967,11 +972,13 @@ Future<KeyProofChallenge> beginKeyProof(
}
/// Takes the key proof [challenge] (once) and checks [signature], the
/// account's Ed25519 signature over [keyProofMessage]; the account's
/// password row. Refused ([PasswordRefusedException]) for a challenge
/// unknown, used or too old ([PasswordRefusal.challengeExpired]), and for a
/// signature that isn't the account's or an address nobody has
/// ([PasswordRefusal.wrongCredentials]).
/// signature over [keyProofMessage] by the recovery phrase's own proof key
/// (`recoveryProofKey`: derived from the phrase alone, so the account's
/// signing key, which every signed-in device keeps, proves nothing); the
/// account's password row. Refused ([PasswordRefusedException]) for a
/// challenge unknown, used or too old ([PasswordRefusal.challengeExpired]),
/// and for a signature by another key, an account that keeps no proof key,
/// or an address nobody has ([PasswordRefusal.wrongCredentials]).
Future<AccountPassword> proveRecoveryPhrase(
Session session,
PasswordKeys keys, {
@ -983,18 +990,19 @@ Future<AccountPassword> proveRecoveryPhrase(
challenge,
SignInChallengePurpose.keyProof,
);
// Checked against the decoy's key for an address nobody has, so it takes
// as long.
// Checked against the decoy's key for an address nobody has (or an
// account keeping no proof key), so it takes as long.
final decoy = await _decoyKeys(keys.decoyKey, kept.addressHash);
final password = await _passwordOf(session, kept.authUserId);
final proofKey = password?.recoveryProofKey;
final signed = await isAccountSignature(
keyProofMessage(challenge, _unhex(kept.secret)),
signature: Uint8List.sublistView(signature),
publicKey: password == null
? decoy.signingPublicKey
: Uint8List.sublistView(password.signingPublicKey),
publicKey: proofKey == null
? decoy.recoveryProofKey
: Uint8List.sublistView(proofKey),
);
if (!signed || password == null) {
if (!signed || password == null || proofKey == null) {
throw refusedFor(PasswordRefusal.wrongCredentials);
}
return password;
@ -1079,14 +1087,16 @@ Uint8List _decoyBytes(
/// An address nobody has's keys, as a key proof's first step shows them:
/// an X25519 public key (its top bit clear, as a point's u is), an Ed25519
/// one made from a seed, and wrapped keys of an account's length. Computed
/// for every proof begun, an account's too.
/// one made from a seed, and wrapped keys of an account's length; and the
/// phrase's proof key its second step checks against. Computed for every
/// proof begun, an account's too.
Future<
({
Uint8List encryptionPublicKey,
Uint8List signingPublicKey,
Uint8List recoveryKeysNonce,
Uint8List recoveryKeysCipherText,
Uint8List recoveryProofKey,
})
>
_decoyKeys(String decoyKey, String hash) async => (
@ -1111,6 +1121,9 @@ _decoyKeys(String decoyKey, String hash) async => (
hash,
wrappedKeysLength,
),
recoveryProofKey: await ed25519PublicKeyFromSeed(
_decoyBytes(decoyKey, 'proof seed', hash, 32),
),
);
/// [length] bytes from the system's secure random source.

View file

@ -300,8 +300,9 @@ abstract interface class AccountRegister {
UuidValue? keepSession,
});
/// Replaces the wrapping of [person]'s keys under their recovery phrase,
/// with the password's proof and a code. Refusals as `setRecoveryKeys`'.
/// Replaces the wrapping of [person]'s keys under their recovery phrase
/// and the phrase's proof key, with the password's proof and a code.
/// Refusals as `setRecoveryKeys`'.
Future<void> setRecoveryKeys(
Session session, {
required UuidValue person,
@ -311,6 +312,7 @@ abstract interface class AccountRegister {
String? totpCode,
String? recoveryCode,
required WrappedKeys recoveryKeys,
required ByteData recoveryProofKey,
});
/// Deletes the account [person] with everything the register's server has

View file

@ -366,6 +366,7 @@ Future<Map<String, Object?>?> _answer(
totpCode: arguments.optionalString('totpCode'),
recoveryCode: arguments.optionalString('recoveryCode'),
recoveryKeys: arguments.model('recoveryKeys', WrappedKeys.fromJson),
recoveryProofKey: arguments.bytes('recoveryProofKey'),
);
return const {};
}

View file

@ -626,6 +626,7 @@ final class LocalRegister implements AccountRegister {
String? totpCode,
String? recoveryCode,
required WrappedKeys recoveryKeys,
required ByteData recoveryProofKey,
}) => change.setRecoveryKeys(
session,
person: person,
@ -635,6 +636,7 @@ final class LocalRegister implements AccountRegister {
totpCode: totpCode,
recoveryCode: recoveryCode,
recoveryKeys: recoveryKeys,
recoveryProofKey: recoveryProofKey,
);
@override

View file

@ -590,6 +590,7 @@ final class RemoteRegister implements AccountRegister {
String? totpCode,
String? recoveryCode,
required WrappedKeys recoveryKeys,
required ByteData recoveryProofKey,
}) => _ask(RegisterOperation.setRecoveryKeys, {
'person': person.uuid,
'challenge': challenge.uuid,
@ -598,6 +599,7 @@ final class RemoteRegister implements AccountRegister {
'totpCode': totpCode,
'recoveryCode': recoveryCode,
'recoveryKeys': recoveryKeys.toJson(),
'recoveryProofKey': _bytes(recoveryProofKey),
});
@override

View file

@ -0,0 +1,979 @@
BEGIN;
--
-- Function: gen_random_uuid_v7()
-- Source: https://gist.github.com/kjmph/5bd772b2c2df145aa645b837da7eca74
-- License: MIT (copyright notice included on the generator source code).
--
create or replace function gen_random_uuid_v7()
returns uuid
as $$
begin
-- use random v4 uuid as starting point (which has the same variant we need)
-- then overlay timestamp
-- then set version 7 by flipping the 2 and 1 bit in the version 4 string
return encode(
set_bit(
set_bit(
overlay(uuid_send(gen_random_uuid())
placing substring(int8send(floor(extract(epoch from clock_timestamp()) * 1000)::bigint) from 3)
from 1 for 6
),
52, 1
),
53, 1
),
'hex')::uuid;
end
$$
language plpgsql
volatile;
--
-- Class AccountDeletionNotice as table account_deletion_notice
--
CREATE TABLE "account_deletion_notice" (
"id" bigserial PRIMARY KEY,
"account" uuid NOT NULL,
"member" text NOT NULL,
"createdAt" timestamp without time zone NOT NULL,
"attempts" bigint NOT NULL,
"lastAttemptAt" timestamp without time zone
);
-- Indexes
CREATE UNIQUE INDEX "account_deletion_notice_idx" ON "account_deletion_notice" USING btree ("account", "member");
--
-- Class AccountPassword as table account_password
--
CREATE TABLE "account_password" (
"id" bigserial PRIMARY KEY,
"authUserId" uuid NOT NULL,
"salt" bytea NOT NULL,
"kdfAlgorithm" text NOT NULL,
"kdfOpsLimit" bigint NOT NULL,
"kdfMemLimit" bigint NOT NULL,
"verifier" bytea NOT NULL,
"encryptionPublicKey" bytea NOT NULL,
"signingPublicKey" bytea NOT NULL,
"passwordKeysNonce" bytea NOT NULL,
"passwordKeysCipherText" bytea NOT NULL,
"recoveryKeysNonce" bytea NOT NULL,
"recoveryKeysCipherText" bytea NOT NULL,
"recoveryProofKey" bytea,
"createdAt" timestamp without time zone NOT NULL,
"updatedAt" timestamp without time zone NOT NULL
);
-- Indexes
CREATE UNIQUE INDEX "account_password_person_idx" ON "account_password" USING btree ("authUserId");
--
-- Class AccountReset as table account_reset
--
CREATE TABLE "account_reset" (
"id" bigserial PRIMARY KEY,
"authUserId" uuid NOT NULL,
"kind" text NOT NULL,
"takesEffectAt" timestamp without time zone NOT NULL,
"createdAt" timestamp without time zone NOT NULL
);
-- Indexes
CREATE UNIQUE INDEX "account_reset_person_kind_idx" ON "account_reset" USING btree ("authUserId", "kind");
--
-- Class EmailCodeRequest as table email_code_request
--
CREATE TABLE "email_code_request" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid_v7(),
"addressHash" text NOT NULL,
"challengeId" uuid NOT NULL,
"switchChallengeId" uuid,
"createdAt" timestamp without time zone NOT NULL
);
-- Indexes
CREATE INDEX "email_code_request_address_idx" ON "email_code_request" USING btree ("addressHash");
--
-- Class EmailIdentity as table email_identity
--
CREATE TABLE "email_identity" (
"id" bigserial PRIMARY KEY,
"authUserId" uuid NOT NULL,
"addressHash" text NOT NULL,
"createdAt" timestamp without time zone NOT NULL
);
-- Indexes
CREATE UNIQUE INDEX "email_identity_person_idx" ON "email_identity" USING btree ("authUserId");
CREATE UNIQUE INDEX "email_identity_address_idx" ON "email_identity" USING btree ("addressHash");
--
-- Class PublicProfile as table public_profile
--
CREATE TABLE "public_profile" (
"id" bigserial PRIMARY KEY,
"authUserId" uuid NOT NULL,
"handle" text NOT NULL,
"handleKey" text NOT NULL,
"displayName" text NOT NULL,
"bio" text NOT NULL,
"links" json NOT NULL,
"verifiedLinks" json,
"avatarHash" text,
"firstPublishedAt" timestamp without time zone,
"updatedAt" timestamp without time zone NOT NULL
);
-- Indexes
CREATE UNIQUE INDEX "public_profile_person_idx" ON "public_profile" USING btree ("authUserId");
CREATE UNIQUE INDEX "public_profile_handle_idx" ON "public_profile" USING btree ("handleKey");
--
-- Class RecoveryCode as table recovery_code
--
CREATE TABLE "recovery_code" (
"id" bigserial PRIMARY KEY,
"authUserId" uuid NOT NULL,
"hash" text NOT NULL
);
-- Indexes
CREATE INDEX "recovery_code_person_idx" ON "recovery_code" USING btree ("authUserId");
--
-- Class SessionsEndedNotice as table sessions_ended_notice
--
CREATE TABLE "sessions_ended_notice" (
"id" bigserial PRIMARY KEY,
"account" uuid NOT NULL,
"member" text NOT NULL,
"endedBefore" timestamp without time zone NOT NULL,
"keepSession" uuid,
"attempts" bigint NOT NULL,
"lastAttemptAt" timestamp without time zone
);
-- Indexes
CREATE UNIQUE INDEX "sessions_ended_notice_idx" ON "sessions_ended_notice" USING btree ("account", "member");
--
-- Class SignInChallenge as table sign_in_challenge
--
CREATE TABLE "sign_in_challenge" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid_v7(),
"authUserId" uuid,
"purpose" text NOT NULL,
"addressHash" text NOT NULL,
"secret" text NOT NULL,
"serverValue" text,
"createdAt" timestamp without time zone NOT NULL
);
-- Indexes
CREATE INDEX "sign_in_challenge_created_idx" ON "sign_in_challenge" USING btree ("createdAt");
--
-- Class TotpAuthenticator as table totp_authenticator
--
CREATE TABLE "totp_authenticator" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid_v7(),
"authUserId" uuid NOT NULL,
"name" text NOT NULL,
"secret" bytea NOT NULL,
"confirmedAt" timestamp without time zone,
"lastStep" bigint,
"createdAt" timestamp without time zone NOT NULL
);
-- Indexes
CREATE INDEX "totp_authenticator_person_idx" ON "totp_authenticator" USING btree ("authUserId");
--
-- Class CloudStorageEntry as table serverpod_cloud_storage
--
CREATE TABLE "serverpod_cloud_storage" (
"id" bigserial PRIMARY KEY,
"storageId" text NOT NULL,
"path" text NOT NULL,
"addedTime" timestamp without time zone NOT NULL,
"expiration" timestamp without time zone,
"byteData" bytea NOT NULL,
"verified" boolean NOT NULL,
"contentType" text,
"cacheControl" text,
"contentDisposition" text,
"contentEncoding" text,
"customMetadata" text
);
-- Indexes
CREATE UNIQUE INDEX "serverpod_cloud_storage_path_idx" ON "serverpod_cloud_storage" USING btree ("storageId", "path");
CREATE INDEX "serverpod_cloud_storage_expiration" ON "serverpod_cloud_storage" USING btree ("expiration");
--
-- Class CloudStorageDirectDownloadEntry as table serverpod_cloud_storage_direct_download
--
CREATE TABLE "serverpod_cloud_storage_direct_download" (
"id" bigserial PRIMARY KEY,
"storageId" text NOT NULL,
"path" text NOT NULL,
"expiration" timestamp without time zone NOT NULL,
"authKey" text NOT NULL,
"downloadFileName" text,
"contentType" text
);
-- Indexes
CREATE UNIQUE INDEX "serverpod_cloud_storage_direct_download_auth_key" ON "serverpod_cloud_storage_direct_download" USING btree ("authKey");
CREATE INDEX "serverpod_cloud_storage_direct_download_expiration" ON "serverpod_cloud_storage_direct_download" USING btree ("expiration");
--
-- Class CloudStorageDirectUploadEntry as table serverpod_cloud_storage_direct_upload
--
CREATE TABLE "serverpod_cloud_storage_direct_upload" (
"id" bigserial PRIMARY KEY,
"storageId" text NOT NULL,
"path" text NOT NULL,
"expiration" timestamp without time zone NOT NULL,
"authKey" text NOT NULL,
"maxFileSize" bigint NOT NULL DEFAULT 10485760,
"contentLength" bigint,
"preventOverwrite" boolean NOT NULL DEFAULT false,
"contentType" text,
"cacheControl" text,
"contentDisposition" text,
"contentEncoding" text,
"customMetadata" text
);
-- Indexes
CREATE UNIQUE INDEX "serverpod_cloud_storage_direct_upload_storage_path" ON "serverpod_cloud_storage_direct_upload" USING btree ("storageId", "path");
--
-- Class FutureCallEntry as table serverpod_future_call
--
CREATE TABLE "serverpod_future_call" (
"id" bigserial PRIMARY KEY,
"name" text NOT NULL,
"time" timestamp without time zone NOT NULL,
"serializedObject" text,
"serverId" text NOT NULL,
"identifier" text,
"scheduling" json
);
-- Indexes
CREATE INDEX "serverpod_future_call_time_idx" ON "serverpod_future_call" USING btree ("time");
CREATE INDEX "serverpod_future_call_serverId_idx" ON "serverpod_future_call" USING btree ("serverId");
CREATE INDEX "serverpod_future_call_identifier_idx" ON "serverpod_future_call" USING btree ("identifier");
--
-- Class FutureCallClaimEntry as table serverpod_future_call_claim
--
CREATE TABLE "serverpod_future_call_claim" (
"id" bigserial PRIMARY KEY,
"futureCallId" bigint,
"lastHeartbeatTime" timestamp without time zone NOT NULL
);
-- Indexes
CREATE UNIQUE INDEX "future_call_unique_idx" ON "serverpod_future_call_claim" USING btree ("futureCallId");
--
-- Class ServerHealthConnectionInfo as table serverpod_health_connection_info
--
CREATE TABLE "serverpod_health_connection_info" (
"id" bigserial PRIMARY KEY,
"serverId" text NOT NULL,
"timestamp" timestamp without time zone NOT NULL,
"active" bigint NOT NULL,
"closing" bigint NOT NULL,
"idle" bigint NOT NULL,
"granularity" bigint NOT NULL
);
-- Indexes
CREATE UNIQUE INDEX "serverpod_health_connection_info_timestamp_idx" ON "serverpod_health_connection_info" USING btree ("timestamp", "serverId", "granularity");
--
-- Class ServerHealthMetric as table serverpod_health_metric
--
CREATE TABLE "serverpod_health_metric" (
"id" bigserial PRIMARY KEY,
"name" text NOT NULL,
"serverId" text NOT NULL,
"timestamp" timestamp without time zone NOT NULL,
"isHealthy" boolean NOT NULL,
"value" double precision NOT NULL,
"granularity" bigint NOT NULL
);
-- Indexes
CREATE UNIQUE INDEX "serverpod_health_metric_timestamp_idx" ON "serverpod_health_metric" USING btree ("timestamp", "serverId", "name", "granularity");
--
-- Class LogEntry as table serverpod_log
--
CREATE TABLE "serverpod_log" (
"id" bigserial PRIMARY KEY,
"sessionLogId" bigint NOT NULL,
"messageId" bigint,
"reference" text,
"serverId" text NOT NULL,
"time" timestamp without time zone NOT NULL,
"logLevel" bigint NOT NULL,
"message" text NOT NULL,
"error" text,
"stackTrace" text,
"order" bigint NOT NULL
);
-- Indexes
CREATE INDEX "serverpod_log_sessionLogId_idx" ON "serverpod_log" USING btree ("sessionLogId", "order");
--
-- Class MessageLogEntry as table serverpod_message_log
--
CREATE TABLE "serverpod_message_log" (
"id" bigserial PRIMARY KEY,
"sessionLogId" bigint NOT NULL,
"serverId" text NOT NULL,
"messageId" bigint NOT NULL,
"endpoint" text NOT NULL,
"messageName" text NOT NULL,
"duration" double precision NOT NULL,
"error" text,
"stackTrace" text,
"slow" boolean NOT NULL,
"order" bigint NOT NULL
);
-- Indexes
CREATE INDEX "serverpod_message_log_sessionLogId_idx" ON "serverpod_message_log" USING btree ("sessionLogId", "order");
--
-- Class MethodInfo as table serverpod_method
--
CREATE TABLE "serverpod_method" (
"id" bigserial PRIMARY KEY,
"endpoint" text NOT NULL,
"method" text NOT NULL
);
-- Indexes
CREATE UNIQUE INDEX "serverpod_method_endpoint_method_idx" ON "serverpod_method" USING btree ("endpoint", "method");
--
-- Class DatabaseMigrationVersion as table serverpod_migrations
--
CREATE TABLE "serverpod_migrations" (
"id" bigserial PRIMARY KEY,
"module" text NOT NULL,
"version" text NOT NULL,
"timestamp" timestamp without time zone
);
-- Indexes
CREATE UNIQUE INDEX "serverpod_migrations_ids" ON "serverpod_migrations" USING btree ("module");
--
-- Class QueryLogEntry as table serverpod_query_log
--
CREATE TABLE "serverpod_query_log" (
"id" bigserial PRIMARY KEY,
"serverId" text NOT NULL,
"sessionLogId" bigint NOT NULL,
"messageId" bigint,
"query" text NOT NULL,
"duration" double precision NOT NULL,
"numRows" bigint,
"error" text,
"stackTrace" text,
"slow" boolean NOT NULL,
"order" bigint NOT NULL
);
-- Indexes
CREATE INDEX "serverpod_query_log_sessionLogId_idx" ON "serverpod_query_log" USING btree ("sessionLogId", "order");
--
-- Class ReadWriteTestEntry as table serverpod_readwrite_test
--
CREATE TABLE "serverpod_readwrite_test" (
"id" bigserial PRIMARY KEY,
"number" bigint NOT NULL
);
--
-- Class RuntimeSettings as table serverpod_runtime_settings
--
CREATE TABLE "serverpod_runtime_settings" (
"id" bigserial PRIMARY KEY,
"logSettings" json NOT NULL,
"logSettingsOverrides" json NOT NULL,
"logServiceCalls" boolean NOT NULL,
"logMalformedCalls" boolean NOT NULL
);
--
-- Class SessionLogEntry as table serverpod_session_log
--
CREATE TABLE "serverpod_session_log" (
"id" bigserial PRIMARY KEY,
"serverId" text NOT NULL,
"time" timestamp without time zone NOT NULL,
"module" text,
"endpoint" text,
"method" text,
"duration" double precision,
"numQueries" bigint,
"slow" boolean,
"error" text,
"stackTrace" text,
"authenticatedUserId" bigint,
"userId" text,
"isOpen" boolean,
"touched" timestamp without time zone NOT NULL
);
-- Indexes
CREATE INDEX "serverpod_session_log_serverid_idx" ON "serverpod_session_log" USING btree ("serverId");
CREATE INDEX "serverpod_session_log_time_idx" ON "serverpod_session_log" USING btree ("time");
CREATE INDEX "serverpod_session_log_touched_idx" ON "serverpod_session_log" USING btree ("touched");
CREATE INDEX "serverpod_session_log_isopen_idx" ON "serverpod_session_log" USING btree ("isOpen");
--
-- Class AnonymousAccount as table serverpod_auth_idp_anonymous_account
--
CREATE TABLE "serverpod_auth_idp_anonymous_account" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid_v7(),
"authUserId" uuid NOT NULL,
"createdAt" timestamp without time zone NOT NULL
);
--
-- Class AppleAccount as table serverpod_auth_idp_apple_account
--
CREATE TABLE "serverpod_auth_idp_apple_account" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid_v7(),
"userIdentifier" text NOT NULL,
"refreshToken" text NOT NULL,
"refreshTokenRequestedWithBundleIdentifier" boolean NOT NULL,
"lastRefreshedAt" timestamp without time zone NOT NULL DEFAULT CURRENT_TIMESTAMP,
"authUserId" uuid NOT NULL,
"createdAt" timestamp without time zone NOT NULL,
"email" text,
"isEmailVerified" boolean,
"isPrivateEmail" boolean,
"firstName" text,
"lastName" text
);
-- Indexes
CREATE UNIQUE INDEX "serverpod_auth_apple_account_identifier" ON "serverpod_auth_idp_apple_account" USING btree ("userIdentifier");
--
-- Class EmailAccount as table serverpod_auth_idp_email_account
--
CREATE TABLE "serverpod_auth_idp_email_account" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid_v7(),
"authUserId" uuid NOT NULL,
"createdAt" timestamp without time zone NOT NULL,
"email" text NOT NULL,
"passwordHash" text NOT NULL
);
-- Indexes
CREATE UNIQUE INDEX "serverpod_auth_idp_email_account_email" ON "serverpod_auth_idp_email_account" USING btree ("email");
--
-- Class EmailAccountPasswordResetRequest as table serverpod_auth_idp_email_account_password_reset_request
--
CREATE TABLE "serverpod_auth_idp_email_account_password_reset_request" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid_v7(),
"emailAccountId" uuid NOT NULL,
"createdAt" timestamp without time zone NOT NULL DEFAULT CURRENT_TIMESTAMP,
"challengeId" uuid NOT NULL,
"setPasswordChallengeId" uuid
);
--
-- Class EmailAccountRequest as table serverpod_auth_idp_email_account_request
--
CREATE TABLE "serverpod_auth_idp_email_account_request" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid_v7(),
"createdAt" timestamp without time zone NOT NULL DEFAULT CURRENT_TIMESTAMP,
"email" text NOT NULL,
"challengeId" uuid NOT NULL,
"createAccountChallengeId" uuid
);
-- Indexes
CREATE UNIQUE INDEX "serverpod_auth_idp_email_account_request_email" ON "serverpod_auth_idp_email_account_request" USING btree ("email");
--
-- Class FacebookAccount as table serverpod_auth_idp_facebook_account
--
CREATE TABLE "serverpod_auth_idp_facebook_account" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid_v7(),
"authUserId" uuid NOT NULL,
"createdAt" timestamp without time zone NOT NULL,
"userIdentifier" text NOT NULL,
"email" text,
"fullName" text,
"firstName" text,
"lastName" text
);
-- Indexes
CREATE UNIQUE INDEX "serverpod_auth_facebook_account_user_identifier" ON "serverpod_auth_idp_facebook_account" USING btree ("userIdentifier");
--
-- Class FirebaseAccount as table serverpod_auth_idp_firebase_account
--
CREATE TABLE "serverpod_auth_idp_firebase_account" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid_v7(),
"authUserId" uuid NOT NULL,
"created" timestamp without time zone NOT NULL,
"email" text,
"phone" text,
"userIdentifier" text NOT NULL
);
-- Indexes
CREATE UNIQUE INDEX "serverpod_auth_firebase_account_user_identifier" ON "serverpod_auth_idp_firebase_account" USING btree ("userIdentifier");
--
-- Class GitHubAccount as table serverpod_auth_idp_github_account
--
CREATE TABLE "serverpod_auth_idp_github_account" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid_v7(),
"authUserId" uuid NOT NULL,
"userIdentifier" text NOT NULL,
"email" text,
"created" timestamp without time zone NOT NULL
);
-- Indexes
CREATE UNIQUE INDEX "serverpod_auth_github_account_user_identifier" ON "serverpod_auth_idp_github_account" USING btree ("userIdentifier");
--
-- Class GoogleAccount as table serverpod_auth_idp_google_account
--
CREATE TABLE "serverpod_auth_idp_google_account" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid_v7(),
"authUserId" uuid NOT NULL,
"created" timestamp without time zone NOT NULL,
"email" text NOT NULL,
"userIdentifier" text NOT NULL
);
-- Indexes
CREATE UNIQUE INDEX "serverpod_auth_google_account_user_identifier" ON "serverpod_auth_idp_google_account" USING btree ("userIdentifier");
--
-- Class MicrosoftAccount as table serverpod_auth_idp_microsoft_account
--
CREATE TABLE "serverpod_auth_idp_microsoft_account" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid_v7(),
"authUserId" uuid NOT NULL,
"userIdentifier" text NOT NULL,
"email" text,
"created" timestamp without time zone NOT NULL
);
-- Indexes
CREATE UNIQUE INDEX "serverpod_auth_microsoft_account_user_identifier" ON "serverpod_auth_idp_microsoft_account" USING btree ("userIdentifier");
--
-- Class PasskeyAccount as table serverpod_auth_idp_passkey_account
--
CREATE TABLE "serverpod_auth_idp_passkey_account" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid_v7(),
"authUserId" uuid NOT NULL,
"createdAt" timestamp without time zone NOT NULL,
"keyId" bytea NOT NULL,
"keyIdBase64" text NOT NULL,
"clientDataJSON" bytea NOT NULL,
"attestationObject" bytea NOT NULL,
"originalChallenge" bytea NOT NULL
);
-- Indexes
CREATE UNIQUE INDEX "serverpod_auth_idp_passkey_account_key_id_base64" ON "serverpod_auth_idp_passkey_account" USING btree ("keyIdBase64");
--
-- Class PasskeyChallenge as table serverpod_auth_idp_passkey_challenge
--
CREATE TABLE "serverpod_auth_idp_passkey_challenge" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid_v7(),
"createdAt" timestamp without time zone NOT NULL,
"challenge" bytea NOT NULL
);
--
-- Class RateLimitedRequestAttempt as table serverpod_auth_idp_rate_limited_request_attempt
--
CREATE TABLE "serverpod_auth_idp_rate_limited_request_attempt" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid_v7(),
"domain" text NOT NULL,
"source" text NOT NULL,
"key" text NOT NULL,
"ipAddress" text,
"attemptedAt" timestamp without time zone NOT NULL,
"extraData" json
);
-- Indexes
CREATE INDEX "serverpod_auth_idp_rate_limited_request_attempt_composite" ON "serverpod_auth_idp_rate_limited_request_attempt" USING btree ("domain", "source", "key", "attemptedAt");
--
-- Class SecretChallenge as table serverpod_auth_idp_secret_challenge
--
CREATE TABLE "serverpod_auth_idp_secret_challenge" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid_v7(),
"challengeCodeHash" text NOT NULL
);
--
-- Class RefreshToken as table serverpod_auth_core_jwt_refresh_token
--
CREATE TABLE "serverpod_auth_core_jwt_refresh_token" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid_v7(),
"authUserId" uuid NOT NULL,
"scopeNames" json NOT NULL,
"extraClaims" text,
"method" text NOT NULL,
"fixedSecret" bytea NOT NULL,
"rotatingSecretHash" text NOT NULL,
"lastUpdatedAt" timestamp without time zone NOT NULL DEFAULT CURRENT_TIMESTAMP,
"createdAt" timestamp without time zone NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- Indexes
CREATE INDEX "serverpod_auth_core_jwt_refresh_token_last_updated_at" ON "serverpod_auth_core_jwt_refresh_token" USING btree ("lastUpdatedAt");
--
-- Class UserProfile as table serverpod_auth_core_profile
--
CREATE TABLE "serverpod_auth_core_profile" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid_v7(),
"authUserId" uuid NOT NULL,
"userName" text,
"fullName" text,
"email" text,
"createdAt" timestamp without time zone NOT NULL DEFAULT CURRENT_TIMESTAMP,
"imageId" uuid
);
-- Indexes
CREATE UNIQUE INDEX "serverpod_auth_profile_user_profile_email_auth_user_id" ON "serverpod_auth_core_profile" USING btree ("authUserId");
--
-- Class UserProfileImage as table serverpod_auth_core_profile_image
--
CREATE TABLE "serverpod_auth_core_profile_image" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid_v7(),
"userProfileId" uuid NOT NULL,
"createdAt" timestamp without time zone NOT NULL DEFAULT CURRENT_TIMESTAMP,
"storageId" text NOT NULL,
"path" text NOT NULL,
"url" text NOT NULL
);
--
-- Class ServerSideSession as table serverpod_auth_core_session
--
CREATE TABLE "serverpod_auth_core_session" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid_v7(),
"authUserId" uuid NOT NULL,
"scopeNames" json NOT NULL,
"createdAt" timestamp without time zone NOT NULL DEFAULT CURRENT_TIMESTAMP,
"lastUsedAt" timestamp without time zone NOT NULL DEFAULT CURRENT_TIMESTAMP,
"expiresAt" timestamp without time zone,
"expireAfterUnusedFor" bigint,
"sessionKeyHash" bytea NOT NULL,
"sessionKeySalt" bytea NOT NULL,
"method" text NOT NULL
);
--
-- Class AuthUser as table serverpod_auth_core_user
--
CREATE TABLE "serverpod_auth_core_user" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid_v7(),
"createdAt" timestamp without time zone NOT NULL,
"scopeNames" json NOT NULL,
"blocked" boolean NOT NULL
);
--
-- Foreign relations for "email_code_request" table
--
ALTER TABLE ONLY "email_code_request"
ADD CONSTRAINT "email_code_request_fk_0"
FOREIGN KEY("challengeId")
REFERENCES "serverpod_auth_idp_secret_challenge"("id")
ON DELETE CASCADE
ON UPDATE NO ACTION;
ALTER TABLE ONLY "email_code_request"
ADD CONSTRAINT "email_code_request_fk_1"
FOREIGN KEY("switchChallengeId")
REFERENCES "serverpod_auth_idp_secret_challenge"("id")
ON DELETE CASCADE
ON UPDATE NO ACTION;
--
-- Foreign relations for "serverpod_future_call_claim" table
--
ALTER TABLE ONLY "serverpod_future_call_claim"
ADD CONSTRAINT "serverpod_future_call_claim_fk_0"
FOREIGN KEY("futureCallId")
REFERENCES "serverpod_future_call"("id")
ON DELETE CASCADE
ON UPDATE NO ACTION;
--
-- Foreign relations for "serverpod_log" table
--
ALTER TABLE ONLY "serverpod_log"
ADD CONSTRAINT "serverpod_log_fk_0"
FOREIGN KEY("sessionLogId")
REFERENCES "serverpod_session_log"("id")
ON DELETE CASCADE
ON UPDATE NO ACTION;
--
-- Foreign relations for "serverpod_message_log" table
--
ALTER TABLE ONLY "serverpod_message_log"
ADD CONSTRAINT "serverpod_message_log_fk_0"
FOREIGN KEY("sessionLogId")
REFERENCES "serverpod_session_log"("id")
ON DELETE CASCADE
ON UPDATE NO ACTION;
--
-- Foreign relations for "serverpod_query_log" table
--
ALTER TABLE ONLY "serverpod_query_log"
ADD CONSTRAINT "serverpod_query_log_fk_0"
FOREIGN KEY("sessionLogId")
REFERENCES "serverpod_session_log"("id")
ON DELETE CASCADE
ON UPDATE NO ACTION;
--
-- Foreign relations for "serverpod_auth_idp_anonymous_account" table
--
ALTER TABLE ONLY "serverpod_auth_idp_anonymous_account"
ADD CONSTRAINT "serverpod_auth_idp_anonymous_account_fk_0"
FOREIGN KEY("authUserId")
REFERENCES "serverpod_auth_core_user"("id")
ON DELETE CASCADE
ON UPDATE NO ACTION;
--
-- Foreign relations for "serverpod_auth_idp_apple_account" table
--
ALTER TABLE ONLY "serverpod_auth_idp_apple_account"
ADD CONSTRAINT "serverpod_auth_idp_apple_account_fk_0"
FOREIGN KEY("authUserId")
REFERENCES "serverpod_auth_core_user"("id")
ON DELETE CASCADE
ON UPDATE NO ACTION;
--
-- Foreign relations for "serverpod_auth_idp_email_account" table
--
ALTER TABLE ONLY "serverpod_auth_idp_email_account"
ADD CONSTRAINT "serverpod_auth_idp_email_account_fk_0"
FOREIGN KEY("authUserId")
REFERENCES "serverpod_auth_core_user"("id")
ON DELETE CASCADE
ON UPDATE NO ACTION;
--
-- Foreign relations for "serverpod_auth_idp_email_account_password_reset_request" table
--
ALTER TABLE ONLY "serverpod_auth_idp_email_account_password_reset_request"
ADD CONSTRAINT "serverpod_auth_idp_email_account_password_reset_request_fk_0"
FOREIGN KEY("emailAccountId")
REFERENCES "serverpod_auth_idp_email_account"("id")
ON DELETE CASCADE
ON UPDATE NO ACTION;
ALTER TABLE ONLY "serverpod_auth_idp_email_account_password_reset_request"
ADD CONSTRAINT "serverpod_auth_idp_email_account_password_reset_request_fk_1"
FOREIGN KEY("challengeId")
REFERENCES "serverpod_auth_idp_secret_challenge"("id")
ON DELETE CASCADE
ON UPDATE NO ACTION;
ALTER TABLE ONLY "serverpod_auth_idp_email_account_password_reset_request"
ADD CONSTRAINT "serverpod_auth_idp_email_account_password_reset_request_fk_2"
FOREIGN KEY("setPasswordChallengeId")
REFERENCES "serverpod_auth_idp_secret_challenge"("id")
ON DELETE CASCADE
ON UPDATE NO ACTION;
--
-- Foreign relations for "serverpod_auth_idp_email_account_request" table
--
ALTER TABLE ONLY "serverpod_auth_idp_email_account_request"
ADD CONSTRAINT "serverpod_auth_idp_email_account_request_fk_0"
FOREIGN KEY("challengeId")
REFERENCES "serverpod_auth_idp_secret_challenge"("id")
ON DELETE CASCADE
ON UPDATE NO ACTION;
ALTER TABLE ONLY "serverpod_auth_idp_email_account_request"
ADD CONSTRAINT "serverpod_auth_idp_email_account_request_fk_1"
FOREIGN KEY("createAccountChallengeId")
REFERENCES "serverpod_auth_idp_secret_challenge"("id")
ON DELETE CASCADE
ON UPDATE NO ACTION;
--
-- Foreign relations for "serverpod_auth_idp_facebook_account" table
--
ALTER TABLE ONLY "serverpod_auth_idp_facebook_account"
ADD CONSTRAINT "serverpod_auth_idp_facebook_account_fk_0"
FOREIGN KEY("authUserId")
REFERENCES "serverpod_auth_core_user"("id")
ON DELETE CASCADE
ON UPDATE NO ACTION;
--
-- Foreign relations for "serverpod_auth_idp_firebase_account" table
--
ALTER TABLE ONLY "serverpod_auth_idp_firebase_account"
ADD CONSTRAINT "serverpod_auth_idp_firebase_account_fk_0"
FOREIGN KEY("authUserId")
REFERENCES "serverpod_auth_core_user"("id")
ON DELETE CASCADE
ON UPDATE NO ACTION;
--
-- Foreign relations for "serverpod_auth_idp_github_account" table
--
ALTER TABLE ONLY "serverpod_auth_idp_github_account"
ADD CONSTRAINT "serverpod_auth_idp_github_account_fk_0"
FOREIGN KEY("authUserId")
REFERENCES "serverpod_auth_core_user"("id")
ON DELETE CASCADE
ON UPDATE NO ACTION;
--
-- Foreign relations for "serverpod_auth_idp_google_account" table
--
ALTER TABLE ONLY "serverpod_auth_idp_google_account"
ADD CONSTRAINT "serverpod_auth_idp_google_account_fk_0"
FOREIGN KEY("authUserId")
REFERENCES "serverpod_auth_core_user"("id")
ON DELETE CASCADE
ON UPDATE NO ACTION;
--
-- Foreign relations for "serverpod_auth_idp_microsoft_account" table
--
ALTER TABLE ONLY "serverpod_auth_idp_microsoft_account"
ADD CONSTRAINT "serverpod_auth_idp_microsoft_account_fk_0"
FOREIGN KEY("authUserId")
REFERENCES "serverpod_auth_core_user"("id")
ON DELETE CASCADE
ON UPDATE NO ACTION;
--
-- Foreign relations for "serverpod_auth_idp_passkey_account" table
--
ALTER TABLE ONLY "serverpod_auth_idp_passkey_account"
ADD CONSTRAINT "serverpod_auth_idp_passkey_account_fk_0"
FOREIGN KEY("authUserId")
REFERENCES "serverpod_auth_core_user"("id")
ON DELETE CASCADE
ON UPDATE NO ACTION;
--
-- Foreign relations for "serverpod_auth_core_jwt_refresh_token" table
--
ALTER TABLE ONLY "serverpod_auth_core_jwt_refresh_token"
ADD CONSTRAINT "serverpod_auth_core_jwt_refresh_token_fk_0"
FOREIGN KEY("authUserId")
REFERENCES "serverpod_auth_core_user"("id")
ON DELETE CASCADE
ON UPDATE NO ACTION;
--
-- Foreign relations for "serverpod_auth_core_profile" table
--
ALTER TABLE ONLY "serverpod_auth_core_profile"
ADD CONSTRAINT "serverpod_auth_core_profile_fk_0"
FOREIGN KEY("authUserId")
REFERENCES "serverpod_auth_core_user"("id")
ON DELETE CASCADE
ON UPDATE NO ACTION;
ALTER TABLE ONLY "serverpod_auth_core_profile"
ADD CONSTRAINT "serverpod_auth_core_profile_fk_1"
FOREIGN KEY("imageId")
REFERENCES "serverpod_auth_core_profile_image"("id")
ON DELETE NO ACTION
ON UPDATE NO ACTION
DEFERRABLE INITIALLY DEFERRED;
--
-- Foreign relations for "serverpod_auth_core_profile_image" table
--
ALTER TABLE ONLY "serverpod_auth_core_profile_image"
ADD CONSTRAINT "serverpod_auth_core_profile_image_fk_0"
FOREIGN KEY("userProfileId")
REFERENCES "serverpod_auth_core_profile"("id")
ON DELETE CASCADE
ON UPDATE NO ACTION;
--
-- Foreign relations for "serverpod_auth_core_session" table
--
ALTER TABLE ONLY "serverpod_auth_core_session"
ADD CONSTRAINT "serverpod_auth_core_session_fk_0"
FOREIGN KEY("authUserId")
REFERENCES "serverpod_auth_core_user"("id")
ON DELETE CASCADE
ON UPDATE NO ACTION;
--
-- MIGRATION VERSION FOR halkyon_base
--
INSERT INTO "serverpod_migrations" ("module", "version", "timestamp")
VALUES ('halkyon_base', '20261020185619505-recovery-proof-key', now())
ON CONFLICT ("module")
DO UPDATE SET "version" = '20261020185619505-recovery-proof-key', "timestamp" = now();
--
-- MIGRATION VERSION FOR serverpod
--
INSERT INTO "serverpod_migrations" ("module", "version", "timestamp")
VALUES ('serverpod', '20260824182259319', now())
ON CONFLICT ("module")
DO UPDATE SET "version" = '20260824182259319', "timestamp" = now();
--
-- MIGRATION VERSION FOR serverpod_auth_idp
--
INSERT INTO "serverpod_migrations" ("module", "version", "timestamp")
VALUES ('serverpod_auth_idp', '20260924105404509', now())
ON CONFLICT ("module")
DO UPDATE SET "version" = '20260924105404509', "timestamp" = now();
--
-- MIGRATION VERSION FOR serverpod_auth_core
--
INSERT INTO "serverpod_migrations" ("module", "version", "timestamp")
VALUES ('serverpod_auth_core', '20260924105232991', now())
ON CONFLICT ("module")
DO UPDATE SET "version" = '20260924105232991', "timestamp" = now();
COMMIT;

View file

@ -0,0 +1,945 @@
{
"__className__": "serverpod.DatabaseDefinition",
"schemaVersion": 2,
"moduleName": "halkyon_base",
"tables": [
{
"__className__": "serverpod.TableDefinition",
"name": "account_deletion_notice",
"dartName": "AccountDeletionNotice",
"module": "halkyon_base",
"schema": "public",
"columns": [
{
"__className__": "serverpod.ColumnDefinition",
"name": "id",
"fieldName": "id",
"columnType": 6,
"isNullable": false,
"columnDefault": "serial",
"dartType": "int?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "account",
"fieldName": "account",
"columnType": 7,
"isNullable": false,
"dartType": "UuidValue"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "member",
"fieldName": "member",
"columnType": 0,
"isNullable": false,
"dartType": "String"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "createdAt",
"fieldName": "createdAt",
"columnType": 4,
"isNullable": false,
"dartType": "DateTime"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "attempts",
"fieldName": "attempts",
"columnType": 6,
"isNullable": false,
"dartType": "int"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "lastAttemptAt",
"fieldName": "lastAttemptAt",
"columnType": 4,
"isNullable": true,
"dartType": "DateTime?"
}
],
"foreignKeys": [],
"indexes": [
{
"__className__": "serverpod.IndexDefinition",
"indexName": "account_deletion_notice_idx",
"elements": [
{
"__className__": "serverpod.IndexElementDefinition",
"type": 0,
"definition": "account"
},
{
"__className__": "serverpod.IndexElementDefinition",
"type": 0,
"definition": "member"
}
],
"type": "btree",
"isUnique": true,
"isPrimary": false
}
],
"managed": true
},
{
"__className__": "serverpod.TableDefinition",
"name": "account_password",
"dartName": "AccountPassword",
"module": "halkyon_base",
"schema": "public",
"columns": [
{
"__className__": "serverpod.ColumnDefinition",
"name": "id",
"fieldName": "id",
"columnType": 6,
"isNullable": false,
"columnDefault": "serial",
"dartType": "int?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "authUserId",
"fieldName": "authUserId",
"columnType": 7,
"isNullable": false,
"dartType": "UuidValue"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "salt",
"fieldName": "salt",
"columnType": 5,
"isNullable": false,
"dartType": "dart:typed_data:ByteData"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "kdfAlgorithm",
"fieldName": "kdfAlgorithm",
"columnType": 0,
"isNullable": false,
"dartType": "String"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "kdfOpsLimit",
"fieldName": "kdfOpsLimit",
"columnType": 6,
"isNullable": false,
"dartType": "int"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "kdfMemLimit",
"fieldName": "kdfMemLimit",
"columnType": 6,
"isNullable": false,
"dartType": "int"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "verifier",
"fieldName": "verifier",
"columnType": 5,
"isNullable": false,
"dartType": "dart:typed_data:ByteData"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "encryptionPublicKey",
"fieldName": "encryptionPublicKey",
"columnType": 5,
"isNullable": false,
"dartType": "dart:typed_data:ByteData"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "signingPublicKey",
"fieldName": "signingPublicKey",
"columnType": 5,
"isNullable": false,
"dartType": "dart:typed_data:ByteData"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "passwordKeysNonce",
"fieldName": "passwordKeysNonce",
"columnType": 5,
"isNullable": false,
"dartType": "dart:typed_data:ByteData"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "passwordKeysCipherText",
"fieldName": "passwordKeysCipherText",
"columnType": 5,
"isNullable": false,
"dartType": "dart:typed_data:ByteData"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "recoveryKeysNonce",
"fieldName": "recoveryKeysNonce",
"columnType": 5,
"isNullable": false,
"dartType": "dart:typed_data:ByteData"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "recoveryKeysCipherText",
"fieldName": "recoveryKeysCipherText",
"columnType": 5,
"isNullable": false,
"dartType": "dart:typed_data:ByteData"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "recoveryProofKey",
"fieldName": "recoveryProofKey",
"columnType": 5,
"isNullable": true,
"dartType": "dart:typed_data:ByteData?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "createdAt",
"fieldName": "createdAt",
"columnType": 4,
"isNullable": false,
"dartType": "DateTime"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "updatedAt",
"fieldName": "updatedAt",
"columnType": 4,
"isNullable": false,
"dartType": "DateTime"
}
],
"foreignKeys": [],
"indexes": [
{
"__className__": "serverpod.IndexDefinition",
"indexName": "account_password_person_idx",
"elements": [
{
"__className__": "serverpod.IndexElementDefinition",
"type": 0,
"definition": "authUserId"
}
],
"type": "btree",
"isUnique": true,
"isPrimary": false
}
],
"managed": true
},
{
"__className__": "serverpod.TableDefinition",
"name": "account_reset",
"dartName": "AccountReset",
"module": "halkyon_base",
"schema": "public",
"columns": [
{
"__className__": "serverpod.ColumnDefinition",
"name": "id",
"fieldName": "id",
"columnType": 6,
"isNullable": false,
"columnDefault": "serial",
"dartType": "int?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "authUserId",
"fieldName": "authUserId",
"columnType": 7,
"isNullable": false,
"dartType": "UuidValue"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "kind",
"fieldName": "kind",
"columnType": 0,
"isNullable": false,
"dartType": "protocol:AccountResetKind"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "takesEffectAt",
"fieldName": "takesEffectAt",
"columnType": 4,
"isNullable": false,
"dartType": "DateTime"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "createdAt",
"fieldName": "createdAt",
"columnType": 4,
"isNullable": false,
"dartType": "DateTime"
}
],
"foreignKeys": [],
"indexes": [
{
"__className__": "serverpod.IndexDefinition",
"indexName": "account_reset_person_kind_idx",
"elements": [
{
"__className__": "serverpod.IndexElementDefinition",
"type": 0,
"definition": "authUserId"
},
{
"__className__": "serverpod.IndexElementDefinition",
"type": 0,
"definition": "kind"
}
],
"type": "btree",
"isUnique": true,
"isPrimary": false
}
],
"managed": true
},
{
"__className__": "serverpod.TableDefinition",
"name": "email_code_request",
"dartName": "EmailCodeRequest",
"module": "halkyon_base",
"schema": "public",
"columns": [
{
"__className__": "serverpod.ColumnDefinition",
"name": "id",
"fieldName": "id",
"columnType": 7,
"isNullable": false,
"columnDefault": "random_v7",
"dartType": "UuidValue?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "addressHash",
"fieldName": "addressHash",
"columnType": 0,
"isNullable": false,
"dartType": "String"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "challengeId",
"fieldName": "challengeId",
"columnType": 7,
"isNullable": false,
"dartType": "UuidValue"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "switchChallengeId",
"fieldName": "switchChallengeId",
"columnType": 7,
"isNullable": true,
"dartType": "UuidValue?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "createdAt",
"fieldName": "createdAt",
"columnType": 4,
"isNullable": false,
"dartType": "DateTime"
}
],
"foreignKeys": [
{
"__className__": "serverpod.ForeignKeyDefinition",
"constraintName": "email_code_request_fk_0",
"columns": [
"challengeId"
],
"referenceTable": "serverpod_auth_idp_secret_challenge",
"referenceTableSchema": "public",
"referenceColumns": [
"id"
],
"onUpdate": 3,
"onDelete": 4
},
{
"__className__": "serverpod.ForeignKeyDefinition",
"constraintName": "email_code_request_fk_1",
"columns": [
"switchChallengeId"
],
"referenceTable": "serverpod_auth_idp_secret_challenge",
"referenceTableSchema": "public",
"referenceColumns": [
"id"
],
"onUpdate": 3,
"onDelete": 4
}
],
"indexes": [
{
"__className__": "serverpod.IndexDefinition",
"indexName": "email_code_request_address_idx",
"elements": [
{
"__className__": "serverpod.IndexElementDefinition",
"type": 0,
"definition": "addressHash"
}
],
"type": "btree",
"isUnique": false,
"isPrimary": false
}
],
"managed": true
},
{
"__className__": "serverpod.TableDefinition",
"name": "email_identity",
"dartName": "EmailIdentity",
"module": "halkyon_base",
"schema": "public",
"columns": [
{
"__className__": "serverpod.ColumnDefinition",
"name": "id",
"fieldName": "id",
"columnType": 6,
"isNullable": false,
"columnDefault": "serial",
"dartType": "int?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "authUserId",
"fieldName": "authUserId",
"columnType": 7,
"isNullable": false,
"dartType": "UuidValue"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "addressHash",
"fieldName": "addressHash",
"columnType": 0,
"isNullable": false,
"dartType": "String"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "createdAt",
"fieldName": "createdAt",
"columnType": 4,
"isNullable": false,
"dartType": "DateTime"
}
],
"foreignKeys": [],
"indexes": [
{
"__className__": "serverpod.IndexDefinition",
"indexName": "email_identity_person_idx",
"elements": [
{
"__className__": "serverpod.IndexElementDefinition",
"type": 0,
"definition": "authUserId"
}
],
"type": "btree",
"isUnique": true,
"isPrimary": false
},
{
"__className__": "serverpod.IndexDefinition",
"indexName": "email_identity_address_idx",
"elements": [
{
"__className__": "serverpod.IndexElementDefinition",
"type": 0,
"definition": "addressHash"
}
],
"type": "btree",
"isUnique": true,
"isPrimary": false
}
],
"managed": true
},
{
"__className__": "serverpod.TableDefinition",
"name": "public_profile",
"dartName": "PublicProfile",
"module": "halkyon_base",
"schema": "public",
"columns": [
{
"__className__": "serverpod.ColumnDefinition",
"name": "id",
"fieldName": "id",
"columnType": 6,
"isNullable": false,
"columnDefault": "serial",
"dartType": "int?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "authUserId",
"fieldName": "authUserId",
"columnType": 7,
"isNullable": false,
"dartType": "UuidValue"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "handle",
"fieldName": "handle",
"columnType": 0,
"isNullable": false,
"dartType": "String"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "handleKey",
"fieldName": "handleKey",
"columnType": 0,
"isNullable": false,
"dartType": "String"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "displayName",
"fieldName": "displayName",
"columnType": 0,
"isNullable": false,
"dartType": "String"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "bio",
"fieldName": "bio",
"columnType": 0,
"isNullable": false,
"dartType": "String"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "links",
"fieldName": "links",
"columnType": 8,
"isNullable": false,
"dartType": "List<String>"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "verifiedLinks",
"fieldName": "verifiedLinks",
"columnType": 8,
"isNullable": true,
"dartType": "List<String>?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "avatarHash",
"fieldName": "avatarHash",
"columnType": 0,
"isNullable": true,
"dartType": "String?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "firstPublishedAt",
"fieldName": "firstPublishedAt",
"columnType": 4,
"isNullable": true,
"dartType": "DateTime?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "updatedAt",
"fieldName": "updatedAt",
"columnType": 4,
"isNullable": false,
"dartType": "DateTime"
}
],
"foreignKeys": [],
"indexes": [
{
"__className__": "serverpod.IndexDefinition",
"indexName": "public_profile_person_idx",
"elements": [
{
"__className__": "serverpod.IndexElementDefinition",
"type": 0,
"definition": "authUserId"
}
],
"type": "btree",
"isUnique": true,
"isPrimary": false
},
{
"__className__": "serverpod.IndexDefinition",
"indexName": "public_profile_handle_idx",
"elements": [
{
"__className__": "serverpod.IndexElementDefinition",
"type": 0,
"definition": "handleKey"
}
],
"type": "btree",
"isUnique": true,
"isPrimary": false
}
],
"managed": true
},
{
"__className__": "serverpod.TableDefinition",
"name": "recovery_code",
"dartName": "RecoveryCode",
"module": "halkyon_base",
"schema": "public",
"columns": [
{
"__className__": "serverpod.ColumnDefinition",
"name": "id",
"fieldName": "id",
"columnType": 6,
"isNullable": false,
"columnDefault": "serial",
"dartType": "int?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "authUserId",
"fieldName": "authUserId",
"columnType": 7,
"isNullable": false,
"dartType": "UuidValue"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "hash",
"fieldName": "hash",
"columnType": 0,
"isNullable": false,
"dartType": "String"
}
],
"foreignKeys": [],
"indexes": [
{
"__className__": "serverpod.IndexDefinition",
"indexName": "recovery_code_person_idx",
"elements": [
{
"__className__": "serverpod.IndexElementDefinition",
"type": 0,
"definition": "authUserId"
}
],
"type": "btree",
"isUnique": false,
"isPrimary": false
}
],
"managed": true
},
{
"__className__": "serverpod.TableDefinition",
"name": "sessions_ended_notice",
"dartName": "SessionsEndedNotice",
"module": "halkyon_base",
"schema": "public",
"columns": [
{
"__className__": "serverpod.ColumnDefinition",
"name": "id",
"fieldName": "id",
"columnType": 6,
"isNullable": false,
"columnDefault": "serial",
"dartType": "int?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "account",
"fieldName": "account",
"columnType": 7,
"isNullable": false,
"dartType": "UuidValue"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "member",
"fieldName": "member",
"columnType": 0,
"isNullable": false,
"dartType": "String"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "endedBefore",
"fieldName": "endedBefore",
"columnType": 4,
"isNullable": false,
"dartType": "DateTime"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "keepSession",
"fieldName": "keepSession",
"columnType": 7,
"isNullable": true,
"dartType": "UuidValue?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "attempts",
"fieldName": "attempts",
"columnType": 6,
"isNullable": false,
"dartType": "int"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "lastAttemptAt",
"fieldName": "lastAttemptAt",
"columnType": 4,
"isNullable": true,
"dartType": "DateTime?"
}
],
"foreignKeys": [],
"indexes": [
{
"__className__": "serverpod.IndexDefinition",
"indexName": "sessions_ended_notice_idx",
"elements": [
{
"__className__": "serverpod.IndexElementDefinition",
"type": 0,
"definition": "account"
},
{
"__className__": "serverpod.IndexElementDefinition",
"type": 0,
"definition": "member"
}
],
"type": "btree",
"isUnique": true,
"isPrimary": false
}
],
"managed": true
},
{
"__className__": "serverpod.TableDefinition",
"name": "sign_in_challenge",
"dartName": "SignInChallenge",
"module": "halkyon_base",
"schema": "public",
"columns": [
{
"__className__": "serverpod.ColumnDefinition",
"name": "id",
"fieldName": "id",
"columnType": 7,
"isNullable": false,
"columnDefault": "random_v7",
"dartType": "UuidValue?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "authUserId",
"fieldName": "authUserId",
"columnType": 7,
"isNullable": true,
"dartType": "UuidValue?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "purpose",
"fieldName": "purpose",
"columnType": 0,
"isNullable": false,
"dartType": "protocol:SignInChallengePurpose"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "addressHash",
"fieldName": "addressHash",
"columnType": 0,
"isNullable": false,
"dartType": "String"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "secret",
"fieldName": "secret",
"columnType": 0,
"isNullable": false,
"dartType": "String"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "serverValue",
"fieldName": "serverValue",
"columnType": 0,
"isNullable": true,
"dartType": "String?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "createdAt",
"fieldName": "createdAt",
"columnType": 4,
"isNullable": false,
"dartType": "DateTime"
}
],
"foreignKeys": [],
"indexes": [
{
"__className__": "serverpod.IndexDefinition",
"indexName": "sign_in_challenge_created_idx",
"elements": [
{
"__className__": "serverpod.IndexElementDefinition",
"type": 0,
"definition": "createdAt"
}
],
"type": "btree",
"isUnique": false,
"isPrimary": false
}
],
"managed": true
},
{
"__className__": "serverpod.TableDefinition",
"name": "totp_authenticator",
"dartName": "TotpAuthenticator",
"module": "halkyon_base",
"schema": "public",
"columns": [
{
"__className__": "serverpod.ColumnDefinition",
"name": "id",
"fieldName": "id",
"columnType": 7,
"isNullable": false,
"columnDefault": "random_v7",
"dartType": "UuidValue?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "authUserId",
"fieldName": "authUserId",
"columnType": 7,
"isNullable": false,
"dartType": "UuidValue"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "name",
"fieldName": "name",
"columnType": 0,
"isNullable": false,
"dartType": "String"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "secret",
"fieldName": "secret",
"columnType": 5,
"isNullable": false,
"dartType": "dart:typed_data:ByteData"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "confirmedAt",
"fieldName": "confirmedAt",
"columnType": 4,
"isNullable": true,
"dartType": "DateTime?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "lastStep",
"fieldName": "lastStep",
"columnType": 6,
"isNullable": true,
"dartType": "int?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "createdAt",
"fieldName": "createdAt",
"columnType": 4,
"isNullable": false,
"dartType": "DateTime"
}
],
"foreignKeys": [],
"indexes": [
{
"__className__": "serverpod.IndexDefinition",
"indexName": "totp_authenticator_person_idx",
"elements": [
{
"__className__": "serverpod.IndexElementDefinition",
"type": 0,
"definition": "authUserId"
}
],
"type": "btree",
"isUnique": false,
"isPrimary": false
}
],
"managed": true
}
],
"installedModules": [
{
"__className__": "serverpod.DatabaseMigrationVersion",
"module": "serverpod",
"version": "20260824182259319"
},
{
"__className__": "serverpod.DatabaseMigrationVersion",
"module": "serverpod_auth_idp",
"version": "20260924105404509"
},
{
"__className__": "serverpod.DatabaseMigrationVersion",
"module": "serverpod_auth_core",
"version": "20260924105232991"
}
],
"migrationApiVersion": 1
}

View file

@ -0,0 +1,33 @@
{
"__className__": "serverpod.DatabaseMigration",
"actions": [
{
"__className__": "serverpod.DatabaseMigrationAction",
"type": "alterTable",
"alterTable": {
"__className__": "serverpod.TableMigration",
"name": "account_password",
"schema": "public",
"addColumns": [
{
"__className__": "serverpod.ColumnDefinition",
"name": "recoveryProofKey",
"fieldName": "recoveryProofKey",
"columnType": 5,
"isNullable": true,
"dartType": "dart:typed_data:ByteData?"
}
],
"deleteColumns": [],
"modifyColumns": [],
"addIndexes": [],
"deleteIndexes": [],
"addForeignKeys": [],
"deleteForeignKeys": [],
"warnings": []
}
}
],
"warnings": [],
"migrationApiVersion": 1
}

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