{"record":{"id":"460a757d67548802","repo":"Mintplex-Labs/anything-llm","slug":"invalid-password","errorCode":null,"errorMessage":"Invalid password.","messagePattern":"Invalid password\\.","errorType":"validation","errorClass":null,"httpStatus":500,"severity":"warning","filePath":"server/utils/PasswordRecovery/index.js","lineNumber":73,"sourceCode":"    const index = unmatchedHashes.findIndex((hash) =>\n      bcrypt.compareSync(code, hash)\n    );\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, {","sourceCodeStart":55,"sourceCodeEnd":91,"githubUrl":"https://github.com/Mintplex-Labs/anything-llm/blob/3aec848f2885144aa8f1e53b9731a04310d5d558/server/utils/PasswordRecovery/index.js#L55-L91","documentation":"resetPassword() trims the new password and throws when the result is empty — i.e. the user submitted no password or one made only of whitespace. Deeper password policy (JOI rules) is enforced later inside User.update, so this specific throw is purely the empty-input guard.","triggerScenarios":"Calling resetPassword(token, '', '') or with a whitespace-only new password — e.g. the reset form was submitted with the password field blank.","commonSituations":"Frontend form validation missing/gapped so empty submissions reach the API; automated calls passing undefined defaults ('' is the default for both params); test harnesses calling the function without arguments.","solutions":["Require a non-empty new password in the UI before submitting the reset request","Pass a real password string as the second argument (it defaults to '' when omitted)","Trim client-side too so whitespace-only input is rejected early","Catch the throw and map it to a 400-style form error rather than a 500"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"const newPassword = String(rawPassword ?? '').trim();\nif (!newPassword) return res.status(400).json({ message: 'Password is required.' });\n// only now call resetPassword(token, newPassword, confirmPassword)","typeGuard":"function isValidPasswordInput(pw) {\n  return typeof pw === 'string' && pw.trim().length > 0;\n}","tryCatchPattern":"try {\n  await resetPassword(token, newPassword, confirmPassword);\n} catch (err) {\n  if (err.message === 'Invalid password.') return res.status(400).json({ message: 'Enter a new password.' });\n  throw err;\n}","preventionTips":["Make the new-password field required in the reset form before submit","Trim inputs client-side so whitespace-only values never reach the API","Map this throw to a 400 response, not a 500, in route handlers"],"tags":["password-recovery","input-validation","empty-input"],"backgroundTag":"empty-required-input","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"}