From caf85ad9ebfda86d41f63ea510d11208986b7868 Mon Sep 17 00:00:00 2001 From: elpatron Date: Wed, 3 Jun 2026 11:48:45 +0200 Subject: [PATCH] Fix shared logbook access for crew after AI summary sync. Correct owner detection while the logbook loads, preserve AI summaries on live-log saves, skip corrupt entry decrypts, and never regenerate keys for shared logbooks. Co-authored-by: Cursor --- client/src/App.tsx | 5 +++-- client/src/components/LogEntriesList.tsx | 6 +++--- client/src/components/LogEntryEditor.tsx | 2 ++ client/src/services/logbookKeys.ts | 12 ++++++++++++ client/src/services/quickEventLog.ts | 13 ++++++++++++- 5 files changed, 32 insertions(+), 6 deletions(-) diff --git a/client/src/App.tsx b/client/src/App.tsx index bcc41c9..d62a5ec 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -103,7 +103,7 @@ function App() { [activeLogbookId] ) - const [activeAccessRole, setActiveAccessRole] = useState('OWNER') + const [activeAccessRole, setActiveAccessRole] = useState(null) useEffect(() => { if (!activeLogbookId) { @@ -574,7 +574,8 @@ function App() { const logbookReadOnly = activeLogbookRecord?.isShared === 1 && activeAccessRole === 'READ' const isLogbookOwner = - activeAccessRole === 'OWNER' || activeLogbookRecord?.isShared !== 1 + activeAccessRole === 'OWNER' || + (activeLogbookRecord != null && activeLogbookRecord.isShared !== 1) if (showUserProfile) { return ( diff --git a/client/src/components/LogEntriesList.tsx b/client/src/components/LogEntriesList.tsx index 2610e2f..6cfa6dd 100644 --- a/client/src/components/LogEntriesList.tsx +++ b/client/src/components/LogEntriesList.tsx @@ -9,7 +9,7 @@ import { downloadCsv, shareCsv } from '../services/csvExport.js' import { downloadLogbookPagePdf } from '../services/pdfExport.js' import { PlausibleEvents, trackPlausibleEvent } from '../services/analytics.js' import { getErrorMessage } from '../utils/errors.js' -import { findTodayEntryId } from '../services/quickEventLog.js' +import { findTodayEntryId, tryDecryptEntryPayload } from '../services/quickEventLog.js' import LogEntryEditor from './LogEntryEditor.tsx' import LiveLogView from './LiveLogView.tsx' import EntrySkipperSignBadge from './EntrySkipperSignBadge.tsx' @@ -136,7 +136,7 @@ export default function LogEntriesList({ } await forEachInBatches(needsDecrypt, 8, async (entry) => { - const decrypted = await decryptJson(entry.encryptedData, entry.iv, entry.tag, masterKey) + const decrypted = await tryDecryptEntryPayload(entry, masterKey) if (!decrypted) return const listCache = await buildEntryListCache(decrypted as Record) @@ -266,7 +266,7 @@ export default function LogEntriesList({ const decryptedEntries: Array = [] for (const entry of localEntries) { - const decrypted = await decryptJson(entry.encryptedData, entry.iv, entry.tag, masterKey) + const decrypted = await tryDecryptEntryPayload(entry, masterKey) if (decrypted) decryptedEntries.push(decrypted as LogEntryTankSource & TravelDaySortable) } diff --git a/client/src/components/LogEntryEditor.tsx b/client/src/components/LogEntryEditor.tsx index 3ff8ab3..01ac61b 100644 --- a/client/src/components/LogEntryEditor.tsx +++ b/client/src/components/LogEntryEditor.tsx @@ -527,6 +527,8 @@ export default function LogEntryEditor({ }, []) useEffect(() => { + setCanSignSkipper(false) + setCanSignCrew(false) getLogbookAccess(logbookId).then((access) => { if (!access) return setCanSignSkipper(access.isOwner) diff --git a/client/src/services/logbookKeys.ts b/client/src/services/logbookKeys.ts index ba505d0..f83f3df 100644 --- a/client/src/services/logbookKeys.ts +++ b/client/src/services/logbookKeys.ts @@ -91,6 +91,7 @@ export function clearLogbookKeysCache() { export async function ensureLogbookKey(logbookId: string): Promise { const localLb = await db.logbooks.get(logbookId) const encryptedTitle = localLb ? localLb.encryptedTitle : '' + const isShared = localLb?.isShared === 1 const masterKey = getActiveMasterKey() let key = await getLogbookKey(logbookId) @@ -103,6 +104,11 @@ export async function ensureLogbookKey(logbookId: string): Promise // Key works, return it return key } catch (err) { + if (isShared) { + throw new Error( + 'Shared logbook encryption key is missing or invalid. Please go online and refresh your logbooks.' + ) + } console.warn('Stored logbook key failed to decrypt title. Testing if master key works (legacy migration)...') try { const parsed = JSON.parse(encryptedTitle) @@ -145,6 +151,12 @@ export async function ensureLogbookKey(logbookId: string): Promise // If no logbook key exists yet if (!key) { + if (isShared) { + throw new Error( + 'Shared logbook encryption key not found. Please go online and refresh your logbooks.' + ) + } + if (encryptedTitle && masterKey) { try { // Check if title is already decryptable using masterKey (meaning it is a legacy logbook) diff --git a/client/src/services/quickEventLog.ts b/client/src/services/quickEventLog.ts index 1e27e7a..84bfc83 100644 --- a/client/src/services/quickEventLog.ts +++ b/client/src/services/quickEventLog.ts @@ -124,11 +124,22 @@ function buildEncryptedPayload( }) const clear = options.clearSignatures - return { + const entryData: Record = { ...payload, signSkipper: clear ? '' : (data.signSkipper ?? ''), signCrew: clear ? '' : (data.signCrew ?? '') } + + const summary = typeof data.aiSummary === 'string' ? data.aiSummary.trim() : '' + if (summary) { + entryData.aiSummary = summary + entryData.aiSummaryGeneratedAt = + typeof data.aiSummaryGeneratedAt === 'string' && data.aiSummaryGeneratedAt + ? data.aiSummaryGeneratedAt + : new Date().toISOString() + } + + return entryData } export async function loadEntry(logbookId: string, entryId: string): Promise {