Skip to content

Commit faa5854

Browse files
fix(zoo-gateway): treat verify 5xx as transient, do not clear token
The website's /api/extension/auth/verify route now returns 503 when the backend can't reach the database, instead of crashing. The extension previously treated any non-OK response from this endpoint as a definitively invalid token, which meant a transient backend hiccup would silently clear the user's session and force a fresh sign-in. verifyZooCodeToken now returns "unreachable" for 5xx responses (same classification as a network error), so initZooCodeAuth keeps the cached token in place and reports subscription status as "unknown" until the backend recovers. handleAuthCallback shows the could-not-verify message on 5xx so users see this is a temporary issue rather than a bad token. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent a99c3f0 commit faa5854

2 files changed

Lines changed: 47 additions & 4 deletions

File tree

src/services/__tests__/zoo-code-auth.test.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,23 @@ describe("zoo-code-auth", () => {
242242
expect(getCachedZooCodeUserInfo().name).toBe("Jane Doe")
243243
expect(getCachedSubscriptionStatus()).toBe("unknown")
244244
})
245+
246+
it("preserves token and user info when verify returns 5xx (transient backend error)", async () => {
247+
await mockSecrets.store("zoo-code-session-token", "zoo_ext_valid_token")
248+
await mockSecrets.store("zoo-code-user-name", "Jane Doe")
249+
await mockSecrets.store("zoo-code-user-email", "jane@example.com")
250+
mockFetch.mockResolvedValueOnce({
251+
ok: false,
252+
status: 503,
253+
statusText: "Service Unavailable",
254+
})
255+
256+
await initZooCodeAuth(mockContext)
257+
258+
expect(getCachedZooCodeToken()).toBe("zoo_ext_valid_token")
259+
expect(getCachedZooCodeUserInfo().name).toBe("Jane Doe")
260+
expect(getCachedSubscriptionStatus()).toBe("unknown")
261+
})
245262
})
246263

247264
describe("setZooCodeToken", () => {
@@ -365,7 +382,7 @@ describe("zoo-code-auth", () => {
365382
expect(getCachedZooCodeToken()).toBe("zoo_ext_invalid_token")
366383
})
367384

368-
it("returns 'invalid' when the backend returns HTTP error", async () => {
385+
it("returns 'invalid' when the backend returns 4xx", async () => {
369386
await initZooCodeAuth(mockContext)
370387
await setZooCodeToken("zoo_ext_invalid_token")
371388
mockFetch.mockResolvedValueOnce({
@@ -377,6 +394,19 @@ describe("zoo-code-auth", () => {
377394
expect(await verifyZooCodeToken()).toBe("invalid")
378395
})
379396

397+
it("returns 'unreachable' when the backend returns 5xx (transient)", async () => {
398+
await initZooCodeAuth(mockContext)
399+
await setZooCodeToken("zoo_ext_token")
400+
mockFetch.mockResolvedValueOnce({
401+
ok: false,
402+
status: 503,
403+
statusText: "Service Unavailable",
404+
})
405+
406+
expect(await verifyZooCodeToken()).toBe("unreachable")
407+
expect(getCachedZooCodeToken()).toBe("zoo_ext_token")
408+
})
409+
380410
it("returns 'unreachable' when a network error occurs", async () => {
381411
await initZooCodeAuth(mockContext)
382412
await setZooCodeToken("zoo_ext_token")

src/services/zoo-code-auth.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,13 @@ export async function handleAuthCallback(token: string): Promise<boolean> {
245245
signal: AbortSignal.timeout(10_000),
246246
})
247247
if (!response.ok) {
248-
vscode.window.showErrorMessage(t("common:zooAuth.errors.token_verification_failed"))
248+
// Treat 5xx as a transient backend issue (e.g. DB unreachable) so the
249+
// user can retry sign-in instead of being told the token is bad.
250+
if (response.status >= 500) {
251+
vscode.window.showErrorMessage(t("common:zooAuth.errors.could_not_verify_token"))
252+
} else {
253+
vscode.window.showErrorMessage(t("common:zooAuth.errors.token_verification_failed"))
254+
}
249255
return false
250256
}
251257
const data = (await response.json()) as { valid?: boolean }
@@ -271,8 +277,12 @@ export async function handleAuthCallback(token: string): Promise<boolean> {
271277
* Verify the stored token against the backend.
272278
* Returns:
273279
* - "valid" — backend confirmed the token is good
274-
* - "invalid" — backend explicitly rejected the token (HTTP error or valid: false)
275-
* - "unreachable" — network error / timeout; token state is unknown
280+
* - "invalid" — backend explicitly rejected the token (4xx or valid: false)
281+
* - "unreachable" — network error / timeout / 5xx backend error; token state is unknown
282+
*
283+
* 5xx responses are treated as transient: the website returns 503 when the
284+
* database is unreachable, and clearing a real session on a backend hiccup
285+
* forces users to sign in again every time the API blips.
276286
*
277287
* This function has no side-effects; callers are responsible for acting on the result.
278288
*/
@@ -289,6 +299,9 @@ export async function verifyZooCodeToken(): Promise<"valid" | "invalid" | "unrea
289299
})
290300

291301
if (!response.ok) {
302+
if (response.status >= 500) {
303+
return "unreachable"
304+
}
292305
return "invalid"
293306
}
294307

0 commit comments

Comments
 (0)