fix: Collaboration-Rolle explizit validieren statt still auf WRITE fallen

parseCollaborationRole warnt bei fehlendem oder ungültigem role-Feld und wird bei Einladung sowie Logbuch-Sync genutzt.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-29 20:36:17 +02:00
parent 96ebb8357d
commit 481724bcb6
2 changed files with 25 additions and 3 deletions
@@ -10,6 +10,7 @@ import {
} from '../services/auth.js' } from '../services/auth.js'
import { decryptJson, encryptBuffer } from '../services/crypto.js' import { decryptJson, encryptBuffer } from '../services/crypto.js'
import { saveLogbookKey } from '../services/logbookKeys.js' import { saveLogbookKey } from '../services/logbookKeys.js'
import { parseCollaborationRole } from '../services/logbook.js'
import { syncLogbook } from '../services/sync.js' import { syncLogbook } from '../services/sync.js'
import { db } from '../services/db.js' import { db } from '../services/db.js'
import { PlausibleEvents, trackPlausibleEvent } from '../services/analytics.js' import { PlausibleEvents, trackPlausibleEvent } from '../services/analytics.js'
@@ -183,6 +184,7 @@ export default function InvitationAcceptance({ onAccepted, onCancel }: Invitatio
} }
const acceptResult = await res.json() const acceptResult = await res.json()
const collaborationRole = parseCollaborationRole(acceptResult.role, 'invitation accept')
await saveLogbookKey(logbookId, logbookKey) await saveLogbookKey(logbookId, logbookKey)
@@ -193,7 +195,7 @@ export default function InvitationAcceptance({ onAccepted, onCancel }: Invitatio
updatedAt: new Date().toISOString(), updatedAt: new Date().toISOString(),
isSynced: 1, isSynced: 1,
isShared: 1, isShared: 1,
collaborationRole: acceptResult.role === 'READ' ? 'READ' : 'WRITE' collaborationRole
}) })
} }
+22 -2
View File
@@ -7,6 +7,22 @@ import { PlausibleEvents, trackPlausibleEvent } from './analytics.js'
const API_BASE = '/api/logbooks' const API_BASE = '/api/logbooks'
export type LogbookAccessRole = 'OWNER' | 'READ' | 'WRITE' export type LogbookAccessRole = 'OWNER' | 'READ' | 'WRITE'
export type CollaborationRole = 'READ' | 'WRITE'
/** Validates server/cached collaboration role; warns and falls back to WRITE if missing or invalid. */
export function parseCollaborationRole(role: unknown, context: string): CollaborationRole {
if (role === 'READ' || role === 'WRITE') {
return role
}
if (role === undefined || role === null || role === '') {
console.warn(`[collaboration] Missing role in ${context}; defaulting to WRITE.`)
} else {
console.warn(`[collaboration] Unexpected role in ${context}:`, role, '— defaulting to WRITE.')
}
return 'WRITE'
}
export interface DecryptedLogbook { export interface DecryptedLogbook {
id: string id: string
@@ -112,7 +128,9 @@ export async function fetchLogbooks(): Promise<DecryptedLogbook[]> {
updatedAt: lb.updatedAt || new Date().toISOString(), updatedAt: lb.updatedAt || new Date().toISOString(),
isSynced: 1, isSynced: 1,
isShared: isShared ? 1 : 0, isShared: isShared ? 1 : 0,
collaborationRole: isShared ? (lb.collaborators?.[0]?.role || 'WRITE') : undefined, collaborationRole: isShared
? parseCollaborationRole(lb.collaborators?.[0]?.role, `fetch logbook ${lb.id}`)
: undefined,
isDemo: localById.get(lb.id)?.isDemo isDemo: localById.get(lb.id)?.isDemo
} }
}) })
@@ -138,7 +156,9 @@ export async function fetchLogbooks(): Promise<DecryptedLogbook[]> {
updatedAt: lb.updatedAt, updatedAt: lb.updatedAt,
isSynced: lb.isSynced === 1, isSynced: lb.isSynced === 1,
isShared: lb.isShared === 1, isShared: lb.isShared === 1,
accessRole: lb.isShared === 1 ? (lb.collaborationRole || 'WRITE') : 'OWNER', accessRole: lb.isShared === 1
? parseCollaborationRole(lb.collaborationRole, `cached logbook ${lb.id}`)
: 'OWNER',
isDemo: lb.isDemo === 1 isDemo: lb.isDemo === 1
}) })
} }