{"record":{"id":"54c1516d90eead66","repo":"RocketChat/Rocket.Chat","slug":"error-parameter-required","errorCode":"error-parameter-required","errorMessage":"challengeId is required","messagePattern":"challengeId is required","errorType":"exception","errorClass":"Meteor.Error","httpStatus":400,"severity":"error","filePath":"apps/meteor/server/api/v1/twoFactorChallenges.ts","lineNumber":20,"sourceCode":"import { TwoFactorChallenges } from '@rocket.chat/models';\nimport { isTwoFactorChallengesSendEmailCodeParamsPOST, isTwoFactorChallengesVerifyChallengeParamsPOST } from '@rocket.chat/rest-typings';\nimport { Accounts } from 'meteor/accounts-base';\nimport { Meteor } from 'meteor/meteor';\n\nimport { getUserForCheck, rememberAuthorizationByToken } from '../../lib/2fa/code';\nimport { emailCheckForOAuth, getTwoFAMethodForOAuth } from '../../lib/oauth/twoFactorAuth';\nimport { generateConnection } from '../ApiClass';\nimport { API } from '../api';\n\nAPI.v1.addRoute(\n\t'twoFactorChallenges.sendEmailCode',\n\t{ validateParams: isTwoFactorChallengesSendEmailCodeParamsPOST, rateLimiterOptions: { intervalTimeInMS: 60000, numRequestsAllowed: 5 } },\n\t{\n\t\tasync post() {\n\t\t\tconst { challengeId } = this.bodyParams;\n\n\t\t\tif (!challengeId) {\n\t\t\t\tthrow new Meteor.Error('error-parameter-required', 'challengeId is required');\n\t\t\t}\n\n\t\t\tconst challenge = await TwoFactorChallenges.findOneByPendingChallengeId(challengeId);\n\n\t\t\tif (!challenge) {\n\t\t\t\tthrow new Meteor.Error('error-challenge-not-found', 'challenge not found');\n\t\t\t}\n\n\t\t\tif (challenge.expireAt && challenge.expireAt < new Date()) {\n\t\t\t\tthrow new Meteor.Error('error-challenge-expired', 'challenge expired');\n\t\t\t}\n\n\t\t\tif (challenge.method !== 'email') {\n\t\t\t\tthrow new Meteor.Error('error-invalid-challenge-method', 'invalid challenge method');\n\t\t\t}\n\n\t\t\tconst { userId } = challenge;\n","sourceCodeStart":2,"sourceCodeEnd":38,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0/apps/meteor/server/api/v1/twoFactorChallenges.ts#L2-L38","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nawait sdk.post('twoFactorChallenges.sendEmailCode', {});\n// after\nconst { challengeId } = loginResponse;\nawait sdk.post('twoFactorChallenges.sendEmailCode', { challengeId });","handlingStrategy":"validation","validationCode":"if (typeof challengeId !== 'string' || !challengeId) throw new Error('challengeId is required — obtain it from the OAuth login step');\nawait sdk.post('twoFactorChallenges.sendEmailCode', { challengeId });","typeGuard":"const isChallengeId = (v: unknown): v is string => typeof v === 'string' && v.length > 0;","tryCatchPattern":"catch (e) { if (e?.error === 'error-parameter-required') surfaceFormError('challengeId'); else throw e; }","preventionTips":["Store the challengeId from the login response before rendering the 2FA screen","Respect the 5-per-minute rate limit on this route"],"tags":["rest-api","validation","two-factor","oauth","authentication"],"backgroundTag":"missing-required-parameter","analyzedSha":"b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0","analyzedAt":"2026-08-18T15:26:39.429Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}