diff --git a/src/encryptid/server.ts b/src/encryptid/server.ts index 6da79f68..d6602413 100644 --- a/src/encryptid/server.ts +++ b/src/encryptid/server.ts @@ -484,6 +484,18 @@ app.use('*', cors({ credentials: true, })); +// Return JSON for unhandled exceptions on JSON API routes so client-side +// `await res.json()` doesn't choke on plain-text "Internal Server Error". +app.onError((err, c) => { + const path = c.req.path; + console.error(`EncryptID: ${c.req.method} ${path} failed:`, err); + const wantsJson = path.startsWith('/api/') || (c.req.header('accept') || '').includes('application/json'); + if (wantsJson) { + return c.json({ error: 'Internal server error', detail: err.message }, 500); + } + return c.text('Internal Server Error', 500); +}); + // /api/internal/* is for service-to-service calls over the Docker network only. // Traefik adds X-Forwarded-For for any request arriving through the public edge // (CF tunnel, direct HTTPS). Direct container-to-container fetches do not set it. @@ -6385,6 +6397,11 @@ function joinPage(token: string): string { .replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=/g, ''); const clientDataJSON = btoa(String.fromCharCode(...new Uint8Array(credential.response.clientDataJSON))) .replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=/g, ''); + const publicKeyBytes = credential.response.getPublicKey ? credential.response.getPublicKey() : null; + const publicKey = publicKeyBytes + ? btoa(String.fromCharCode(...new Uint8Array(publicKeyBytes))) + .replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=/g, '') + : ''; const completeRes = await fetch('/api/register/complete', { method: 'POST', @@ -6396,6 +6413,7 @@ function joinPage(token: string): string { deviceName: detectDeviceName(), credential: { credentialId, + publicKey, attestationObject, clientDataJSON, transports: credential.response.getTransports ? credential.response.getTransports() : [], @@ -6728,6 +6746,7 @@ function oidcAcceptPage(token: string): string { }); showStatus('Completing registration...'); + const publicKeyBytes = credential.response.getPublicKey ? credential.response.getPublicKey() : null; const completeRes = await fetch('/api/register/complete', { method: 'POST', headers: { 'Content-Type': 'application/json' }, @@ -6738,6 +6757,7 @@ function oidcAcceptPage(token: string): string { deviceName: detectDeviceName(), credential: { credentialId: toB64url(credential.rawId), + publicKey: publicKeyBytes ? toB64url(publicKeyBytes) : '', attestationObject: toB64url(credential.response.attestationObject), clientDataJSON: toB64url(credential.response.clientDataJSON), transports: credential.response.getTransports ? credential.response.getTransports() : [],