hiteshchoudhary / chai-backend

A video series on chai aur code youtube channel
4.74k stars 704 forks source link

Password is not being hashed properly. You get Invalid Password despite both the passwords being the same. #47

Open Sreejit-Sengupto opened 8 months ago

Sreejit-Sengupto commented 8 months ago

I encountered this bug while logging in the user. The login controller was throwing "Invalid Password" each time despite the passwords being the same.

So, I thought of checking it out on Replit. I registered the user and got hashed password from the MongoDB document, and then compared them in Replit but the return value was still false, here are the images for reference.

User registered successfully image

Error while logging in image

Hashed password from MongoDB image

Error on Replit image

Then I asked ChatGPT to generate a code for the same and it started working as it should work

    const user = this;
    if (!user.isModified('password')) {
        return next();
    }
    try {
        const salt = await bcrypt.genSalt(10);
        const hashedPassword = await bcrypt.hash(user.password, salt);
        user.password = hashedPassword;
        next();
    } catch (error) {
        next(error);
    }
});

I am still trying to understand what was wrong in the original code. Sir it's a request please check this on your side and let us know about it.

ganeswar07 commented 8 months ago

Send your previous code of user.model and login handler

aashishgulshan commented 8 months ago

I am also facing the same issue, I was tried with different code but again its showing the same issue.

image

Note: - Normally text easily able to hash.

image

blackcodeau commented 5 months ago

`import { asyncHandler } from "../utils/asynchandler.js"; import { ApiError } from "../utils/apierror.js" import { User } from "../models/user.js" import { ApiResponse } from "../utils/apiresponse.js";

const generateAccessTokenaandrefreshtokens = async (userId) => { try { const user = await User.findById(userId) const accesstoken = user.generateAccessToken() const refreshtoken = user.generateRefreshToken()

user.refreshtoken = refreshtoken
await user.save({ validateBeforeSave: false })

return { accesstoken, refreshtoken }

} catch (error) { throw new ApiError(500, "internal server error")

} }

const registerUser = asyncHandler(async (req, res) => { const { fullname, email, password, phonenumber } = req.body console.log("email:", email); console.log("fullname:", fullname); console.log("phonenumber", phonenumber); console.log("password:", password);

if (fullname === "") { throw new ApiError(404, "fullname is required") } if (email === "") { throw new ApiError(400, "email is required") } if (phonenumber === "") { throw new ApiError(400, "phonenumber is required") } if (password === "") { throw new ApiError(400, "password is required") }

const existinguser = await User.findOne({ $or: [{ email }, { fullname}] })

if (existinguser) { throw new ApiError(409, "user with this userid or email alread exist") }

const user = await User.create({ fullname, email, phonenumber, password })

const createduser = await User.findById(user._id).select( "-password -refreshtoken" )

if (!createduser) { throw new ApiError(550, "user not created")

}

return res.status(201).json( new ApiResponse(200, createduser, "user registered successfully") ) })

const loginUser = asyncHandler(async (req, res) => {

const { email, fullname, password } = req.body console.log(email);

if(!(email || fullname)){ throw new ApiError(400," plese enter valid information") }

const user = await User.findOne({ $or: [{ email },{ fullname }] })

if (!user) { throw new ApiError(404, "user do not exists") }

const isPasswordvalid = await user.isPasswordCorrect(password)

if ( !isPasswordvalid ) { throw new ApiError(401, "incorrect passw") }

const { accesstoken , refreshtoken } = await generateAccessTokenaandrefreshtokens(user._id)

const loggedinUser = await User.findById(user._id).select("-password -refreshtoken")

const option = { httpOnly: true, secure: true }

return res .status(200) .cookie("accesstoken", accesstoken , option) .cookie("refreshtoken", refreshtoken , option) .json( new ApiResponse( 200, { user: loggedinUser, accesstoken, refreshtoken } ,"user logged in successfully") )

})

const logoutuser = asyncHandler(async (req, res) => { await User.findByIdAndUpdate( req.user._id, { $set: { refreshtoken: "undefined" } }, { new: true } )

const option = { httpOnly: true, secure: true } return res .status(200) .clearcookie("accesstoken", option) .clearcookie("refreshtoken", option) .json(new ApiResponse(200, {}, "user logged out ")) })

export {

registerUser, logoutuser, loginUser

}`

Ayusht777 commented 1 month ago

