RocketChat/Rocket.Chat · error · Meteor.Error

error-parameter-required

error-parameter-required

Error message

challengeId is required

What it means

Thrown by POST twoFactorChallenges.sendEmailCode when the body has no challengeId. This endpoint is step two of an OAuth login with email 2FA: the login flow first creates a pending challenge, then this endpoint resends the email code for it. challengeId is the only way to address that pending challenge.

Source

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

import { TwoFactorChallenges } from '@rocket.chat/models';
import { isTwoFactorChallengesSendEmailCodeParamsPOST, isTwoFactorChallengesVerifyChallengeParamsPOST } from '@rocket.chat/rest-typings';
import { Accounts } from 'meteor/accounts-base';
import { Meteor } from 'meteor/meteor';

import { getUserForCheck, rememberAuthorizationByToken } from '../../lib/2fa/code';
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;

View on GitHub (pinned to b2c16d5842)

Solutions

  1. Complete the OAuth login call first and take challengeId from its 2fa/challenge response payload, then POST it here
  2. Send {"challengeId": "<id-from-login-step>"} as JSON
  3. Persist the challengeId in client state until the flow finishes so the resend button always has it

Example fix

// before
await sdk.post('twoFactorChallenges.sendEmailCode', {});
// after
const { challengeId } = loginResponse;
await sdk.post('twoFactorChallenges.sendEmailCode', { challengeId });
Defensive patterns

Strategy: validation

Validate before calling

if (typeof challengeId !== 'string' || !challengeId) throw new Error('challengeId is required — obtain it from the OAuth login step');
await sdk.post('twoFactorChallenges.sendEmailCode', { challengeId });

Type guard

const isChallengeId = (v: unknown): v is string => typeof v === 'string' && v.length > 0;

Try / catch

catch (e) { if (e?.error === 'error-parameter-required') surfaceFormError('challengeId'); else throw e; }

Prevention

When it happens

Trigger: POST /api/v1/twoFactorChallenges.sendEmailCode with body {} or {challengeId: ""}; calling it out of order before the OAuth login step that creates the challenge; misreading the login response and sending the interim token or userId instead of challengeId.

Common situations: Custom OAuth clients implementing the 2FA handoff themselves; a frontend resend-code button wired before the challenge response is stored; rate limiting (5 requests/60s on this route) causing earlier calls to fail and leaving the client state empty.

Understand the failure class

Background: Missing required parameter errors: what 'X is required' and 'the required X param is missing' mean, and how to fix them — this error's family across 27 libraries.

Related errors


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