feat: Hybride Passkey-Freigabe für Skipper und Crew

Skipper (nur Owner) und Crew (WRITE-Collaborators) können Logbuchseiten
optional per WebAuthn freigeben; klassische Unterschrift bleibt als Fallback.
Signatur ist an den Eintrags-Hash gebunden, Export in CSV/PDF angepasst.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-29 16:28:52 +02:00
parent 5706d1762d
commit ce47fe5fdc
18 changed files with 1471 additions and 68 deletions
+41 -3
View File
@@ -1,12 +1,50 @@
import type { PasskeySignature, SignatureValue } from '../types/signatures.js'
export function isSignatureImage(value: string | undefined | null): boolean {
return typeof value === 'string' && value.startsWith('data:image/')
}
export function formatSignatureForExport(
value: string | undefined | null,
export function isPasskeySignature(value: unknown): value is PasskeySignature {
return (
typeof value === 'object' &&
value !== null &&
(value as PasskeySignature).kind === 'passkey' &&
(value as PasskeySignature).version === 1
)
}
export function normalizeSignature(value: unknown): SignatureValue | undefined {
if (value === null || value === undefined || value === '') return undefined
if (isPasskeySignature(value)) return value
if (typeof value === 'string') return value
return undefined
}
export function isSignatureValidForEntry(sig: PasskeySignature, entryHash: string): boolean {
return sig.entryHash === entryHash
}
export interface SignatureExportLabels {
imagePlaceholder: string
passkeyLabel: (username: string, signedAt: string) => string
}
export function formatSignatureForExport(
value: SignatureValue | undefined | null,
labels: SignatureExportLabels
): string {
if (!value) return ''
if (isSignatureImage(value)) return imagePlaceholder
if (isPasskeySignature(value)) {
return labels.passkeyLabel(value.username, value.signedAt)
}
if (isSignatureImage(value)) return labels.imagePlaceholder
return value
}
export function serializeSignature(value: SignatureValue | '' | undefined): SignatureValue | undefined {
if (!value) return undefined
if (isPasskeySignature(value)) return value
if (isSignatureImage(value)) return value
const trimmed = value.trim()
return trimmed || undefined
}