{"record":{"id":"7bcaf209ad6009ee","repo":"decolua/9router","slug":"scopes-is-required","errorCode":null,"errorMessage":"scopes is required","messagePattern":"scopes is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/lib/oauth/kiroExternalIdp.js","lineNumber":107,"sourceCode":"  }\n\n  const authMethod = normalizeString(input.auth_method || input.authMethod);\n  if (authMethod && authMethod !== \"external_idp\") {\n    throw new Error(\"Only external_idp Kiro auth is supported by this importer\");\n  }\n\n  const accessToken = normalizeString(input.access_token || input.accessToken);\n  const refreshToken = normalizeString(input.refresh_token || input.refreshToken);\n  const clientId = normalizeString(input.client_id || input.clientId);\n  const tokenEndpoint = validateMicrosoftTokenEndpoint(input.token_endpoint || input.tokenEndpoint);\n  const profileArn = normalizeString(input.profile_arn || input.profileArn);\n  const region = normalizeString(input.region) || DEFAULT_REGION;\n  const scope = normalizeScope(input.scopes || input.scope);\n\n  if (!accessToken) throw new Error(\"access_token is required\");\n  if (!refreshToken) throw new Error(\"refresh_token is required\");\n  if (!clientId) throw new Error(\"client_id is required\");\n  if (!scope) throw new Error(\"scopes is required\");\n  if (!profileArn) throw new Error(\"profile_arn is required\");\n\n  const payload = decodeJwtPayload(accessToken);\n  const email = input.email || payload?.email || payload?.preferred_username || payload?.upn || payload?.sub || null;\n\n  return {\n    accessToken,\n    refreshToken,\n    expiresAt: resolveExpiresAt(input),\n    email,\n    providerSpecificData: {\n      profileArn,\n      region,\n      authMethod: \"external_idp\",\n      provider: \"CLIProxyAPI\",\n      clientId,\n      tokenEndpoint,\n      scope,","sourceCodeStart":89,"sourceCodeEnd":125,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/src/lib/oauth/kiroExternalIdp.js#L89-L125","documentation":"A non-empty scopes (or scope) value is required — the OAuth scope string sent with refresh requests and persisted in providerSpecificData. Accepts an array of strings or a single space-separated string; it must normalize to something non-empty.","triggerScenarios":"Auth JSON missing scopes/scope, scopes: [], scopes: [\"\"], or scopes: \"   \" — normalizeScope returns an empty string and the guard throws.","commonSituations":"Hand-built auth JSON omitting scopes; an array containing only empty strings; a tool exporting scope under a different key (e.g. 'scope_list') the importer doesn't read.","solutions":["Add scopes with the space-separated scope string used by the login (e.g. 'openid profile email offline_access')","If passing an array, ensure at least one non-empty string element","Match the scopes exactly to what the original token was issued with — mismatched scopes can make refresh fail upstream","Rename the key to scopes or scope in your auth document"],"exampleFix":"// before\n{ \"access_token\": \"...\", \"refresh_token\": \"...\", \"client_id\": \"...\" }\n// after\n{ \"access_token\": \"...\", \"refresh_token\": \"...\", \"client_id\": \"...\", \"scopes\": \"openid profile email offline_access\" }","handlingStrategy":"validation","validationCode":"const sc = auth.scopes ?? auth.scope;\nconst normalized = Array.isArray(sc)\n  ? sc.filter(s => typeof s === 'string' && s.trim()).join(' ')\n  : (typeof sc === 'string' ? sc.trim() : '');\nif (!normalized) throw new Error('scopes missing or empty in Kiro auth');","typeGuard":"function hasScopes(a) {\n  const s = a?.scopes ?? a?.scope;\n  if (Array.isArray(s)) return s.some(x => typeof x === 'string' && x.trim());\n  return typeof s === 'string' && s.trim() !== '';\n}","tryCatchPattern":"try {\n  normalizeKiroExternalIdpAuth(auth);\n} catch (e) {\n  if (e.message === 'scopes is required') {\n    console.error('Add the OAuth scopes used at login, e.g. \"openid profile email offline_access\"');\n  }\n  throw e;\n}","preventionTips":["Store scopes exactly as issued at login so refresh requests match the original grant","Filter empty strings out of scope arrays before import","Use 'scopes' or 'scope' as the key — other names are ignored","Include offline_access in scopes if you need refresh tokens at all"],"tags":["oauth","missing-field","scopes","validation"],"backgroundTag":"missing-required-field","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}