MongoDB stores "Promise" object instead of hashed password - javascript

I am using bcrypt to hash passwords and MongoDB as my database.
Here is the code:
export default function buildMakeUser({pwdHasher})
{
return function makeUser({
username,
email,
password,
password_hash = pwdHasher(password), // the important part
favoriteColor
} = {})
{
// ...
return Object.freeze({
getUsername: () => username,
getEmail: () => email,
getHashedPassword: () => password_hash,
getFavoriteColor: () => favoriteColor
});
}
And here is pwdHasher's definition:
import bcrypt from "bcrypt";
import buildMakeUser from "./entity/user.js";
async function pwdHasher(password){
let hashed;
hashed = await bcrypt.hash(password, 10);
return hashed;
}
However, when I store the user in the database, here's the result:
ops: [
{
username: 'kibe',
email: 'blabla#gmail.com',
password_hash: [Promise],
_id: 5ecc8b752e0aa53e87d5b62a
}
],
It seems like makeUser's object does not wait for pwdHasher(password). I have tried wrapping pwdHasher in a Promise and it also did not work.
Does anyone know why?
Thank you.

There are two solutions I see based on your code snippet:
First way:(Recommended)
Use await when calling the async function. The code will be:
export default function buildMakeUser({pwdHasher})
{
return function makeUser({
...
password_hash = await pwdHasher(password), // the important part
...
})
}
Alternative way:
Use hashSync method of bcrypt instead of using async-await.

You have defined pwdHasher as:
async function pwdHasher(password) { ... }
But you call it as:
password_hash = pwdHasher(password),
By definition, an async function RETURNS A PROMISE. If you'd like to get the value from the promise, you must either await the result or use pwdHasher(password).then(...)

Related

mongoose instance methods are showing different results

i'm currently making a server with javascript, koa, and mongoose.
i made two different mongoose instance methods, which receives password from JSON body
and hashes it with bcrypt, saves it on a 'hashedPassword' on user.
Initially, i wrote the second setPassword function (which uses promise, 'then' methods.),
but when the user document saved, the 'hashedPassword' property didn't showed up.
so i tried with first one, it worked well and user document has saved with hashedPassword.
although these two instance methods looks similar, i wonder why these methods leads to different result.. please help
import mongoose from "mongoose";
import bcrypt from "bcrypt";
const UserSchema = mongoose.Schema({
username: String,
hashedPassword: String,
});
UserSchema.methods.setPassword = async function (password) {
const hash = await bcrypt.hash(password, 10);
this.hashedPassword = hash;
};
// UserSchema.methods.setPassword = async function (password) {
// bcrypt.hash(password, 10).then((hash) => {
// this.hashedPassword = hash;
// });
// };
// this code didn't save the hashed
const User = mongoose.model("user", UserSchema);
export default User;
user controller would be like this
const register = async (ctx) => {
const { username, password } = ctx.request.body;
try {
const user = new User({
username,
});
await user.setPassword(password);
await user.save();
catch(e) { ctx.throw(500,e) }
}
This is not a mongoose-specific issue, you need to revise your understanding of how promises work.
Adding await before bcrypt.hash in the commented-out code should make it work, but there is no reason for you here to prefer .then over await.
In the second example, setPassword only fires the hashing process and doesn't ask the caller to wait until the hashing is done, and then adds the hashed password to the document, while the first one does.
You can try this approach using schema built-in methods:
UserSchema.pre('save', async function (next) {
if (this.isModified('hashedPassword')) {
this.hashedPassword= await bcrypt.hash(this.hashedPassword, 10)
}
next()
})
Use this pre-save function inside the schema like that :
UserSchema.pre('save', async function (next) {
// Only if password was moddified
if (!this.isModified('hashedPassword')) return next();
// then Hash password
this.hashedPassword = await bcrypt.hash(this.hashedPassword, 10);
next();
});

How to get a value from a promise and store it in a variable for further use

How to get a value from a promise and store it in a variable for further use.
I am expecting my method to return a passoword as string rather than a resolved promise object. I need passoword string so that I can pass it to httpAuth function given below. httpAuth() is from TestCafe automation framework
Test.js code:
let mypass=Utils.returnPwd(); //I am using returnPwd() from Utils.js file that's returning a password.
fixture`Automated Test1`
.meta('fixturepack', 'regression')
.page("http://intranetURL")
.beforeEach(async t => {
DriverManager.setDriver(t);
await DriverManager.maximize();
})
.httpAuth({
username: 'loginuser1',
password: mypass
})
Utils.js code:
async base64Decoding(encodedPassword) {
var decodedData = Buffer.from(encodedPassword, 'base64').toString('ascii');
var decodedPassword = decodedData.toString()
console.log(decodedPassword);
return decodedPassword;
}
async returnPwd(){
let mypass2= this.base64Decoding('A2dIOEhBfXYvfSNba');
return mypass2.then(function(){ })
}
Current error:
credentials.password is expected to be a string, but it was object.
.httpAuth({
username: 'loginuser1',
password: mypass
})
I did not see any async code in Untils.js, you can make it synchronous
//Test.js
let mypass = Utils.returnPwd();
//Utils.js
base64Decoding(encodedPassword) {
var decodedData = Buffer.from(encodedPassword, 'base64').toString('ascii');
var decodedPassword = decodedData.toString()
console.log(decodedPassword);
return decodedPassword;
}
returnPwd(){
let mypass2= this.base64Decoding('A2dIOEhBfXYvfSNba');
return mypass2;
}

Unsure of how to unit test password validation function that uses bCrypt.compare(), getting undefined

I am trying to write a jest unit test for my validateUser function, but when I call the function in my test I am always getting the value undefined returned.
additional context
I am using nestJS as my api framework, I have mocked my user findOne query function to return expectedUserObjHashed.
I am using bcrypt to handle hashing and comparing passwords, I am using hashSync and compare from the library in this test
(not sure if this was the proper way to validate) but I added log statements to verify that my test is making it into the if(result) block and not throwing an exception.
I am guessing this is some async problem but I've been trying to a few hours a day for a couple days and im not sure where its occuring.
// This is the set up for my test
const saltRounds = 10;
let hashedPassword: string;
let expectedUserObjHashed: any;
beforeAll(() => {
hashedPassword = hashSync('test123!', saltRounds);
expectedUserObjHashed = {
id: 1,
email: 'test#test.com',
first_name: 'foo',
last_name: 'barr',
password: hashedPassword,
};
});
it('should validate password', async () => {
expect(
await service.validateUser(
// expectedUserObjUnhashed is a duplicate of expectedUserObjHashed minus having the password property hashed
expectedUserObjUnhashed.email,
expectedUserObjUnhashed.password,
),
// validatedUserObj is the same as the other UserObj objects but the password property is removed
).toStrictEqual(validatedUserObj);
});
async validateUser(email: string, password: string): Promise<any> {
// findUserByEmail() is mocked in the test to return expectedUserObjHashed (noted above before code blocks)
const user = await this.userService.findUserByEmail(email);
if (user !== undefined) {
compare(password, user.password, function (err, result) {
if (result) {
const { password, ...userInfo } = user;
console.log(userInfo);
return userInfo;
} else {
throw new UnauthorizedException();
}
});
} else {
throw new BadRequestException();
}
}
Update: I wrote a controller to test the validateUser() function in postman and it seems that it is not getting any return value (which is probably why the test got undefined) but right in the bcrypt.compare() callback (right before the return userInfo line) I logged userInfo and it is defined, so now i'm unsure why the callback is not returning userInfo as instructed.
Update 2:
I played around with my implementation of bcrypt.compare() and I got it to work like this:
...
const isMatch = await compare(password, user.password);
if (isMatch) {
const { password, ...userInfo } = user;
return userInfo;
} else {
throw new UnauthorizedException();
}
...
I would still like to know why my original implementation did not work for my knowledge.
compare(password, user.password, function (err, result) {
if (result) {
const { password, ...userInfo } = user;
console.log(userInfo);
return userInfo;
} else {
throw new UnauthorizedException();
}
Function inside compare is a callback that works after compare is done. So compare is firing and it returns nothing and if it's done that function is running so console.log it's working but return don't have any effect. When you added await you actually waited for compare to be complete.

How to mock a method inside Express router Jest test?

I'm trying to test a router in Node.js app with Jest + Supertest, but my router is making a call to service, which is calling the endpoint:
router.post('/login', async (req, res, next) => {
try {
const { username, password } = req.body;
// I WANT TO MOCK userService.getUserInfo FUNCTION, BECAUSE IT IS MAKING A POST CALL
const identity = await userService.getUserInfo(username, password);
if (!identity.authenticated) {
return res.json({});
}
const requiredTenantId = process.env.TENANT_ID;
const tenant = identity.tenants.find(it => it.id === requiredTenantId);
if (requiredTenantId && !tenant) {
return res.json({});
}
const userResponse = {
...identity,
token: jwt.sign(identity, envVars.getVar(envVars.variables.AUTH_TOKEN_SECRET), {
expiresIn: '2h',
}),
};
return res.json(userResponse);
} catch (err) {
return next(err);
}
});
This is my test that works well:
test('Authorized - respond with user object', async () => {
const response = await request(app)
.post('/api/user/login')
.send(users.authorized);
expect(response.body).toHaveProperty('authenticated', true);
});
this is how getUserInfo function looks like:
const getUserInfo = async (username, password) => {
const identity = await axios.post('/user', {username, password});
return identity;
}
but it executes the method getUserInfo inside a router and this method is making a REST call - I want to mock this method in order to avoid REST calls to other services.
How it could be done?
I've found a mockImplementation function in Jest docs https://jestjs.io/docs/en/mock-function-api.html#mockfnmockimplementationfn
but how I can mock func inside a supertest testing?
You can use jest's auto mocking at the top of your test
like so:
jest.mock('./path/to/userService');
// and include it as well in your test
const userService = require('./path/to/userService');
it will generate a mock of the entire module and every function will be replaced with jest.fn() with no implementation
and then depending on the userService if it's just an object it's getUserInfo method will be a jest.fn() and you can set it's return value like this:
// resolved value as it should return a promise
userService.getUserInfo.mockResolvedValue(mockIdentity);
and the mockIdentity will have to look something like this:
const mockIdentity = {
authenticated: true,
tenants: [
{
id: "x12",
mockInfo: "mock-info-value"
}
],
mother: "Superwoman",
father: "Superman"
})
}

Is it possible to use destructuring while function call?

I'd like to use destructuring right inside a constructor call like this:
signup: async (userInfo) => {
const user = new User({ email, password, privacyPolicyConsent, username } = userInfo);
}
But params are undefined.
I want to prevent from injection of undesirable params by redeclaration, therefore I don't want to pass whole object like this:
signup: async (userInfo) => {
const user = new User(userInfo);
}
For now my working solution is as follow:
signup: async (userInfo) => {
const { email, password, privacyPolicyConsent, username } = userInfo;
const user = new User({ email, password, privacyPolicyConsent, username });
}
But I got a feeling I could write this part in a better way. Did I miss something? Any advice regarding best practices appreciated.
You can Destruct it directly, like the following
signup: async ({ email, password, privacyPolicyConsent, username }) => {
const user = new User(email,password,privacyPolicyConsent,userName);
}
signUp(userInfo);
UPDATE
This will prevent to handle destruct error if it is sent undefined
so you need to check for userInfo before you send it
if(userInfo) signUp(userInfo);
UPDATE 2
if you dont want to check for userInfo if it is undefined
you can assign a default value in method level like following
signup: async ({ email, password, privacyPolicyConsent, username }={}) => {
const user = new User(email,password,privacyPolicyConsent,userName);
}
Ooh so would I.
This is what I tend to do (an immediately invoked function):
signup: async userInfo => {
const user = new User((({ email, password, privacyPolicyConsent, username }) =>
({ email, password, privacyPolicyConsent, username })
))(userInfo));
}
It reduces it to one statement, but doesn't remove the problem of duplicated (and ugly) code.
Maybe a function to do it might be best practices:
const dstr = (obj, ...keys) => keys.reduce((o, k) => { o[k] = obj[k]; return o; }, {});
// ...
const user = dstr(userInfo, email, password, privacyPolicyConsent, username);
Or you could use that reduce method to create it inline :)
Intrigued what alternatives there are.
(EDIT) The above is assuming that you can't destructure the object beforehand.

Categories

Resources