same issue while comparing password

`import { asyncHandler } from "../utils/asyncHandler.js"; import { ApiError } from "../utils/ApiError.js"; import { User } from "../models/user.model.js"; import { uploadFileCloudinary } from "../service/cloudinary.js"; import { ApiResponse } from "../utils/ApiResponse.js"; import jwt from "jsonwebtoken"; const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$/; const passwordRegex = /^(?=.[A-Z])(?=.\d)(?=.[!@#$%^&])[A-Za-z\d!@#$%^&*]{6,}$/;

const MIN_FILE_SIZE = 20 1024; // 20 KB const MAX_FILE_SIZE = 5 1024 * 1024; // 5 MB const allowedMimeTypes = [ "image/jpeg", "image/png", "image/jpg", "image/webp", "image/avif", "image/gif", ];

const generateAccessTokenAndRefreshToken = async (userId) => { try { const user = await User.findById(userId); // user object from model if (!user) { throw new ApiError( 500, "Something went wrong while generating Refresh & Access Token due to user not found" ); } const accessToken = await user.generateAccessToken(); const refreshToken = await user.generateRefreshToken(); user.refreshToken = refreshToken; await user.save({ ValidateBeforeSave: false }); //off auto validation return { accessToken, refreshToken }; } catch (error) { throw new ApiError( 500, "Something went wrong while generating Refresh & Access Token" ); } };

const optionsForAccessTokenCookie = { httpOnly: true, secure: true, maxAge: 15 60 1000, // 15 minutes }; const optionsForRefreshTokenCookie = { httpOnly: true, secure: true, maxAge: 7 24 60 60 1000, // 7 days }; const optionsForLoginStateCookie = { httpOnly: true, secure: true, }; const registerUser = asyncHandler(async (req, res) => { // get user detail from frontend // validation on backend site {not empty} // check if user already exit check {email,username} // check for images ,check for avatar // check for multer image added then check for cloudaniry // create user object in db // send response to frontend site (remove password and refresh token) // check user creation // return response const { email, password, userName, fullName } = req.body; // console.log(req.body); if ( [email, password, userName, fullName].some( (inputFields) => inputFields?.trim() == "" //trim used to remove space ) ) { throw new ApiError(400, "All fields are required"); } if (userName.length <= 3) { throw new ApiError(422, "Username must be at least 3 characters"); }

if (fullName.length <= 3) { throw new ApiError(422, "Full name must be at least 3 characters"); }

if (!RegExp(emailRegex).test(email)) { throw new ApiError(422, "Invalid email address"); }

if (password.length < 6) { throw new ApiError( 422, "Password must be at least 6 characters" ); /Error Code 422 (Unprocessable Entity): Both checks for password length and complexity use this code. It indicates that the password does not meet the required length or complexity, which are both issues with the provided data's validity./ } else if (!RegExp(passwordRegex).test(password)) { throw new ApiError( 400, "Password must contains a Capital letter,spacial symbol and number" ); }

const existedUser = await User.findOne({ $or: [{ email }, { userName }] }); if (existedUser) { throw new ApiError(409, "User already exists"); }

const avatar = req.files?.avatar?.[0]?.path; // console.log(avatar); const coverImage = req.files?.coverImage?.[0]?.path;

if (!avatar) { throw new ApiError(400, "Avatar is required"); // 400 Bad Request: No file uploaded }

const { mimetype, size } = avatar;

// Validate file type if (allowedMimeTypes.includes(mimetype)) { throw new ApiError( 415, "Unsupported file type. Allowed formats are: JPEG, PNG, JPG, WebP, AVIF, GIF" ); // 415 Unsupported Media Type }

// Validate file size if (size < MIN_FILE_SIZE || size > MAX_FILE_SIZE) { throw new ApiError( 413, File size must be between ${MIN_FILE_SIZE / 1024} KB and ${ MAX_FILE_SIZE / 1024 / 1024 } MB // 413 Payload Too Large ); }

if (coverImage) { const { mimetype, size } = coverImage; if (!allowedMimeTypes.includes(mimetype)) { throw new ApiError( 415, "Unsupported file type. Allowed formats are: JPEG, PNG, JPG, WebP, AVIF, GIF" ); // 415 Unsupported Media Type }

// Validate file size
if (size < MIN_FILE_SIZE || size > MAX_FILE_SIZE) {
  throw new ApiError(
    413,
    `File size must be between ${MIN_FILE_SIZE / 1024} KB and ${
      MAX_FILE_SIZE / 1024 / 1024
    } MB` // 413 Payload Too Large
  );
}

}

const avatarUrl = await uploadFileCloudinary(avatar);

if (!avatarUrl) { throw new ApiError(500, "Failed to upload avatar. Please try again."); }

let coverImageUrl = ""; if (coverImage) { coverImageUrl = await uploadFileCloudinary(coverImage); if (!coverImageUrl) { throw new ApiError( 500, "Failed to upload cover image. Please try again." ); } }

const user = await User.create({ userName: userName.toLowerCase(), email, fullName, avatar: avatarUrl, coverImage: coverImageUrl?.url || "", password, });

const newUser = await User.findById(user._id).select( "-password -refreshToken" ); //remove password & refresh token

if (!newUser) { throw new ApiError(500, "Failed to create user. Please try again."); }

return res.status(201).json(new ApiResponse(201, newUser, "User created")); });

const loginUser = asyncHandler(async (req, res) => { //get data fields like username , mail , password //validate all fields //check user existed in db or not //generate access token and refresh token //send cookie secure // send response

// if (req.cookies.accessToken || req.cookies.refreshToken) { // throw new ApiError(400, "User is already logged in."); // } const { userName, email, password } = req.body; // console.log(userName, email, password);

if (!userName && !email) { throw new ApiError(422, "username & email is required"); } if (!password) { throw new ApiError(422, "Password is required"); }

if (userName.length <= 3) { throw new ApiError(411, "username should be at least of 3 letters"); } if (!RegExp(emailRegex).test(email)) { throw new ApiError(406, "enter a valid email"); } if (password.length < 6) { throw new ApiError(411, "password should be at least of 6 letters"); } else if (!RegExp(passwordRegex).test(password)) { throw new ApiError( 406, "Password must contains a Capital letter,spacial symbol and numbe" ); }

const user = await User.findOne({ $or: [{ userName }, { email }], }); if (!user) throw new ApiError(401, "User does not exist !!"); console.log("user->", user);

const isPasswordValid = await user.isPasswordCorrect(password); console.log(isPasswordValid); if (isPasswordValid) throw new ApiError(401, "Invalid User Password"); //refer user model for isPasswordCorrect Checked is not inverse

const { accessToken, refreshToken } = await generateAccessTokenAndRefreshToken(user._id);

const loggedInUser = await User.findById(user._id).select( "-password -refreshToken" );

return res .status(200) .cookie("accessToken", accessToken, optionsForAccessTokenCookie) .cookie("refreshToken", refreshToken, optionsForRefreshTokenCookie) .cookie("loginSate", true, optionsForLoginStateCookie) .json( new ApiResponse( 200, { user: loggedInUser, accessToken, refreshToken }, "User Logged In Successfully" ) ); });

const logoutUser = asyncHandler(async (req, res) => { const user = await User.findById(req.user._id); if (!user || !user.refreshToken) { throw new ApiError(401, "User is not logged in or session has expired"); } if (user && user.refreshToken) { await User.findByIdAndUpdate( req.user._id, { $set: { refreshToken: undefined }, }, { new: true } ); } else { throw new ApiError(401, "Unauthorized request for LOGOUT"); }

return res .status(200) .clearCookie("accessToken", optionsForAccessTokenCookie) .clearCookie("refreshToken", optionsForRefreshTokenCookie) .json(new ApiResponse(200, {}, "User Logout successfully")); });

const refreshAccessToken = asyncHandler(async (req, res) => { const receivedRefreshedToken = req.cookies.refreshToken || req.body.refreshToken;

if (!receivedRefreshedToken) { throw new ApiError(401, "Unauthorized request"); }

try { const decodedRefreshToken = await jwt.verify( receivedRefreshedToken, process.env.REFRESH_TOKEN_SECRET );

if (!decodedRefreshToken) {
  throw new ApiError(401, "Refresh token is not decoded");
}
const user = await User.findById(decodedRefreshToken?._id); //refer user model for this

if (!user) {
  throw new ApiError(401, "Invalid Refresh token");
}
if (receivedRefreshedToken !== user?.refreshToken) {
  throw new ApiError(401, "Refresh token is expired or used");
}
const { generatedAccessToken, generatedRefreshToken } =
  await generateAccessTokenAndRefreshToken(user._id);

return res
  .status(200)
  .cookie("accessToken", generatedAccessToken, optionsForAccessTokenCookie)
  .cookie(
    "refreshToken",
    generatedRefreshToken,
    optionsForRefreshTokenCookie
  )
  .json(
    new ApiResponse(
      200,
      {
        accessToken: generatedAccessToken,
        refreshToken: generatedRefreshToken,
      },
      "successfully generated Access and refresh token"
    )
  );

} catch (error) { throw new ApiError(401, error?.message || "Invalid Refresh Token"); } });

const changeCurrentPassword = asyncHandler(async (req, res) => { //one case possible if user login or not then it should able to access this route //password body current and new password // error handling //check if current password for user is matched in db // if matched then update the password in db // if not matched then throw error const { currentPassword, newPassword } = req.body; if (!currentPassword || !newPassword) { throw new ApiError(422, "current password and new password is required"); } if (currentPassword.length < 6 || newPassword.length < 6) { throw new ApiError(411, "password should be at least of 6 letters"); } if (!RegExp(passwordRegex).test(currentPassword)) { throw new ApiError( 406, "Password must contains a Capital letter,spacial symbol and number" ); }

if (!RegExp(passwordRegex).test(newPassword)) { throw new ApiError( 406, "Password must contains a Capital letter,spacial symbol and number" ); }

if (currentPassword === newPassword) { throw new ApiError(406, "New password is same as current password"); } const user = await User.findById(req.user._id); //see in auth middleware

if (!user) { throw new ApiError(404, "User does not existed"); }

const isPasswordValid = await user.isPasswordCorrect(password); if (!isPasswordValid) throw new ApiError(401, "Invalid user password");

user.password = newPassword; await user.save({ ValidateBeforeSave: false });

return res .status(200) .json(new ApiResponse(200, {}, "Current password changed successfully")); });

const getCurrentUser = asyncHandler(async (req, res) => { return res .status(200) .json(new ApiResponse(200, req.user, "User fetched successfully")); });

const updateAccountDetails = asyncHandler(async (req, res) => { const { fullName, email } = req.body; if (!fullName || !email) { throw new ApiError(422, "fullName and email is required"); } if (fullName.length < 3) { throw new ApiError(411, "fullName should be at least of 6 letters"); } if (!RegExp(emailRegex).test(email)) { throw new ApiError(406, "Invalid email"); }

const user = User.findById( req.user._id, { $set: { fullName: fullName, email: email, }, }, { new: true } ).select("-password");

if (!user) { throw new ApiError(404, "User does not existed"); }

return res .status(200) .json(new ApiResponse(200, user, "User updated successfully")); });

export { registerUser, loginUser, logoutUser, refreshAccessToken, changeCurrentPassword, getCurrentUser, updateAccountDetails, }; `

