feat(security): Session-Cookies statt X-User-Id und API-Härtung

Ersetzt die spoofbare X-User-Id-Auth durch signierte HttpOnly-Sessions nach
WebAuthn, erzwingt WRITE-only Sync, speichert den Master-Key nur im RAM und
ergänzt CORS, Rate-Limits, Helmet sowie Passkey-Reauth für sensible Aktionen.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-30 13:47:24 +02:00
parent 4f3f530f1f
commit dea33e3f00
33 changed files with 657 additions and 397 deletions
+2 -12
View File
@@ -14,6 +14,7 @@ import { parseCollaborationRole } from '../services/logbook.js'
import { syncLogbook } from '../services/sync.js'
import { db } from '../services/db.js'
import { PlausibleEvents, trackPlausibleEvent } from '../services/analytics.js'
import { apiJson } from '../services/api.js'
interface InvitationAcceptanceProps {
onAccepted: (logbookId: string, title: string) => void
@@ -164,12 +165,8 @@ export default function InvitationAcceptance({ onAccepted, onCancel }: Invitatio
)
const encrypted = await encryptBuffer(logbookKey, aesMasterKey)
const res = await fetch('/api/collaboration/accept', {
const acceptResult = await apiJson<{ role: string; logbookId: string }>('/api/collaboration/accept', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-User-Id': activeUserId
},
body: JSON.stringify({
token,
encryptedLogbookKey: encrypted.ciphertext,
@@ -177,13 +174,6 @@ export default function InvitationAcceptance({ onAccepted, onCancel }: Invitatio
tag: encrypted.tag
})
})
if (!res.ok) {
const serverError = await res.json().catch(() => ({}))
throw new Error(serverError.error || (isDe ? 'Beitritt auf dem Server fehlgeschlagen.' : 'Failed to join logbook on the server.'))
}
const acceptResult = await res.json()
const collaborationRole = parseCollaborationRole(acceptResult.role, 'invitation accept')
await saveLogbookKey(logbookId, logbookKey)