mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 20:01:09 +02:00
fix(writing): decrypt drafts/versions before reading encrypted fields
Mehrere Store-Methoden lasen die verschlüsselten Felder eines frisch aus Dexie geholten Records direkt — references/title/briefing/content landen aber als Ciphertext-String und nicht als Array/Objekt im Speicher. Auswirkungen die jetzt behoben sind: - startDraftGeneration: 'refs.map is not a function' beim ersten Klick auf "Generieren" (draft.references war Ciphertext) - refineSelection: Crash beim Lesen von draft.briefing.language - applyRefinement: Slice-Konkatenation auf Ciphertext (korrumpiert die Version still beim ersten Selection-Refinement) - updateBriefing: Spread-merge eines Ciphertext-Strings in den Patch - createCheckpointVersion: kopiert die Ciphertext-Bytes als neue Version-Content statt des Plaintexts Fix: decryptRecord() direkt nach jedem .get() der relevante encrypted Felder liest. queries.ts war schon korrekt (decryptRecords im liveQuery- Pfad), aber die Mutation-Pfade haben das übersprungen. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
449837354d
commit
450372e545
2 changed files with 27 additions and 7 deletions
|
|
@ -12,7 +12,7 @@
|
|||
* store and then call `pointToVersion` — never append to an existing version.
|
||||
*/
|
||||
|
||||
import { encryptRecord } from '$lib/data/crypto';
|
||||
import { encryptRecord, decryptRecord } from '$lib/data/crypto';
|
||||
import { emitDomainEvent } from '$lib/data/events';
|
||||
import { getActiveSpace } from '$lib/data/scope';
|
||||
import { getEffectiveUserId } from '$lib/data/current-user';
|
||||
|
|
@ -150,6 +150,9 @@ export const draftsStore = {
|
|||
async updateBriefing(id: string, briefingPatch: Partial<DraftBriefing>) {
|
||||
const existing = await draftTable.get(id);
|
||||
if (!existing) return;
|
||||
// briefing is in the encrypt-list — decrypt before merging or the
|
||||
// ciphertext blob spreads garbage into the patch.
|
||||
await decryptRecord('writingDrafts', existing);
|
||||
const merged: DraftBriefing = { ...existing.briefing, ...briefingPatch };
|
||||
await draftsStore.updateDraft(id, { briefing: merged });
|
||||
},
|
||||
|
|
@ -227,6 +230,9 @@ export const draftsStore = {
|
|||
) {
|
||||
const source = await draftVersionTable.get(sourceVersionId);
|
||||
if (!source) throw new Error(`Version ${sourceVersionId} not found`);
|
||||
// content is encrypted — decrypt so the checkpoint copies plaintext,
|
||||
// not the ciphertext blob.
|
||||
await decryptRecord('writingDraftVersions', source);
|
||||
const existing = await draftVersionTable.where('draftId').equals(draftId).toArray();
|
||||
const nextNumber = Math.max(0, ...existing.map((v) => v.versionNumber)) + 1;
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
* back into the same current version in-place.
|
||||
*/
|
||||
|
||||
import { encryptRecord } from '$lib/data/crypto';
|
||||
import { encryptRecord, decryptRecord } from '$lib/data/crypto';
|
||||
import { emitDomainEvent } from '$lib/data/events';
|
||||
import { generationTable, draftTable, draftVersionTable, writingStyleTable } from '../collections';
|
||||
import { callWritingGeneration } from '../api';
|
||||
|
|
@ -98,13 +98,21 @@ export const generationsStore = {
|
|||
): Promise<string> {
|
||||
const draft = await draftTable.get(draftId);
|
||||
if (!draft) throw new Error(`Draft ${draftId} not found`);
|
||||
// title / briefing / styleOverrides / references are all in the
|
||||
// encrypt-list — without decrypting them first, references is a
|
||||
// ciphertext string and refs.map() blows up.
|
||||
await decryptRecord('writingDrafts', draft);
|
||||
|
||||
const generationId = crypto.randomUUID();
|
||||
const kind: GenerationKind =
|
||||
draft.currentVersionId &&
|
||||
(await draftVersionTable.get(draft.currentVersionId))?.content?.trim()
|
||||
? 'full-regenerate'
|
||||
: 'draft-from-brief';
|
||||
let priorContent = '';
|
||||
if (draft.currentVersionId) {
|
||||
const priorRow = await draftVersionTable.get(draft.currentVersionId);
|
||||
if (priorRow) {
|
||||
await decryptRecord('writingDraftVersions', priorRow);
|
||||
priorContent = priorRow.content ?? '';
|
||||
}
|
||||
}
|
||||
const kind: GenerationKind = priorContent.trim() ? 'full-regenerate' : 'draft-from-brief';
|
||||
const resolved = await loadStyle(draft.styleId);
|
||||
const stylePreset =
|
||||
resolved?.source === 'preset'
|
||||
|
|
@ -281,6 +289,9 @@ export const generationsStore = {
|
|||
): Promise<{ generationId: string; refined: string }> {
|
||||
const draft = await draftTable.get(draftId);
|
||||
if (!draft) throw new Error(`Draft ${draftId} not found`);
|
||||
// briefing.language sits behind the briefing-encryption — read after
|
||||
// decrypting or the ciphertext-shaped value crashes property access.
|
||||
await decryptRecord('writingDrafts', draft);
|
||||
|
||||
const resolved = await loadStyle(draft.styleId);
|
||||
const stylePreset =
|
||||
|
|
@ -413,6 +424,9 @@ export const generationsStore = {
|
|||
): Promise<{ before: string; after: string }> {
|
||||
const existing = await draftVersionTable.get(versionId);
|
||||
if (!existing) throw new Error(`Version ${versionId} not found`);
|
||||
// content is encrypted; splice in plaintext or we'd build the new
|
||||
// version by concatenating ciphertext with the replacement.
|
||||
await decryptRecord('writingDraftVersions', existing);
|
||||
const before = existing.content;
|
||||
const after = before.slice(0, selection.start) + replacement + before.slice(selection.end);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue