{"record":{"id":"327305f0f9cbf04d","repo":"RocketChat/Rocket.Chat","slug":"error-invalid-challenge-method","errorCode":"error-invalid-challenge-method","errorMessage":"invalid challenge method","messagePattern":"invalid challenge method","errorType":"exception","errorClass":"Meteor.Error","httpStatus":400,"severity":"error","filePath":"apps/meteor/server/api/v1/twoFactorChallenges.ts","lineNumber":34,"sourceCode":"\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\n\t\t\tconst user = await getUserForCheck(userId);\n\n\t\t\tif (!user) {\n\t\t\t\tthrow new Meteor.Error('error-user-not-found', 'user not found');\n\t\t\t}\n\n\t\t\tawait emailCheckForOAuth.sendEmailCode(user);\n\n\t\t\treturn API.v1.success();\n\t\t},\n\t},\n);\n\nAPI.v1.addRoute(","sourceCodeStart":16,"sourceCodeEnd":52,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0/apps/meteor/server/api/v1/twoFactorChallenges.ts#L16-L52","documentation":"Thrown by POST twoFactorChallenges.sendEmailCode when the pending challenge's method is anything other than 'email'. sendEmailCode only knows how to send email codes; a challenge created for TOTP or another method cannot be serviced by it. Which method a challenge uses is decided by the user's configured 2FA during the OAuth login flow.","triggerScenarios":"User's 2FA is TOTP and the client unconditionally calls sendEmailCode for every challenge; a hybrid flow where the challenge method is chosen server-side but the client assumes email; calling sendEmailCode with a challengeId obtained from a TOTP-gated OAuth login.","commonSituations":"Frontends hardcoding the email-code path after testing only against email-2FA accounts; workspaces where admins switched the test user from email codes to authenticator apps; clients not branching on the method field returned with the challenge.","solutions":["Branch on the method reported by the login/challenge response: show the email resend button only for method 'email'","For TOTP challenges, direct the user to their authenticator app and use twoFactorChallenges.verifyChallenge with the 6-digit code","Do not call sendEmailCode speculatively to 'detect' the method — inspect the challenge payload instead"],"exampleFix":"// before\nawait sdk.post('twoFactorChallenges.sendEmailCode', { challengeId }); // assumes email\n// after\nif (challenge.method === 'email') {\n  await sdk.post('twoFactorChallenges.sendEmailCode', { challengeId });\n} else {\n  showAuthenticatorInput(); // TOTP path\n}","handlingStrategy":"try-catch","validationCode":"if (challenge.method && challenge.method !== 'email') throw new Error(`sendEmailCode only handles email challenges, got ${challenge.method}`);","typeGuard":"const isEmailChallenge = (c: { method?: string }): boolean => c.method === 'email';","tryCatchPattern":"catch (e) { if (e?.error === 'error-invalid-challenge-method') renderTotpInput(); else throw e; }","preventionTips":["Branch UI on challenge.method before choosing which endpoint to call","Never probe with sendEmailCode to detect the method"],"tags":["rest-api","two-factor","oauth","authentication","validation"],"backgroundTag":"invalid-two-factor-method","analyzedSha":"b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0","analyzedAt":"2026-08-18T15:26:39.429Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}