{"record":{"id":"6999f1117990d761","repo":"BloopAI/vibe-kanban","slug":"local-login-failed-res-status","errorCode":null,"errorMessage":"Local login failed (${res.status})","messagePattern":"Local login failed \\((.+?)\\)","errorType":"http","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/remote-web/src/shared/lib/api.ts","lineNumber":110,"sourceCode":"    }),\n  });\n  if (!res.ok) {\n    throw new Error(`OAuth redeem failed (${res.status})`);\n  }\n  return res.json();\n}\n\nexport async function localLogin(\n  email: string,\n  password: string,\n): Promise<LocalLoginResponse> {\n  const res = await fetch(`${API_BASE}/v1/auth/local/login`, {\n    method: \"POST\",\n    headers: { \"Content-Type\": \"application/json\" },\n    body: JSON.stringify({ email, password }),\n  });\n  if (!res.ok) {\n    throw new Error(`Local login failed (${res.status})`);\n  }\n  return res.json();\n}\n\nexport async function getInvitation(\n  token: string,\n): Promise<InvitationLookupResponse> {\n  const res = await fetch(`${API_BASE}/v1/invitations/${token}`);\n  if (!res.ok) {\n    throw new Error(`Invitation not found (${res.status})`);\n  }\n  return res.json();\n}\n\nexport async function acceptInvitation(\n  token: string,\n  accessToken: string,\n): Promise<AcceptInvitationResponse> {","sourceCodeStart":92,"sourceCodeEnd":128,"githubUrl":"https://github.com/BloopAI/vibe-kanban/blob/4deb7eca8f381f7cbc1f9d15515a9ab8f8009053/packages/remote-web/src/shared/lib/api.ts#L92-L128","documentation":"localLogin authenticates a user with email and password by POSTing to ${API_BASE}/v1/auth/local/login and expects an access_token/refresh_token pair in the response. If the server responds with any non-OK status it throws 'Local login failed (<status>)'. Most commonly this is a 401 for bad credentials, but any HTTP failure (403, 404, 429, 5xx) produces this error.","triggerScenarios":"Calling localLogin(email, password) and the POST /v1/auth/local/login returns non-2xx: wrong email or password (401), local (password) auth disabled on the server (403/404), account locked or rate-limited after repeated attempts (429), wrong API_BASE so the route 404s, or a server error (500/502/503).","commonSituations":"User mistypes credentials on the login form; deployment has local_auth_enabled=false so the local login endpoint rejects or does not exist; brute-force protection throttles a user retrying many times; VITE_API_BASE_URL misconfigured in self-hosted setups; backend down during deploy.","solutions":["If status is 401, prompt the user to re-enter correct email/password (do not auto-retry).","Confirm local password auth is enabled on the server (check AuthMethodsResponse.local_auth_enabled from getAuthMethods before showing the form).","Verify VITE_API_BASE_URL points at the server that serves /v1/auth/local/login.","If 429, wait for the rate-limit window before allowing another attempt.","Check server logs for /v1/auth/local/login on 5xx statuses and retry once transient errors clear.","Validate inputs client-side (non-empty email/password, email format) before calling."],"exampleFix":"// before\nawait localLogin(email, password);\n\n// after: guard against disabled local auth and handle 401\nconst methods = await getAuthMethods();\nif (!methods.local_auth_enabled) throw new Error('Local login is disabled; use OAuth');\ntry {\n  await localLogin(email.trim(), password);\n} catch (e) {\n  if (/\\(401\\)/.test(e.message)) showInvalidCredentials();\n  else throw e;\n}","handlingStrategy":"try-catch","validationCode":"// validate before calling localLogin\nconst emailOk = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email);\nif (!emailOk || password.length === 0) {\n  throw new Error('Enter a valid email and password');\n}\n// optionally ensure local auth is enabled\nconst methods = await getAuthMethods();\nif (!methods.local_auth_enabled) throw new Error('Local login is disabled on this server');","typeGuard":"function isLocalLoginResponse(x: unknown): x is { access_token: string; refresh_token: string } {\n  return typeof x === 'object' && x !== null\n    && typeof (x as any).access_token === 'string'\n    && typeof (x as any).refresh_token === 'string';\n}","tryCatchPattern":"try {\n  const tokens = await localLogin(email, password);\n  saveTokens(tokens);\n} catch (e) {\n  const status = (e as Error).message.match(/\\((\\d+)\\)/)?.[1];\n  if (status === '401') showFormError('Incorrect email or password.');\n  else if (status === '429') showFormError('Too many attempts; try again shortly.');\n  else showFormError('Login is temporarily unavailable. Please retry.');\n}","preventionTips":["Show clear 'invalid credentials' messaging for 401 instead of a generic error.","Check local_auth_enabled via getAuthMethods before rendering the password form.","Validate email/password fields client-side before submitting.","Respect rate limits: throttle submit attempts and disable the button while pending.","Verify VITE_API_BASE_URL per environment and alert on 5xx for the login endpoint."],"tags":["authentication","http","login"],"backgroundTag":"local-login-failed","analyzedSha":"4deb7eca8f381f7cbc1f9d15515a9ab8f8009053","analyzedAt":"2026-08-29T09:24:13.446Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}