{"record":{"id":"b9480ea15ad92ada","repo":"koala73/worldmonitor","slug":"imd-auth-http-response-status","errorCode":null,"errorMessage":"IMD_AUTH_HTTP_${response.status}","messagePattern":"IMD_AUTH_HTTP_(.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"scripts/lib/imd-cyclone-marine.mjs","lineNumber":833,"sourceCode":"  password,\n  fetchFn = globalThis.fetch,\n  userAgent = CHROME_UA,\n  maxBytes = IMD_MAX_BYTES,\n  timeoutMs = IMD_TIMEOUT_MS,\n}) {\n  try {\n    const response = await fetchFn(IMD_OAUTH_TOKEN_URL, {\n      method: 'POST',\n      headers: {\n        Accept: 'application/json',\n        'Content-Type': 'application/json',\n        'User-Agent': userAgent,\n      },\n      body: JSON.stringify({ email, password }),\n      redirect: 'error',\n      signal: AbortSignal.timeout(timeoutMs),\n    });\n    if (!response.ok) throw new Error(`IMD_AUTH_HTTP_${response.status}`);\n    let payload;\n    try {\n      payload = await readBoundedJsonResponse(response, maxBytes);\n    } catch (err) {\n      if (/^IMD_RESPONSE_TOO_LARGE:\\d+$/.test(String(err?.message || ''))) throw err;\n      throw new Error('IMD_AUTH_RESPONSE_INVALID');\n    }\n    const accessToken = typeof payload?.access_token === 'string' ? payload.access_token : '';\n    const tokenType = typeof payload?.token_type === 'string' ? payload.token_type.trim() : '';\n    const expiresIn = Number(payload?.expires_in);\n    if (\n      !/^[\\u0021-\\u007E]+$/.test(accessToken)\n      || tokenType.toLowerCase() !== 'bearer'\n      || !Number.isFinite(expiresIn)\n      || expiresIn <= 0\n    ) {\n      throw new Error('IMD_AUTH_RESPONSE_INVALID');\n    }","sourceCodeStart":815,"sourceCodeEnd":851,"githubUrl":"https://github.com/koala73/worldmonitor/blob/7d06c8633d256c18e38133030bc3613976a96ec9/scripts/lib/imd-cyclone-marine.mjs#L815-L851","documentation":"Thrown by mintImdApiToken in scripts/lib/imd-cyclone-marine.mjs when the POST to the IMD OAuth token endpoint returns a non-2xx HTTP status. The library converts the status code into a stable error string (IMD_AUTH_HTTP_<status>) which imdAuthFailureReason later maps to the failure reason surfaced to callers as { token: null, error }. It signals the upstream authentication API rejected the credential request before any token could be minted.","triggerScenarios":"POST { email, password } to IMD_OAUTH_TOKEN_URL responds with a non-ok status (e.g. 401 for wrong email/password, 403 for blocked IP, 429 for rate limiting, 5xx for IMD outage). redirect: 'error' also makes any redirect surface here as IMD_AUTH_HTTP_3xx.","commonSituations":"Expired or rotated IMD portal credentials in env config; IMD temporarily blocking datacenter IPs or rate-limiting token mints; IMD moving the OAuth endpoint so requests hit a redirect or 404; IMD-side outage returning 5xx.","solutions":["Verify the IMD email/password credentials are current and correctly loaded from env, then retry with valid credentials.","Log response.status (it is embedded in the thrown message) and if 429 back off and retry later with jitter.","Check whether IMD's token URL changed; a 301/302/404 means update IMD_OAUTH_TOKEN_URL.","If 5xx, wait and retry — the failure reason propagates as IMD_AUTH_HTTP_5xx and the worker should retry on the next cycle.","Confirm the outbound network/proxy can reach the IMD host (a proxy failure surfaces as a different IMD_PROXY_CONNECT_HTTP_* reason)."],"exampleFix":"// before\nconst response = await fetchFn(IMD_OAUTH_TOKEN_URL, { method: 'POST', body: JSON.stringify({ email: process.env.IMD_EMAIL, password: process.env.IMD_PASSWORD }) });\n// after\n// validate credentials are present before the request; handle specific statuses\nif (!process.env.IMD_EMAIL || !process.env.IMD_PASSWORD) throw new Error('IMD credentials missing from env');\nconst response = await fetchFn(IMD_OAUTH_TOKEN_URL, { method: 'POST', body: JSON.stringify({ email: process.env.IMD_EMAIL, password: process.env.IMD_PASSWORD }) });\nif (response.status === 429) await sleep(backoffMs); // then retry","handlingStrategy":"try-catch","validationCode":"if (!/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email) || !password) throw new Error('IMD credentials missing or malformed before auth request');","typeGuard":"function hasImdCredentials(env) { return typeof env.IMD_EMAIL === 'string' && env.IMD_EMAIL.includes('@') && typeof env.IMD_PASSWORD === 'string' && env.IMD_PASSWORD.length > 0; }","tryCatchPattern":"const { token, error } = await mintImdApiToken({ email, password });\nif (!token) {\n  if (/^IMD_AUTH_HTTP_429$/.test(error)) await sleep(backoffWithJitter());\n  else if (/^IMD_AUTH_HTTP_5/.test(error)) scheduleRetryNextCycle();\n  else throw new Error(`IMD auth rejected: ${error}`);\n}","preventionTips":["Keep IMD credentials in env and rotate them before expiry; never hardcode them.","Add an alert on repeated IMD_AUTH_HTTP_401/403 so credential rot is caught quickly.","Include a descriptive User-Agent and reputable egress IP to avoid WAF 403s.","Handle 429 with exponential backoff instead of tight retry loops."],"tags":["auth","http","upstream-api"],"backgroundTag":"http-error-response","analyzedSha":"7d06c8633d256c18e38133030bc3613976a96ec9","analyzedAt":"2026-09-15T16:44:39.439Z","contentChangedAt":"2026-09-15T16:44:39.439Z","schemaVersion":2},"datasetVersion":"2026-09-15T18:17:12.389Z"}