fix(logs): Crew-Unterschrift mit Benutzerzuordnung und Owner-Crew-Signatur
Klassische Crew-Signaturen speichern Unterzeichner und Datum; Export und UI zeigen die Zuordnung. Eigner ohne WRITE-Collaborators dürfen wieder als Crew per Passkey signieren. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,8 +1,13 @@
|
||||
import { hashEntryForSigning } from './entryCanonicalHash.js'
|
||||
import type { PasskeySignature, SignatureValue } from '../types/signatures.js'
|
||||
import type { ClassicSignature, PasskeySignature, SignatureValue } from '../types/signatures.js'
|
||||
|
||||
export type SkipperSignStatus = 'none' | 'valid' | 'invalid'
|
||||
|
||||
export interface SignatureAttribution {
|
||||
username: string
|
||||
signedAt: string
|
||||
}
|
||||
|
||||
export function isSignatureImage(value: string | undefined | null): boolean {
|
||||
return typeof value === 'string' && value.startsWith('data:image/')
|
||||
}
|
||||
@@ -16,9 +21,52 @@ export function isPasskeySignature(value: unknown): value is PasskeySignature {
|
||||
)
|
||||
}
|
||||
|
||||
export function isClassicSignature(value: unknown): value is ClassicSignature {
|
||||
return (
|
||||
typeof value === 'object' &&
|
||||
value !== null &&
|
||||
(value as ClassicSignature).kind === 'classic' &&
|
||||
(value as ClassicSignature).version === 1
|
||||
)
|
||||
}
|
||||
|
||||
export function getSignaturePayload(value: SignatureValue | '' | undefined | null): string {
|
||||
if (!value) return ''
|
||||
if (isClassicSignature(value)) return value.payload
|
||||
if (isPasskeySignature(value)) return ''
|
||||
return value
|
||||
}
|
||||
|
||||
export function getSignatureAttribution(value: SignatureValue | '' | undefined | null): SignatureAttribution | null {
|
||||
if (!value || typeof value === 'string') return null
|
||||
if (isPasskeySignature(value) || isClassicSignature(value)) {
|
||||
return { username: value.username, signedAt: value.signedAt }
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export function createClassicSignature(input: {
|
||||
role: 'skipper' | 'crew'
|
||||
userId: string
|
||||
username: string
|
||||
signedAt: string
|
||||
payload: string
|
||||
}): ClassicSignature {
|
||||
return {
|
||||
kind: 'classic',
|
||||
version: 1,
|
||||
role: input.role,
|
||||
userId: input.userId,
|
||||
username: input.username,
|
||||
signedAt: input.signedAt,
|
||||
payload: input.payload
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeSignature(value: unknown): SignatureValue | undefined {
|
||||
if (value === null || value === undefined || value === '') return undefined
|
||||
if (isPasskeySignature(value)) return value
|
||||
if (isClassicSignature(value)) return value
|
||||
if (typeof value === 'string') return value
|
||||
return undefined
|
||||
}
|
||||
@@ -47,6 +95,7 @@ export async function getSkipperSignStatus(
|
||||
export interface SignatureExportLabels {
|
||||
imagePlaceholder: string
|
||||
passkeyLabel: (username: string, signedAt: string) => string
|
||||
attributionLabel: (username: string, signedAt: string) => string
|
||||
}
|
||||
|
||||
export function formatSignatureForExport(
|
||||
@@ -57,15 +106,19 @@ export function formatSignatureForExport(
|
||||
if (isPasskeySignature(value)) {
|
||||
return labels.passkeyLabel(value.username, value.signedAt)
|
||||
}
|
||||
if (isClassicSignature(value)) {
|
||||
return labels.attributionLabel(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()
|
||||
if (isPasskeySignature(value) || isClassicSignature(value)) return value
|
||||
const payload = typeof value === 'string' ? value : getSignaturePayload(value)
|
||||
if (isSignatureImage(payload)) return payload
|
||||
const trimmed = payload.trim()
|
||||
return trimmed || undefined
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user