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 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-04-08 19:25:51 -04:00
parent 65b72ed7ac
commit ca45eb43d2
3 changed files with 11 additions and 8 deletions

View File

@ -195,7 +195,7 @@ export class RecoveryManager {
throw new Error(err.error || `Server error: ${res.status}`); throw new Error(err.error || `Server error: ${res.status}`);
} }
const data = await res.json(); const data = await res.json().catch(() => ({}));
serverId = data.guardian?.id; serverId = data.guardian?.id;
console.log('EncryptID: Guardian added via server', { console.log('EncryptID: Guardian added via server', {
@ -345,7 +345,7 @@ export class RecoveryManager {
}); });
if (res.ok) { 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); const serverGuardian = data.guardians?.find((g: any) => g.id === guardianId);
if (!serverGuardian) { if (!serverGuardian) {

View File

@ -179,8 +179,9 @@ export class SessionManager {
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ credentialId: authResult.credentialId }), body: JSON.stringify({ credentialId: authResult.credentialId }),
}); });
if (!startRes.ok) throw new Error('Failed to start server auth'); if (!startRes.ok) throw new Error(`Failed to start server auth: ${startRes.status}`);
const { options } = await startRes.json(); 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 // Step 2: Complete auth with credentialId to get signed token
const completeRes = await fetch(`${ENCRYPTID_SERVER}/api/auth/complete`, { const completeRes = await fetch(`${ENCRYPTID_SERVER}/api/auth/complete`, {
@ -191,8 +192,8 @@ export class SessionManager {
credential: { credentialId: authResult.credentialId }, credential: { credentialId: authResult.credentialId },
}), }),
}); });
if (!completeRes.ok) throw new Error('Server auth failed'); if (!completeRes.ok) throw new Error(`Server auth failed: ${completeRes.status}`);
const data = await completeRes.json(); const data = await completeRes.json().catch(() => { throw new Error('Invalid JSON from auth/complete'); });
if (!data.token) throw new Error('No token in response'); if (!data.token) throw new Error('No token in response');
accessToken = data.token; accessToken = data.token;
} catch (err) { } catch (err) {
@ -544,7 +545,7 @@ export class SessionManager {
}); });
if (!res.ok) throw new Error(`Refresh failed: ${res.status}`); 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'); if (!data.token) throw new Error('No token in refresh response');
// Decode new claims // Decode new claims

View File

@ -667,7 +667,9 @@ export class EncryptIDLoginButton extends HTMLElement {
body: JSON.stringify({ username }), body: JSON.stringify({ username }),
}); });
if (!res.ok) return undefined; 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; if (!userFound || !options.allowCredentials?.length) return undefined;
return options.allowCredentials.map((c: any) => ({ return options.allowCredentials.map((c: any) => ({
type: 'public-key' as const, type: 'public-key' as const,