feat: Security
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/** IndexedDB schema version - bump when adding/changing object stores. */
|
||||
export const BROWSER_DATABASE_VERSION = 3;
|
||||
export const BROWSER_DATABASE_VERSION = 4;
|
||||
|
||||
const STORE_MESSAGES = 'messages';
|
||||
const STORE_USERS = 'users';
|
||||
@@ -9,6 +9,7 @@ const STORE_BANS = 'bans';
|
||||
const STORE_META = 'meta';
|
||||
const STORE_ATTACHMENTS = 'attachments';
|
||||
const STORE_CUSTOM_EMOJIS = 'customEmojis';
|
||||
const STORE_MESSAGE_REVISIONS = 'messageRevisions';
|
||||
|
||||
export function ensureObjectStoreDuringUpgrade(
|
||||
database: IDBDatabase,
|
||||
@@ -64,4 +65,8 @@ export function applyBrowserDatabaseSchema(
|
||||
|
||||
ensureStoreIndex(customEmojisStore, 'updatedAt', 'updatedAt');
|
||||
ensureStoreIndex(customEmojisStore, 'creatorUserId', 'creatorUserId');
|
||||
|
||||
const revisionsStore = ensureObjectStoreDuringUpgrade(database, upgradeTransaction, STORE_MESSAGE_REVISIONS, { keyPath: 'id' });
|
||||
|
||||
ensureStoreIndex(revisionsStore, 'messageId', 'messageId');
|
||||
}
|
||||
|
||||
@@ -6,7 +6,8 @@ import {
|
||||
User,
|
||||
Room,
|
||||
Reaction,
|
||||
BanEntry
|
||||
BanEntry,
|
||||
type MessageRevision
|
||||
} from '../../shared-kernel';
|
||||
import type { ChatAttachmentMeta, CustomEmoji } from '../../shared-kernel';
|
||||
import { getStoredCurrentUserId } from '../../core/storage/current-user-storage';
|
||||
@@ -25,6 +26,7 @@ const STORE_BANS = 'bans';
|
||||
const STORE_META = 'meta';
|
||||
const STORE_ATTACHMENTS = 'attachments';
|
||||
const STORE_CUSTOM_EMOJIS = 'customEmojis';
|
||||
const STORE_MESSAGE_REVISIONS = 'messageRevisions';
|
||||
/** All object store names, used when clearing the entire database. */
|
||||
const ALL_STORE_NAMES: string[] = [
|
||||
STORE_MESSAGES,
|
||||
@@ -34,6 +36,7 @@ const ALL_STORE_NAMES: string[] = [
|
||||
STORE_BANS,
|
||||
STORE_ATTACHMENTS,
|
||||
STORE_CUSTOM_EMOJIS,
|
||||
STORE_MESSAGE_REVISIONS,
|
||||
STORE_META
|
||||
];
|
||||
|
||||
@@ -67,6 +70,13 @@ export class BrowserDatabaseService {
|
||||
await this.put(STORE_MESSAGES, message);
|
||||
}
|
||||
|
||||
async saveMessageRevision(revision: MessageRevision): Promise<void> {
|
||||
await this.put(STORE_MESSAGE_REVISIONS, {
|
||||
...revision,
|
||||
id: `${revision.messageId}:${revision.revision}`
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the latest messages for a room, sorted oldest-first for display.
|
||||
* @param roomId - Target room.
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
type Room,
|
||||
type User
|
||||
} from '../../shared-kernel';
|
||||
import type { ChatAttachmentMeta, CustomEmoji } from '../../shared-kernel';
|
||||
import type { ChatAttachmentMeta, CustomEmoji, MessageRevision } from '../../shared-kernel';
|
||||
import { getStoredCurrentUserId } from '../../core/storage/current-user-storage';
|
||||
import {
|
||||
attachmentToValues,
|
||||
@@ -42,6 +42,15 @@ export class CapacitorDatabaseService {
|
||||
await this.connection.initialize();
|
||||
}
|
||||
|
||||
async saveMessageRevision(revision: MessageRevision): Promise<void> {
|
||||
const store = await this.connection.getStore();
|
||||
|
||||
await store.run(
|
||||
'INSERT OR REPLACE INTO meta (key, value) VALUES (?, ?)',
|
||||
[`message-revision:${revision.messageId}:${revision.revision}`, JSON.stringify(revision)]
|
||||
);
|
||||
}
|
||||
|
||||
async saveMessage(message: Message): Promise<void> {
|
||||
const store = await this.connection.getStore();
|
||||
const row = messageToRow(message);
|
||||
@@ -49,8 +58,9 @@ export class CapacitorDatabaseService {
|
||||
await store.run(
|
||||
`INSERT OR REPLACE INTO messages (
|
||||
id, roomId, ownerUserId, channelId, senderId, senderName, content,
|
||||
timestamp, editedAt, isDeleted, replyToId, linkMetadata, kind, systemEvent
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
timestamp, editedAt, isDeleted, replyToId, linkMetadata, kind, systemEvent,
|
||||
revision, headHash
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
[
|
||||
row.id,
|
||||
row.roomId,
|
||||
@@ -65,7 +75,9 @@ export class CapacitorDatabaseService {
|
||||
row.replyToId ?? null,
|
||||
row.linkMetadata ?? null,
|
||||
row.kind ?? null,
|
||||
row.systemEvent ?? null
|
||||
row.systemEvent ?? null,
|
||||
row.revision ?? 0,
|
||||
row.headHash ?? null
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,8 @@ import {
|
||||
User,
|
||||
Room,
|
||||
Reaction,
|
||||
BanEntry
|
||||
BanEntry,
|
||||
type MessageRevision
|
||||
} from '../../shared-kernel';
|
||||
import type { ChatAttachmentMeta, CustomEmoji } from '../../shared-kernel';
|
||||
import { PlatformService } from '../../core/platform';
|
||||
@@ -99,6 +100,11 @@ export class DatabaseService {
|
||||
/** Persist a single chat message. */
|
||||
saveMessage(message: Message) { return this.withReady(() => this.backend.saveMessage(message)); }
|
||||
|
||||
/** Persist an append-only message revision audit entry. */
|
||||
saveMessageRevision(revision: MessageRevision) {
|
||||
return this.withReady(() => this.backend.saveMessageRevision(revision));
|
||||
}
|
||||
|
||||
/** Retrieve the latest messages for a room or channel with optional pagination.
|
||||
*
|
||||
* When `beforeTimestamp` is provided, only messages strictly older than that
|
||||
|
||||
@@ -4,7 +4,8 @@ import {
|
||||
User,
|
||||
Room,
|
||||
Reaction,
|
||||
BanEntry
|
||||
BanEntry,
|
||||
type MessageRevision
|
||||
} from '../../shared-kernel';
|
||||
import type { CustomEmoji } from '../../shared-kernel';
|
||||
import type { ElectronApi } from '../../core/platform/electron/electron-api.models';
|
||||
@@ -38,6 +39,16 @@ export class ElectronDatabaseService {
|
||||
return this.api.command({ type: 'save-message', payload: { message } });
|
||||
}
|
||||
|
||||
saveMessageRevision(revision: MessageRevision): Promise<void> {
|
||||
return this.api.command({
|
||||
type: 'save-meta',
|
||||
payload: {
|
||||
key: `message-revision:${revision.messageId}:${revision.revision}`,
|
||||
value: JSON.stringify(revision)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the latest messages for a room, sorted oldest-first for display.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user