fix: PDF-Passkey-Datum i18n und Challenge erst nach Verify löschen

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 <cursoragent@cursor.com>
This commit is contained in:
2026-05-29 16:48:13 +02:00
parent 81da01e786
commit f87f5e382d
2 changed files with 10 additions and 4 deletions
+8 -2
View File
@@ -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<jsPDF> {
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)) {
+2 -2
View File
@@ -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) }