Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions src/db/sqlite/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,65 @@ function createSchema(d: DatabaseSync): void {
updated_at TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_image_jobs_status ON image_jobs(status, created_at);

-- MLS delivery service (GRYT-1500). Blobs are MLS wire bytes the server can't
-- open. Keyed on server_user_id like conversations, since the groups are theirs.
CREATE TABLE IF NOT EXISTS mls_devices (
server_user_id TEXT NOT NULL,
device_id TEXT NOT NULL,
created_at TEXT NOT NULL,
last_seen_at TEXT NOT NULL,
PRIMARY KEY (server_user_id, device_id)
);

-- data goes to NULL when a package is handed out. The row stays until the
-- retention sweep, so a Welcome naming the ref can still find its device.
CREATE TABLE IF NOT EXISTS mls_key_packages (
key_package_ref TEXT PRIMARY KEY,
server_user_id TEXT NOT NULL,
device_id TEXT NOT NULL,
data BLOB,
last_resort INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL,
claimed_at TEXT
);
CREATE INDEX IF NOT EXISTS idx_mls_key_packages_device
ON mls_key_packages(server_user_id, device_id, last_resort, claimed_at);

-- One group per conversation. head_seq is kept here rather than read off the
-- log, because the log gets swept and a cursor must never be handed out twice.
CREATE TABLE IF NOT EXISTS mls_groups (
group_id TEXT PRIMARY KEY,
conversation_id TEXT NOT NULL UNIQUE,
epoch INTEGER NOT NULL,
head_seq INTEGER NOT NULL,
created_by_server_user_id TEXT NOT NULL,
created_at TEXT NOT NULL
);

CREATE TABLE IF NOT EXISTS mls_log (
group_id TEXT NOT NULL,
seq INTEGER NOT NULL,
kind TEXT NOT NULL,
epoch INTEGER NOT NULL,
sender_server_user_id TEXT NOT NULL,
sender_device_id TEXT NOT NULL,
data BLOB NOT NULL,
created_at TEXT NOT NULL,
PRIMARY KEY (group_id, seq)
);
CREATE INDEX IF NOT EXISTS idx_mls_log_created ON mls_log(created_at);

CREATE TABLE IF NOT EXISTS mls_welcomes (
welcome_id TEXT PRIMARY KEY,
server_user_id TEXT NOT NULL,
device_id TEXT NOT NULL,
group_id TEXT NOT NULL,
data BLOB NOT NULL,
created_at TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_mls_welcomes_device ON mls_welcomes(server_user_id, device_id);
CREATE INDEX IF NOT EXISTS idx_mls_welcomes_created ON mls_welcomes(created_at);
`);
}

Expand Down
2 changes: 2 additions & 0 deletions src/db/sqlite/conversations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { createHash, randomUUID } from "crypto";

import type { ConversationRecord } from "../interfaces";
import { fromIso, fromIsoNullable, getSqliteDb, toIso } from "./connection";
import { dropMlsGroupForConversation } from "./mls";

/**
* Nothing here is cross-server: a DM is filed under `server_user_id`, this
Expand Down Expand Up @@ -262,6 +263,7 @@ export async function purgeOrphanedConversations(): Promise<string[]> {
const deleteMembers = db.prepare(`DELETE FROM conversation_members WHERE conversation_id = ?`);
const deleteConversation = db.prepare(`DELETE FROM conversations WHERE conversation_id = ?`);
for (const id of ids) {
dropMlsGroupForConversation(id);
deleteMessages.run(id);
deleteMembers.run(id);
deleteConversation.run(id);
Expand Down
1 change: 1 addition & 0 deletions src/db/sqlite/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ export * from "./contactPrefs";
export * from "./friends";
export * from "./mentions";
export * from "./webhooks";
export * from "./mls";
1 change: 1 addition & 0 deletions src/db/sqlite/mergeGuest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ describe("merging a guest into an account that is already a member", () => {
const handledApart = new Set([
"users.server_user_id", "roles.server_user_id", "conversation_members.server_user_id",
"mentions.server_user_id", "refresh_tokens.server_user_id",
"mls_devices.server_user_id", "mls_key_packages.server_user_id", "mls_welcomes.server_user_id",
]);
const listed = new Set(AUTHOR_COLUMNS.map(([t, c]) => `${t}.${c}`));
const tables = db.prepare(`SELECT name FROM sqlite_master WHERE type = 'table'`).all() as { name: string }[];
Expand Down
4 changes: 4 additions & 0 deletions src/db/sqlite/mergeGuest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { DatabaseSync } from "node:sqlite";
import type { Reaction } from "../interfaces";
import { getSqliteDb, toIso } from "./connection";
import { carryFriendsForward } from "./friends";
import { carryMlsDevicesForward } from "./mls";

export interface GuestMerge {
guestServerUserId: string;
Expand Down Expand Up @@ -34,6 +35,8 @@ export const AUTHOR_COLUMNS: ReadonlyArray<readonly [table: string, column: stri
["bots", "decided_by_server_user_id"],
["audit_log", "actor_server_user_id"],
["audit_log", "target"],
["mls_groups", "created_by_server_user_id"],
["mls_log", "sender_server_user_id"],
];

interface MemberRow {
Expand Down Expand Up @@ -110,6 +113,7 @@ function mergeInTransaction(
mergeReactions(db, from, to);
carryBlocksForward(db, guestGrytUserId, accountGrytUserId);
carryFriendsForward(db, guestGrytUserId, accountGrytUserId);
carryMlsDevicesForward(db, from, to);

const ownerMoved =
db
Expand Down
Loading
Loading