decolua/9router · error · Error

"Missing account email/phone in user info"

Error message

"Missing account email/phone in user info"

What it means

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.

Source

Thrown at src/lib/oauth/providers/iflow.js:76

      throw new Error(`Failed to fetch user info: ${errorText}`);
    }

    const result = await userInfoRes.json();
    if (!result.success) {
      throw new Error(`User info request failed: ${result.message || 'Unknown error'}`);
    }

    const userInfo = result.data || {};

    // Validate API key (critical for iFlow)
    if (!userInfo.apiKey || userInfo.apiKey.trim() === "") {
      throw new Error("Empty API key returned from iFlow");
    }

    // Validate email/phone
    const email = userInfo.email?.trim() || userInfo.phone?.trim();
    if (!email) {
      throw new Error("Missing account email/phone in user info");
    }

    return { userInfo };
  },
  mapTokens: (tokens, extra) => ({
    accessToken: tokens.access_token,
    refreshToken: tokens.refresh_token,
    expiresIn: tokens.expires_in,
    apiKey: extra?.userInfo?.apiKey,
    email: extra?.userInfo?.email || extra?.userInfo?.phone,
    displayName: extra?.userInfo?.nickname || extra?.userInfo?.name,
  }),
};

export default iflow;

View on GitHub (pinned to 90b52e06ff)

Solutions

  1. Bind an email or phone number to the iFlow account in the iFlow console, then re-run the OAuth flow.
  2. Use a different iFlow account that has a verified email or phone attached.
  3. Inspect the raw userInfo JSON — if identifiers exist under different field names (e.g. account, userId), extend the lookup in iflow.js postExchange.
  4. 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.

Example fix

// before
const email = userInfo.email?.trim() || userInfo.phone?.trim();
if (!email) {
  throw new Error("Missing account email/phone in user info");
}
// after: accept alternative identifiers
const email = userInfo.email?.trim() || userInfo.phone?.trim() || userInfo.account?.trim();
if (!email) {
  throw new Error("Missing account email/phone in user info (have: " + Object.keys(userInfo).join(',') + ")");
}
Defensive patterns

Strategy: validation

Validate before calling

// verify the account carries an identifier before running the flow, or pre-inspect the payload
function hasAccountIdentifier(userInfo) {
  const id = userInfo?.email?.trim() || userInfo?.phone?.trim();
  return typeof id === 'string' && id.length > 0;
}

Type guard

function isIdentifiableUserInfo(u) {
  return typeof u === 'object' && u !== null
    && (typeof u.email === 'string' && u.email.trim() !== ''
     || typeof u.phone === 'string' && u.phone.trim() !== '');
}

Try / catch

try {
  const { userInfo } = await provider.postExchange(tokens);
} catch (e) {
  if (e.message === 'Missing account email/phone in user info') {
    return { ok: false, reason: 'no-identifier', hint: 'Bind an email or phone to the iFlow account, then retry login' };
  }
  throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30). Data as JSON: /api/errors/7f1910d884250f4c. Report an issue: GitHub.