{"record":{"id":"6c5f93909e55f52a","repo":"Mintplex-Labs/anything-llm","slug":"passwords-do-not-match","errorCode":null,"errorMessage":"Passwords do not match","messagePattern":"Passwords do not match","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"server/utils/PasswordRecovery/index.js","lineNumber":75,"sourceCode":"    );\n    if (index === -1) return false;\n    unmatchedHashes.splice(index, 1);\n    return true;\n  });\n  if (!validCodes) return { success: false, error: \"Invalid recovery codes.\" };\n\n  const { passwordResetToken, error } = await PasswordResetToken.create(\n    user.id\n  );\n  if (!!error) return { success: false, error };\n  return { success: true, resetToken: passwordResetToken.token };\n}\n\nasync function resetPassword(token, _newPassword = \"\", confirmPassword = \"\") {\n  const newPassword = String(_newPassword).trim(); // No spaces in passwords\n  if (!newPassword) throw new Error(\"Invalid password.\");\n  if (newPassword !== String(confirmPassword))\n    throw new Error(\"Passwords do not match\");\n\n  const resetToken = await PasswordResetToken.findUnique({\n    token: String(token),\n  });\n  if (!resetToken || resetToken.expiresAt < new Date()) {\n    return { success: false, message: \"Invalid reset token\" };\n  }\n\n  // JOI password rules will be enforced inside .update.\n  const { error } = await User.update(resetToken.user_id, {\n    password: newPassword,\n  });\n\n  // seen_recovery_codes is not publicly writable\n  // so we have to do direct update here\n  await User._update(resetToken.user_id, {\n    seen_recovery_codes: false,\n  });","sourceCodeStart":57,"sourceCodeEnd":93,"githubUrl":"https://github.com/Mintplex-Labs/anything-llm/blob/3aec848f2885144aa8f1e53b9731a04310d5d558/server/utils/PasswordRecovery/index.js#L57-L93","documentation":"resetPassword() compares the trimmed new password against the raw confirmPassword (String(confirmPassword), no trim) and throws on mismatch. Note the asymmetry: 'pass ' passes when confirm is 'pass', but a leading/trailing space in the confirm field alone fails the equality check.","triggerScenarios":"Confirm field differs from the new password — typo, or whitespace padding: newPassword 'secret' with confirmPassword ' secret ' fails because only the new password is trimmed.","commonSituations":"User retypes with a typo; browser autofill inserting different values; mobile keyboards adding a trailing space to one field; frontend not comparing fields before submit.","solutions":["Re-enter the password and confirmation so they match exactly","Add client-side equality validation before calling resetPassword","Trim the confirm input client-side to match the server's trimming of the new password","Catch the throw and show a 'passwords do not match' form message instead of a generic failure"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"const pw = newPassword.trim();\nconst confirm = String(confirmPassword ?? '').trim();\nif (!pw || pw !== confirm) {\n  return res.status(400).json({ message: 'Passwords do not match or are empty.' });\n}","typeGuard":"function passwordsMatch(a, b) {\n  return typeof a === 'string' && typeof b === 'string' && a.trim() === b.trim() && a.trim().length > 0;\n}","tryCatchPattern":"try {\n  await resetPassword(token, newPassword, confirmPassword);\n} catch (err) {\n  if (err.message === 'Passwords do not match') return res.status(400).json({ message: 'Passwords do not match.' });\n  throw err;\n}","preventionTips":["Compare both fields client-side before submitting the reset request","Trim the confirm field the same way the server trims the new password","Disable submit until both fields are non-empty and identical"],"tags":["password-recovery","input-validation","password-mismatch"],"backgroundTag":"password-mismatch","analyzedSha":"3aec848f2885144aa8f1e53b9731a04310d5d558","analyzedAt":"2026-08-18T10:02:21.017Z","contentChangedAt":"2026-08-18T10:02:21.017Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}