fix: Passkey-Sign Challenge und Signatur-Moduswechsel
WebAuthn-Challenge wird als Bytes übergeben und unter options.challenge gespeichert. Passkey/Klassisch-Toggle erlaubt Wechsel zwischen Freigabearten. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,10 +1,13 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Check } from 'lucide-react'
|
||||
import SignaturePad from './SignaturePad.tsx'
|
||||
import PasskeySignButton from './PasskeySignButton.tsx'
|
||||
import type { SignatureValue } from '../types/signatures.js'
|
||||
import type { PasskeySignature, SignatureValue } from '../types/signatures.js'
|
||||
import { isPasskeySignature } from '../utils/signatures.js'
|
||||
|
||||
type SignatureMode = 'passkey' | 'classic'
|
||||
|
||||
interface SignatureSectionProps {
|
||||
readOnly?: boolean
|
||||
disabled?: boolean
|
||||
@@ -26,6 +29,157 @@ function padValue(value: SignatureValue | ''): string {
|
||||
return value
|
||||
}
|
||||
|
||||
function modeFromValue(value: SignatureValue | '', passkeyAvailable: boolean): SignatureMode {
|
||||
if (isPasskeySignature(value)) return 'passkey'
|
||||
if (value) return 'classic'
|
||||
return passkeyAvailable ? 'passkey' : 'classic'
|
||||
}
|
||||
|
||||
interface RoleSignatureBlockProps {
|
||||
roleLabel: string
|
||||
passkeyLabel: string
|
||||
padId: string
|
||||
value: SignatureValue | ''
|
||||
passkeySignature?: PasskeySignature
|
||||
signatureValid: boolean
|
||||
showPasskey: boolean
|
||||
readOnly: boolean
|
||||
disabled: boolean
|
||||
classicHint?: string
|
||||
offlineHint?: string
|
||||
onChange: (value: SignatureValue | '') => void
|
||||
onPasskeySign: () => Promise<void>
|
||||
}
|
||||
|
||||
function RoleSignatureBlock({
|
||||
roleLabel,
|
||||
passkeyLabel,
|
||||
padId,
|
||||
value,
|
||||
passkeySignature,
|
||||
signatureValid,
|
||||
showPasskey,
|
||||
readOnly,
|
||||
disabled,
|
||||
classicHint,
|
||||
offlineHint,
|
||||
onChange,
|
||||
onPasskeySign
|
||||
}: RoleSignatureBlockProps) {
|
||||
const { t } = useTranslation()
|
||||
const [mode, setMode] = useState<SignatureMode>(() => modeFromValue(value, showPasskey))
|
||||
|
||||
useEffect(() => {
|
||||
setMode(modeFromValue(value, showPasskey))
|
||||
}, [value, showPasskey])
|
||||
|
||||
const switchToClassic = () => {
|
||||
setMode('classic')
|
||||
if (isPasskeySignature(value)) onChange('')
|
||||
}
|
||||
|
||||
const switchToPasskey = () => {
|
||||
setMode('passkey')
|
||||
if (value && !isPasskeySignature(value)) onChange('')
|
||||
}
|
||||
|
||||
const handlePadChange = (next: string) => {
|
||||
setMode('classic')
|
||||
onChange(next)
|
||||
}
|
||||
|
||||
if (readOnly) {
|
||||
if (isPasskeySignature(value)) {
|
||||
return (
|
||||
<div className="signature-role-block">
|
||||
<PasskeySignButton
|
||||
label={passkeyLabel}
|
||||
signature={value}
|
||||
signatureValid={signatureValid}
|
||||
disabled={disabled}
|
||||
canSign={false}
|
||||
onSign={onPasskeySign}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div className="signature-role-block">
|
||||
<SignaturePad
|
||||
id={padId}
|
||||
label={roleLabel}
|
||||
value={padValue(value)}
|
||||
onChange={() => {}}
|
||||
disabled={disabled}
|
||||
readOnly
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const showPasskeyPanel = showPasskey && mode === 'passkey'
|
||||
const showClassicPanel = !showPasskey || mode === 'classic'
|
||||
|
||||
return (
|
||||
<div className="signature-role-block">
|
||||
{showPasskey && (
|
||||
<div className="signature-mode-toggle" role="tablist" aria-label={passkeyLabel}>
|
||||
<button
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={mode === 'passkey'}
|
||||
className={`signature-mode-btn ${mode === 'passkey' ? 'active' : ''}`}
|
||||
onClick={switchToPasskey}
|
||||
>
|
||||
{t('logs.sign_mode_passkey')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={mode === 'classic'}
|
||||
className={`signature-mode-btn ${mode === 'classic' ? 'active' : ''}`}
|
||||
onClick={switchToClassic}
|
||||
>
|
||||
{t('logs.sign_mode_classic')}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showPasskeyPanel && (
|
||||
<PasskeySignButton
|
||||
label={passkeyLabel}
|
||||
signature={passkeySignature}
|
||||
signatureValid={signatureValid}
|
||||
disabled={disabled}
|
||||
canSign
|
||||
onSign={onPasskeySign}
|
||||
onClear={passkeySignature ? switchToClassic : undefined}
|
||||
/>
|
||||
)}
|
||||
|
||||
{showClassicPanel && (
|
||||
<>
|
||||
<SignaturePad
|
||||
id={padId}
|
||||
label={roleLabel}
|
||||
value={padValue(value)}
|
||||
onChange={handlePadChange}
|
||||
disabled={disabled}
|
||||
readOnly={false}
|
||||
/>
|
||||
{classicHint && !passkeySignature && (
|
||||
<p className="signature-hint">{classicHint}</p>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{offlineHint && !showPasskey && (
|
||||
<p className="signature-hint">{offlineHint}</p>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default function SignatureSection({
|
||||
readOnly = false,
|
||||
disabled = false,
|
||||
@@ -43,9 +197,6 @@ export default function SignatureSection({
|
||||
}: SignatureSectionProps) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const skipperPasskey = isPasskeySignature(signSkipper) ? signSkipper : undefined
|
||||
const crewPasskey = isPasskeySignature(signCrew) ? signCrew : undefined
|
||||
|
||||
const showSkipperPasskey = isOwner && isOnline
|
||||
const showCrewPasskey = hasWriteCollaborators && isOnline
|
||||
|
||||
@@ -57,67 +208,36 @@ export default function SignatureSection({
|
||||
</div>
|
||||
|
||||
<div className="form-grid signature-grid">
|
||||
<div className="signature-role-block">
|
||||
{showSkipperPasskey && (
|
||||
<PasskeySignButton
|
||||
label={t('logs.sign_skipper')}
|
||||
signature={skipperPasskey}
|
||||
signatureValid={skipperSignatureValid}
|
||||
disabled={disabled}
|
||||
canSign={!readOnly}
|
||||
onSign={onPasskeySignSkipper}
|
||||
onClear={skipperPasskey ? () => onSignSkipperChange('') : undefined}
|
||||
/>
|
||||
)}
|
||||
<RoleSignatureBlock
|
||||
roleLabel={t('logs.sign_skipper')}
|
||||
passkeyLabel={t('logs.sign_skipper')}
|
||||
padId="sign-skipper"
|
||||
value={signSkipper}
|
||||
passkeySignature={isPasskeySignature(signSkipper) ? signSkipper : undefined}
|
||||
signatureValid={skipperSignatureValid}
|
||||
showPasskey={showSkipperPasskey}
|
||||
readOnly={readOnly}
|
||||
disabled={disabled}
|
||||
classicHint={showSkipperPasskey ? t('logs.sign_classic_or_passkey') : undefined}
|
||||
offlineHint={!isOnline && isOwner ? t('logs.sign_offline_hint') : undefined}
|
||||
onChange={onSignSkipperChange}
|
||||
onPasskeySign={onPasskeySignSkipper}
|
||||
/>
|
||||
|
||||
{!skipperPasskey && (
|
||||
<SignaturePad
|
||||
id="sign-skipper"
|
||||
label={t('logs.sign_skipper')}
|
||||
value={padValue(signSkipper)}
|
||||
onChange={onSignSkipperChange}
|
||||
disabled={disabled}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
)}
|
||||
|
||||
{showSkipperPasskey && !skipperPasskey && !readOnly && (
|
||||
<p className="signature-hint">{t('logs.sign_classic_or_passkey')}</p>
|
||||
)}
|
||||
|
||||
{!isOnline && isOwner && !readOnly && (
|
||||
<p className="signature-hint">{t('logs.sign_offline_hint')}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="signature-role-block">
|
||||
{showCrewPasskey && (
|
||||
<PasskeySignButton
|
||||
label={t('logs.sign_crew')}
|
||||
signature={crewPasskey}
|
||||
signatureValid={crewSignatureValid}
|
||||
disabled={disabled}
|
||||
canSign={!readOnly}
|
||||
onSign={onPasskeySignCrew}
|
||||
onClear={crewPasskey ? () => onSignCrewChange('') : undefined}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!crewPasskey && (
|
||||
<SignaturePad
|
||||
id="sign-crew"
|
||||
label={t('logs.sign_crew')}
|
||||
value={padValue(signCrew)}
|
||||
onChange={onSignCrewChange}
|
||||
disabled={disabled}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
)}
|
||||
|
||||
{showCrewPasskey && !crewPasskey && !readOnly && (
|
||||
<p className="signature-hint">{t('logs.sign_crew_passkey_hint')}</p>
|
||||
)}
|
||||
</div>
|
||||
<RoleSignatureBlock
|
||||
roleLabel={t('logs.sign_crew')}
|
||||
passkeyLabel={t('logs.sign_crew')}
|
||||
padId="sign-crew"
|
||||
value={signCrew}
|
||||
passkeySignature={isPasskeySignature(signCrew) ? signCrew : undefined}
|
||||
signatureValid={crewSignatureValid}
|
||||
showPasskey={showCrewPasskey}
|
||||
readOnly={readOnly}
|
||||
disabled={disabled}
|
||||
classicHint={showCrewPasskey ? t('logs.sign_crew_passkey_hint') : undefined}
|
||||
onChange={onSignCrewChange}
|
||||
onPasskeySign={onPasskeySignCrew}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user