{"record":{"id":"26830007382aa3bc","repo":"Hmbown/CodeWhale","slug":"missing-login-limiter","errorCode":null,"errorMessage":"Missing login limiter","messagePattern":"Missing login limiter","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"critical","filePath":"web/app/api/admin/login/route.ts","lineNumber":32,"sourceCode":"}\n\nexport async function POST(req: Request) {\n  const env = await getAgentEnv();\n  const url = new URL(req.url);\n  const localeFromQuery = pickLocale(url.searchParams.get(\"locale\"));\n\n  if (!env.MAINTAINER_TOKEN) {\n    return new NextResponse(\"Not configured\", {\n      status: 503,\n      headers: { \"Cache-Control\": \"no-store\" },\n    });\n  }\n\n  // One maintainer principal: key by that account, never by attacker-controlled\n  // headers or submitted tokens. The binding shares counters across isolates\n  // in a Cloudflare location. An unavailable limiter must not disable the gate.\n  try {\n    if (!env.ADMIN_LOGIN_LIMITER) throw new Error(\"Missing login limiter\");\n    const { success } = await env.ADMIN_LOGIN_LIMITER.limit({ key: \"codewhale-web:admin-login\" });\n    if (!success) {\n      return new NextResponse(\"Too many login attempts\", {\n        status: 429,\n        headers: { \"Cache-Control\": \"no-store\", \"Retry-After\": \"60\" },\n      });\n    }\n  } catch {\n    return new NextResponse(\"Login temporarily unavailable\", {\n      status: 503,\n      headers: { \"Cache-Control\": \"no-store\", \"Retry-After\": \"60\" },\n    });\n  }\n\n  let form: URLSearchParams;\n  try {\n    form = await readBoundedUrlEncodedForm(req, MAX_LOGIN_BODY_BYTES);\n  } catch (error) {","sourceCodeStart":14,"sourceCodeEnd":50,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/web/app/api/admin/login/route.ts#L14-L50","documentation":"The admin login route gates every login attempt behind a Cloudflare rate-limit binding, env.ADMIN_LOGIN_LIMITER. If the binding is absent the code deliberately throws instead of allowing logins ungated — an unavailable limiter must not disable the gate.","triggerScenarios":"POST /api/admin/login when the deployed Worker/Pages environment lacks the ADMIN_LOGIN_LIMITER binding (e.g. wrangler.toml missing the rate limiting binding, or a local dev run without `wrangler dev` bindings).","commonSituations":"Deploying web/ without updating wrangler config after the limiter was introduced; running `next dev` locally where Cloudflare bindings do not exist; preview environments created from an outdated config.","solutions":["Add the ADMIN_LOGIN_LIMITER rate-limiting binding to wrangler.toml/wrangler.jsonc and redeploy.","Run the route through `wrangler dev` (or Miniflare) so bindings are available instead of plain `next dev`.","Verify with `wrangler deployments list` / `wrangler versions view` that the deployed version carries the binding."],"exampleFix":"// before (wrangler.jsonc)\n{\n  \"compatibility_date\": \"2025-01-01\"\n}\n// after (wrangler.jsonc)\n{\n  \"compatibility_date\": \"2025-01-01\",\n  \"ratelimits\": [{ \"name\": \"ADMIN_LOGIN_LIMITER\", \"namespace_id\": \"1001\" }]\n}","handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  const res = await fetch(\"/api/admin/login\", { method: \"POST\", body: creds });\n} catch (err) {\n  if (String(err.message).includes(\"Missing login limiter\")) {\n    // Deployment misconfiguration: verify Cloudflare bindings before retrying.\n    reportDeploymentConfigError(err);\n  }\n}","preventionTips":["Declare ADMIN_LOGIN_LIMITER in wrangler config and keep it in every environment (prod, preview, local wrangler dev).","Add a CI check that wrangler config includes the required bindings before deploy.","Never bypass the limiter check locally — run auth routes through wrangler dev, not plain next dev."],"tags":["cloudflare","authentication","configuration"],"backgroundTag":"missing-env-var","analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}