From f87f5e382d51c2096d41717fe9630d9e3be6c2f3 Mon Sep 17 00:00:00 2001 From: elpatron Date: Fri, 29 May 2026 16:48:13 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20PDF-Passkey-Datum=20i18n=20und=20Challen?= =?UTF-8?q?ge=20erst=20nach=20Verify=20l=C3=B6schen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Passkey-Signaturen im PDF nutzen die App-Sprache für Datumsformatierung. Signing-Challenge bleibt bei fehlgeschlagener WebAuthn-Verifikation retry-fähig. Co-authored-by: Cursor --- client/src/services/pdfExport.ts | 10 ++++++++-- server/src/routes/sign.ts | 4 ++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/client/src/services/pdfExport.ts b/client/src/services/pdfExport.ts index 77abd22..a3b4638 100644 --- a/client/src/services/pdfExport.ts +++ b/client/src/services/pdfExport.ts @@ -4,6 +4,12 @@ import { getActiveMasterKey } from './auth.js' import { getLogbookKey } from './logbookKeys.js' import { decryptJson } from './crypto.js' import { isSignatureImage, isPasskeySignature } from '../utils/signatures.js' +import i18n from '../i18n/index.js' + +function formatPasskeySignDate(signedAt: string): string { + const locale = i18n.language === 'de' ? 'de-DE' : 'en-GB' + return new Date(signedAt).toLocaleString(locale) +} export async function generateLogbookPagePdf(logbookId: string, entryId: string, preloadedData?: { yacht: any; entry: any }): Promise { let yachtName = '', homePort = '', registration = '', callsign = '', atis = '', mmsi = ''; @@ -232,7 +238,7 @@ export async function generateLogbookPagePdf(logbookId: string, entryId: string, doc.text('Skipper Unterschrift:', sigX + 2, sigY + 4.2); if (isPasskeySignature(entry.signSkipper)) { doc.setFont('Helvetica', 'normal'); - const skipperDate = new Date(entry.signSkipper.signedAt).toLocaleString('de-DE'); + const skipperDate = formatPasskeySignDate(entry.signSkipper.signedAt); doc.text(`Passkey: ${entry.signSkipper.username}`, sigX + 2, sigY + 9); doc.text(skipperDate, sigX + 2, sigY + 13.5); } else if (isSignatureImage(entry.signSkipper)) { @@ -246,7 +252,7 @@ export async function generateLogbookPagePdf(logbookId: string, entryId: string, doc.text('Crew Unterschrift:', sigX + 80.5, sigY + 4.2); if (isPasskeySignature(entry.signCrew)) { doc.setFont('Helvetica', 'normal'); - const crewDate = new Date(entry.signCrew.signedAt).toLocaleString('de-DE'); + const crewDate = formatPasskeySignDate(entry.signCrew.signedAt); doc.text(`Passkey: ${entry.signCrew.username}`, sigX + 80.5, sigY + 9); doc.text(crewDate, sigX + 80.5, sigY + 13.5); } else if (isSignatureImage(entry.signCrew)) { diff --git a/server/src/routes/sign.ts b/server/src/routes/sign.ts index 142fdc8..225d6d7 100644 --- a/server/src/routes/sign.ts +++ b/server/src/routes/sign.ts @@ -204,8 +204,6 @@ router.post('/verify', async (req: any, res) => { return res.status(400).json({ error: 'Signing context mismatch' }) } - signingChallenges.delete(challenge) - const access = await getLogbookWithAccess(logbookId, req.userId) if (!access) { return res.status(403).json({ error: 'Forbidden: Access denied' }) @@ -250,6 +248,8 @@ router.post('/verify', async (req: any, res) => { return res.status(400).json({ error: 'Signature verification failed' }) } + signingChallenges.delete(challenge) + await prisma.credential.update({ where: { id: dbCred.id }, data: { counter: BigInt(verification.authenticationInfo.newCounter) }