RocketChat/Rocket.Chat · error · Meteor.Error

error-challenge-not-found

error-challenge-not-found

Error message

challenge not found

What it means

Thrown by POST twoFactorChallenges.sendEmailCode when TwoFactorChallenges.findOneByPendingChallengeId returns nothing — no pending challenge document exists for that id. Challenges are short-lived, single-flow records created during an OAuth login that requires email 2FA; once resolved, expired, or removed they no longer match.

Source

Thrown at apps/meteor/server/api/v1/twoFactorChallenges.ts:26

import { emailCheckForOAuth, getTwoFAMethodForOAuth } from '../../lib/oauth/twoFactorAuth';
import { generateConnection } from '../ApiClass';
import { API } from '../api';

API.v1.addRoute(
	'twoFactorChallenges.sendEmailCode',
	{ validateParams: isTwoFactorChallengesSendEmailCodeParamsPOST, rateLimiterOptions: { intervalTimeInMS: 60000, numRequestsAllowed: 5 } },
	{
		async post() {
			const { challengeId } = this.bodyParams;

			if (!challengeId) {
				throw new Meteor.Error('error-parameter-required', 'challengeId is required');
			}

			const challenge = await TwoFactorChallenges.findOneByPendingChallengeId(challengeId);

			if (!challenge) {
				throw new Meteor.Error('error-challenge-not-found', 'challenge not found');
			}

			if (challenge.expireAt && challenge.expireAt < new Date()) {
				throw new Meteor.Error('error-challenge-expired', 'challenge expired');
			}

			if (challenge.method !== 'email') {
				throw new Meteor.Error('error-invalid-challenge-method', 'invalid challenge method');
			}

			const { userId } = challenge;

			const user = await getUserForCheck(userId);

			if (!user) {
				throw new Meteor.Error('error-user-not-found', 'user not found');
			}

View on GitHub (pinned to b2c16d5842)

Solutions

  1. Restart the OAuth login flow to create a fresh challenge, then resend the code
  2. Handle this error as 'restart flow', not 'show generic failure' — no retry of the same call can succeed
  3. Make sure only one component drives the challenge; parallel resends/verifies consume it
  4. Keep the challenge-to-page lifetime short in the UI

Example fix

// before
await retry(() => sdk.post('twoFactorChallenges.sendEmailCode', { challengeId })); // hopeless once deleted
// after
try {
  await sdk.post('twoFactorChallenges.sendEmailCode', { challengeId });
} catch (e) {
  if (e.error === 'error-challenge-not-found') window.location = oauthLoginUrl; // restart flow
}
Defensive patterns

Strategy: try-catch

Try / catch

try { await sdk.post('twoFactorChallenges.sendEmailCode', { challengeId }); } catch (e) { if (e?.error === 'error-challenge-not-found') window.location = oauthLoginUrl; else throw e; }

Prevention

When it happens

Trigger: POST with a challengeId from a login attempt that already completed; a challenge removed after too many failed verify attempts (the verify endpoint deletes it); a mistyped or truncated id; calling sendEmailCode long after the initial login started.

Common situations: User leaves the 2FA page open, pastes the code much later, clicks resend, and the challenge is gone; retrying an old flow from a deep-linked page; double-submit racing where one branch finishes the challenge first.

Related errors


AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18). Data as JSON: /api/errors/6a22f5c391cc73c5. Report an issue: GitHub.