{"record":{"id":"7f1910d884250f4c","repo":"decolua/9router","slug":"missing-account-email-phone-in-user-info","errorCode":null,"errorMessage":"\"Missing account email/phone in user info\"","messagePattern":"\"Missing account email/phone in user info\"","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/lib/oauth/providers/iflow.js","lineNumber":76,"sourceCode":"      throw new Error(`Failed to fetch user info: ${errorText}`);\n    }\n\n    const result = await userInfoRes.json();\n    if (!result.success) {\n      throw new Error(`User info request failed: ${result.message || 'Unknown error'}`);\n    }\n\n    const userInfo = result.data || {};\n\n    // Validate API key (critical for iFlow)\n    if (!userInfo.apiKey || userInfo.apiKey.trim() === \"\") {\n      throw new Error(\"Empty API key returned from iFlow\");\n    }\n\n    // Validate email/phone\n    const email = userInfo.email?.trim() || userInfo.phone?.trim();\n    if (!email) {\n      throw new Error(\"Missing account email/phone in user info\");\n    }\n\n    return { userInfo };\n  },\n  mapTokens: (tokens, extra) => ({\n    accessToken: tokens.access_token,\n    refreshToken: tokens.refresh_token,\n    expiresIn: tokens.expires_in,\n    apiKey: extra?.userInfo?.apiKey,\n    email: extra?.userInfo?.email || extra?.userInfo?.phone,\n    displayName: extra?.userInfo?.nickname || extra?.userInfo?.name,\n  }),\n};\n\nexport default iflow;\n","sourceCodeStart":58,"sourceCodeEnd":92,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/src/lib/oauth/providers/iflow.js#L58-L92","documentation":"The iFlow user-info payload lacked any account identifier: neither email nor phone was present after trimming. The library requires one because mapTokens uses it as the account's identity (email field) for display and multi-account bookkeeping.","triggerScenarios":"postExchange computes email = userInfo.email?.trim() || userInfo.phone?.trim() and throws when both are falsy — accounts registered only with a username/OAuth identity (no email or phone bound), or a schema change removing those fields from result.data.","commonSituations":"iFlow accounts created via third-party login that never bound an email or phone; privacy-restricted accounts where iFlow omits contact fields; response schema drift moving email/phone into a nested object.","solutions":["Bind an email or phone number to the iFlow account in the iFlow console, then re-run the OAuth flow.","Use a different iFlow account that has a verified email or phone attached.","Inspect the raw userInfo JSON — if identifiers exist under different field names (e.g. account, userId), extend the lookup in iflow.js postExchange.","If iFlow legitimately cannot supply an identifier, relax the validation and fall back to a derived name (e.g. nickname or 'iflow-<id>') when saving the account."],"exampleFix":"// before\nconst email = userInfo.email?.trim() || userInfo.phone?.trim();\nif (!email) {\n  throw new Error(\"Missing account email/phone in user info\");\n}\n// after: accept alternative identifiers\nconst email = userInfo.email?.trim() || userInfo.phone?.trim() || userInfo.account?.trim();\nif (!email) {\n  throw new Error(\"Missing account email/phone in user info (have: \" + Object.keys(userInfo).join(',') + \")\");\n}","handlingStrategy":"validation","validationCode":"// verify the account carries an identifier before running the flow, or pre-inspect the payload\nfunction hasAccountIdentifier(userInfo) {\n  const id = userInfo?.email?.trim() || userInfo?.phone?.trim();\n  return typeof id === 'string' && id.length > 0;\n}","typeGuard":"function isIdentifiableUserInfo(u) {\n  return typeof u === 'object' && u !== null\n    && (typeof u.email === 'string' && u.email.trim() !== ''\n     || typeof u.phone === 'string' && u.phone.trim() !== '');\n}","tryCatchPattern":"try {\n  const { userInfo } = await provider.postExchange(tokens);\n} catch (e) {\n  if (e.message === 'Missing account email/phone in user info') {\n    return { ok: false, reason: 'no-identifier', hint: 'Bind an email or phone to the iFlow account, then retry login' };\n  }\n  throw e;\n}","preventionTips":["Require accounts to have a bound email or phone before they use the OAuth import feature.","If iFlow changes its user-info schema, log Object.keys(userInfo) on this failure to spot renamed identifier fields quickly.","Consider accepting nickname/account-name fallbacks in your own wrapper if identifier-less accounts are legitimate in your deployment.","Surface this error as an actionable message to the end user — it is fixable in the iFlow console, not in code."],"tags":["oauth","validation","user-info","account-identity"],"backgroundTag":"missing-account-identifier","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}