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
- Complete the OAuth login call first and take challengeId from its 2fa/challenge response payload, then POST it here
- Send {"challengeId": "<id-from-login-step>"} as JSON
- 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
- Store the challengeId from the login response before rendering the 2FA screen
- Respect the 5-per-minute rate limit on this route
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
- error-invalid-challenge-method
- error-challenge-not-found
- error-challenge-expired
- totp-max-attempts
- error-user-not-found
AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18).
Data as JSON: /api/errors/54c1516d90eead66.
Report an issue: GitHub.