controller

`import { Schema, model } from "mongoose"; import jwt from "jsonwebtoken"; import bcrypt from "bcryptjs";

const userSchema = new Schema( { userName: { type: String, require: true, unique: true, lowercase: true, trim: true, index: true, }, email: { type: String, require: true, unique: true, lowercase: true, trim: true, }, fullName: { type: String, require: true, trim: true, index: true, }, avatar: { type: String, //cloudinary require: true, }, coverImage: { type: String, }, watchHistory: { type: Schema.Types.ObjectId, ref: "Video", }, password: { type: String, require: [true, "Password is Required"], }, refreshToken: { type: String, }, }, { timestamps: true } );

userSchema.pre("save", async function (next) { if (!this.isModified("password")) return next(); const salt = await bcrypt.genSalt(10); this.password = await bcrypt.hash(this.password, salt); next(); });

userSchema.methods.isPasswordCorrect = async function (password) { return await bcrypt.compare(password, this.password); }; userSchema.methods.generateAccessToken = async function () { return await jwt.sign( { _id: this._id, userName: this.userName, fullName: this.fullName, email: this.email, }, process.env.ACCESS_TOKEN_SECRET, { expiresIn: process.env.ACCESS_TOKEN_EXPIRY, } ); };

userSchema.methods.generateRefreshToken = async function () { return await jwt.sign( { _id: this._id, }, process.env.REFRESH_TOKEN_SECRET, { expiresIn: process.env.REFRESH_TOKEN_EXPIRY, } ); };

export const User = model("User", userSchema);

`

user model