0

I am stuck here figuring out why the password argument from bcrypt.comapare(password, user.password) line keeps getting value from const {password, ...rest} = user; instead of const {email, password} = req.body line.

I know that I can fix this issue by changing variables' name, but I'm curious about the reason why...

For me it doesn't make sense that it is referring to the variable that is not even declared.

Thanks!

util.js

import jwt from "jsonwebtoken";
import bcrypt from "bcryptjs";

// Create a token for logged in user
export const createToken = (user) => {
  if (!user.role) {
    throw new Error("No user role specified");
  }
  return jwt.sign(
    {
      sub: user._id,
      email: user.email,
      role: user.role,
      iss: "my-api",
      aud: "my-api",
    },
    "asldkfjaslasfdasdfasdfsadfasfdsfdsf",
    { algorithm: "HS256", expiresIn: "1h" }
  );
};

// Verify password
export const verifyPassword = (passwordAttempt, hashedPassword) => {
  return bcrypt.compare(passwordAttempt, hashedPassword);
};

userController.js

import User from "../models/User";
import jwtDecode from "jwt-decode";
import bcrypt from "bcryptjs";
import { createToken, verifyPassword } from "../util";

export const userAuthenticate = async (req, res) => {
  const { email, password } = req.body;
  try {
    const user = await User.findOne({
      email,
    }).lean();
    if (!user) {
      return res.status(400).json({ Error: "No user found" });
    }
    const match = await bcrypt.compare(password, user.password);
    if (!match) {
      return res.status(400).send("No user found");
    }
    const token = await createToken(user);
    const { password, ...rest } = user;
    const userInfo = Object.assign({}, { ...rest });

    const decodedToken = jwtDecode(token);
    console.log(decodedToken);

    return res.status(200).json({
      //userInfo,
    });
  } catch (error) {
    console.log(error);
    return res.status(400).send(error);
  }
};
A.Lee
  • 69
  • 3
  • 1
    It's not clear which lines you refer to – Vlad DX Feb 28 '23 at 23:46
  • This should be throwing an error at you about trying to access `password` before initialisation. If that's not the case, then this isn't an accurate representation of your code – Phil Mar 01 '23 at 00:25
  • 2
    FYI you can easily rename destrutured assignments using this syntax... `const { originalName: newName } = ...` – Phil Mar 01 '23 at 00:32
  • @Phil Yeah, I figured that renaming the destructured assignments is an easy way to do what I intended to do, but just wanted to know the cause of the issue. Thanks for your advice tho! – A.Lee Mar 01 '23 at 05:44

1 Answers1

2

JS declarations get hoisted to the top of the scope. So the code

{
    // statements
    const x = 4;
}

is actually more like

{
    const x;
    // statements
    x = 4;
}

if I understand correctly.

Here is a dummy example of your code:

const body = { email: 'test', password: 'hello' };

(() => {
    // extract from body
    const { email, password } = body;
    console.log(password);

    // enter inner scope
    try {
        // get the user
        const user = {
            otherStuff: 'ok',
            password: 'goodbye',
        };

        // try to use the password variable (like bcrypt call)
        console.log(password);

        // now create the new password variable and use it
        const { password } = user;
        console.log(password);
    } catch (error) {
        console.error(error);
    }
})();

This produces the following output:

hello
ReferenceError: Cannot access 'password' before initialization
    at /home/kkovacs/oss_97542/src/asdf.js:17:21
    at Object.<anonymous> (/home/kkovacs/oss_97542/src/asdf.js:25:3)
    at Module._compile (node:internal/modules/cjs/loader:1120:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1174:10)
    at Module.load (node:internal/modules/cjs/loader:998:32)
    at Module._load (node:internal/modules/cjs/loader:839:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)
    at node:internal/main/run_main_module:17:47

The code will throw an error right after the // try to use comment because the const { password } = user; declaration gets hoisted to the top of the try scope, but the initialization remains in place. So then we are trying to print out a const variable that has not been initialized, so it throws an error.

So I am thinking it might be that there is a transpilation step that's doing something non-obvious. I don't think I can fully answer this question, but hopefully this helps a little. In general, you should try to avoid naming variables the same like this, but I think you already know that.

nullromo
  • 2,165
  • 2
  • 18
  • 39
  • 1
    Excellent answer. OP should have been seeing that error though – Phil Mar 01 '23 at 00:30
  • 1
    @nullromo I'm kinda new to Javascript, so I was not aware of the fact that the variable declarations get hoisted to the top of the scope. Thanks for your explanation! – A.Lee Mar 01 '23 at 05:42