From ca45eb43d2f9c6e50c632b2b7c934db22ec8a187 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Wed, 8 Apr 2026 19:25:51 -0400 Subject: [PATCH] fix(encryptid): handle non-JSON error responses in auth flow When EncryptID server returns plain text errors (e.g. "Internal Server Error"), the client's .json() calls threw SyntaxError which surfaced as an ugly parse error to users. Add .catch() to all unsafe .json() calls in session.ts, login-button.ts, and recovery.ts so auth gracefully falls back to unsigned tokens instead. Co-Authored-By: Claude Opus 4.6 --- src/encryptid/recovery.ts | 4 ++-- src/encryptid/session.ts | 11 ++++++----- src/encryptid/ui/login-button.ts | 4 +++- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/encryptid/recovery.ts b/src/encryptid/recovery.ts index ba1d2d5..4746a12 100644 --- a/src/encryptid/recovery.ts +++ b/src/encryptid/recovery.ts @@ -195,7 +195,7 @@ export class RecoveryManager { throw new Error(err.error || `Server error: ${res.status}`); } - const data = await res.json(); + const data = await res.json().catch(() => ({})); serverId = data.guardian?.id; console.log('EncryptID: Guardian added via server', { @@ -345,7 +345,7 @@ export class RecoveryManager { }); if (res.ok) { - const data = await res.json(); + const data = await res.json().catch(() => ({ guardians: [] })); const serverGuardian = data.guardians?.find((g: any) => g.id === guardianId); if (!serverGuardian) { diff --git a/src/encryptid/session.ts b/src/encryptid/session.ts index a056eb2..f20370f 100644 --- a/src/encryptid/session.ts +++ b/src/encryptid/session.ts @@ -179,8 +179,9 @@ export class SessionManager { headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ credentialId: authResult.credentialId }), }); - if (!startRes.ok) throw new Error('Failed to start server auth'); - const { options } = await startRes.json(); + if (!startRes.ok) throw new Error(`Failed to start server auth: ${startRes.status}`); + const startBody = await startRes.json().catch(() => { throw new Error('Invalid JSON from auth/start'); }); + const { options } = startBody; // Step 2: Complete auth with credentialId to get signed token const completeRes = await fetch(`${ENCRYPTID_SERVER}/api/auth/complete`, { @@ -191,8 +192,8 @@ export class SessionManager { credential: { credentialId: authResult.credentialId }, }), }); - if (!completeRes.ok) throw new Error('Server auth failed'); - const data = await completeRes.json(); + if (!completeRes.ok) throw new Error(`Server auth failed: ${completeRes.status}`); + const data = await completeRes.json().catch(() => { throw new Error('Invalid JSON from auth/complete'); }); if (!data.token) throw new Error('No token in response'); accessToken = data.token; } catch (err) { @@ -544,7 +545,7 @@ export class SessionManager { }); if (!res.ok) throw new Error(`Refresh failed: ${res.status}`); - const data = await res.json(); + const data = await res.json().catch(() => { throw new Error('Invalid JSON from session/refresh'); }); if (!data.token) throw new Error('No token in refresh response'); // Decode new claims diff --git a/src/encryptid/ui/login-button.ts b/src/encryptid/ui/login-button.ts index a77caf9..19f584b 100644 --- a/src/encryptid/ui/login-button.ts +++ b/src/encryptid/ui/login-button.ts @@ -667,7 +667,9 @@ export class EncryptIDLoginButton extends HTMLElement { body: JSON.stringify({ username }), }); if (!res.ok) return undefined; - const { options, userFound } = await res.json(); + const body = await res.json().catch(() => null); + if (!body) return undefined; + const { options, userFound } = body; if (!userFound || !options.allowCredentials?.length) return undefined; return options.allowCredentials.map((c: any) => ({ type: 'public-key' as const,