I'm trying to hash my password field before storing it in the database. For that reason I've created a virtual field called password and an actual field called hashedPassword. The trouble is that when I try to encrypt the password in beforeCreate hook, it the user.password is undefined. I have tried every thing. I've also defined custom getters and setters for the virtual field. I don't know what I'm doing wrong here. Any help or guidance would be appreciated. Thanks.
import bcrypt from 'bcrypt';
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define(
'User',
{
passwordhash: {
type: DataTypes.STRING,
allowNull: false,
validate: {
notEmpty: true
}
},
password: {
allowNull: false,
type: DataTypes.VIRTUAL,
set(password) {
const valid = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[#$^+=!*()#%&]).{8,30}$/.test(
password
);
if (!valid) {
throw new Error(`Password not valid ${password}`);
}
this.setDataValue('password', password);
},
get() {
return this.getDataValue('password');
}
}
}
},
{
hooks: {
beforeCreate: function hashPassword(user) {
console.log('user is:', user);
return bcrypt
.hash('Abcdefgh1#', 12)
.then(hashed => {
user.passwordhash = hashed;
})
.catch(error => error);
}
}
}
);
User.associate = function(models) {
// associations can be defined here
};
return User;
};
use hooks :
hooks: {
beforeCreate: async (user, options) => {
let salt = await bcrypt.genSalt(10)
let hash = await bcrypt.hash(user.password, salt)
user.password = hash;
}
}
and for updating password fields use this:
User.beforeBulkUpdate(async instance => {
if (instance.attributes.password) {
let salt = await bcrypt.genSalt(10)
let hash = await bcrypt.hash(instance.attributes.password, salt)
instance.attributes.password = hash;
}
})
this works fine for me
Related
I’m refactoring a Google Books app from a Restful API to GraphQL, and I am stuck on a mutation not behaving the way I expect.
When a user fills out the form found on Signup.js the Mutation ADD_USER should create a user within Mongoose, this user should have a JWT token assigned to them, and user should be logged in upon successful execution of the Mutation.
Actions observed:
• Mutation is being fired off from the front end. When I open developer tools in the browser I can see the Username, Email and Password being passed as variables.
• I have tried console logging the token, and keep getting an undefined return
• When I try to run the mutation in the GraphQL sandbox I get a null value returned.
• When I console log the args in resolvers.js no value appears on the console, which tells me the request is not reaching the resolver.
SignupForm.js (React FE Page)
import React, { useState } from "react";
import { Form, Button, Alert } from "react-bootstrap";
import { useMutation } from "#apollo/client";
import { ADD_USER } from "../utils/mutations";
import Auth from "../utils/auth";
const SignupForm = () => {
// set initial form state
const [userFormData, setUserFormData] = useState({
username: "",
email: "",
password: "",
});
// set state for form validation
const [validated] = useState(false);
// set state for alert
const [showAlert, setShowAlert] = useState(false);
const [addUser] = useMutation(ADD_USER);
const handleInputChange = (event) => {
const { name, value } = event.target;
setUserFormData({ ...userFormData, [name]: value });
};
const handleFormSubmit = async (event) => {
event.preventDefault();
// check if form has everything (as per react-bootstrap docs)
const form = event.currentTarget;
if (form.checkValidity() === false) {
event.preventDefault();
event.stopPropagation();
}
try {
///Add user is not returning data. payload is being passed as an object
const response = await addUser({
variables: { ...userFormData },
});
if (!response.ok) {
throw new Error("OH NO!SOMETHING WENT WRONG!");
}
const { token, user } = await response.json();
console.log(user);
Auth.login(token);
} catch (err) {
console.error(err);
setShowAlert(true);
}
setUserFormData({
username: "",
email: "",
password: "",
});
};
Mutation.js
export const ADD_USER = gql`
mutation addUser($username: String!, $email: String!, $password: String!) {
addUser(username: $username, email: $email, password: $password) {
token
user {
username
email
}
}
}
`;
typeDefs.js
const { gql } = require("apollo-server-express");
const typeDefs = gql`
input SavedBooks {
authors: [String]
description: String
bookId: String
image: String
link: String
title: String
}
type Books {
authors: [String]
description: String
bookId: ID
image: String
link: String
title: String
}
type User {
_id: ID
username: String
email: String
password: String
savedBooks: [Books]
}
type Auth {
token: ID!
user: User
}
type Query {
me: User
}
type Mutation {
##creates a user profile through the Auth type, that way we can pass a token upon creation
addUser(username: String!, email: String!, password: String!): Auth
login(email: String!, password: String!): Auth
saveBook(bookData: SavedBooks): User
deleteBook(bookId: ID!): User
}
`;
module.exports = typeDefs;
resolvers.js
const { User, Book } = require("../models");
const { AuthenticationError } = require("apollo-server-express");
const { signToken } = require("../utils/auth");
const resolvers = {
Query: {
me: async (parent, args, context) => {
if (context.user) {
return User.findOne({ _id: context.user._id }).populate("books");
}
throw new AuthenticationError("You need to log in");
},
},
};
Mutation: {
//try refactoring as a .then
addUser: async (parent, args) => {
//create user profile
await console.log("resolver test");
console.log(args);
const user = await User.create({ username, email, password });
//assign token to user
const token = signToken(user);
return { token, user };
};
login: async (parent, { email, password }) => {
const user = User.findOne({ email });
if (!user) {
throw new AuthenticationError("Invalid Login Credentials");
}
const correctPw = await profile.isCorrectPassword(password);
if (!correctPw) {
throw new AuthenticationError("Invalid Login Credentials");
}
const token = signToken(user);
return { token, user };
};
saveBook: async (parent, { bookData }, context) => {
if (context.user) {
return User.findOneAndUpdate(
{ _id: context.user._id },
{ $addToSet: { savedBooks: bookData } },
{ new: true }
);
}
throw new AuthenticationError("You need to log in");
};
deleteBook: async (parent, { bookId }, context) => {
if (context.user) {
return User.findOneAndUpdate(
{ _id: contex.user._id },
//remove selected books from the savedBooks Array
{ $pull: { savedBooks: context.bookId } },
{ new: true }
);
}
throw new AuthenticationError("You need to log in");
};
}
module.exports = resolvers;
auth.js
const jwt = require("jsonwebtoken");
// set token secret and expiration date
const secret = "mysecretsshhhhh";
const expiration = "2h";
module.exports = {
// function for our authenticated routes
authMiddleware: function ({ req }) {
// allows token to be sent via req.query or headers
let token = req.query.token || req.headers.authorization || req.body.token;
// ["Bearer", "<tokenvalue>"]
if (req.headers.authorization) {
token = token.split(" ").pop().trim();
}
if (!token) {
return req;
}
// verify token and get user data out of it
try {
const { data } = jwt.verify(token, secret, { maxAge: expiration });
req.user = data;
} catch {
console.log("Invalid token");
return res.status(400).json({ message: "invalid token!" });
}
// send to next endpoint
return req;
},
signToken: function ({ username, email, _id }) {
const payload = { username, email, _id };
return jwt.sign({ data: payload }, secret, { expiresIn: expiration });
},
};
Basically, I have combed from front to back end looking for where I introduced this bug, and am stuck. Any suggestions or feedback is greatly appreciated.
I was able to figure out the issue. First, a syntax error on resolver.js was preventing my mutations from being read.
Next, I made the following adjustment to handleFormSubmit on SignupForm.js
try {
///Add user is not returning data. payload is being passed as an object
const {data} = await addUser({
variables: { ...userFormData },
});
console.log(data)
console.log(userFormData)
**Auth.login(data.addUser.token);**
} catch (err) {
console.error(err);
setShowAlert(true);
}
That way my FE was properly accounting for what my Auth Middleware was passing back after successful user creation. Thanks for your help xadm, being able to talk this out got me thinking about where else to attack the bug.
I use Sequelize to work with the database.
In my project, I encountered code duplication, and decided to study the repository design pattern, which will separate the work with the database and data output from the business logic. Having studied the information on the Internet, I decided to consolidate the material and check with more experienced programmers. I use the repository pattern correctly?
controller/user.controller.js
const userService = require('../services/user.service');
exports.userCreate = async (req, res, next) => {
try {
const user = await userService.userCreate(req.body);
return res.status(201).json(user);
} catch (e) {
return next(e);
}
}
services/user.service.js
const ApiError = require('../utils/error');
const userRepo = require('../repositories/user.repository');
exports.userCreate = async (data) => {
if (!data) throw ApiError.badRequest('Bad');
const { login } = data;
if (!login) throw ApiError.badRequest('Bad');
const password = 'hash';
const user = await userRepo.userCreate({login, password});
return user;
}
repositories/user.repository.js
const User = require('../models/User');
exports.userCreate = async (data) => {
const { login, passowrd } = data;
const user = await User.create({login, password});
return {
login: user.login,
}
}
models/User.js
const { sequelize, Sequelize } = require('../config/db');
const User = sequelize.define('users', {
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
allowNull: false,
primaryKey: true,
},
login: {
type: Sequelize.STRING,
allowNull: false,
},
password: {
type: Sequelize.STRING,
allowNull: false,
}
});
module.exports = User;
Your User model is fine, but the structure of the service seems a little weird. if all three of the functions userCreates are in separate files I'd recommend you combine them and rename services to Controllers, as services are usually used on the client side of things.
services/user.service.js
const User = require('../models/User')
// const userRepo = require('../repositories/user.repository') Not sure what this does
const ApiError = require('../utils/error')
module.exports = {
async createUser (req, res) {
try {
if (!req.body) throw ApiError.badRequest('Bad')
const { login } = req.body
if (!login) throw ApiError.badRequest('Bad')
const password = 'hash'
const user = await User.create({ login, password })
res.stauts(201).json(user.login)
} catch (err) {
console.log(err)
res.staus(500).send({
error: err
})
// return next(err)
}
}
}
Have been trying to create a password hash in my nodejs code
But it not working and not showing any error message for me to debug.
And my user is not creating also.
I dont know if there is a better way to do it.
This is my file which is responsible for the code...Model/User.js
const Promise = require('bluebird')
const bcrypt = Promise.promisifyAll(require('bcrypt-nodejs'))
function hashPassword (user) {
const SALT_FACTOR = 8
if (!user.changed('password')) {
return;
}
return bcrypt
.genSaltSyncAsync(SALT_FACTOR)
.then(salt => bcrypt.hashAsync(user.password, salt, null))
.then(hash => {
user.setDataValue('password', hash)
})}
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
email: {
type: DataTypes.STRING,
unique: true
},
password: DataTypes.STRING
}, {
hooks: {
beforeCreate: hashPassword,
beforeUpdate: hashPassword,
beforeSave: hashPassword
}
})
User.prototype.comparePassword = function (password) {
return bcrypt.compareAsync(password, this.password)
}
return User }
Does the following snippet help in any way?
const bcrypt = require('bcryptjs');
const userAbc = {
email: 'user#user.com',
password: '1234'
}
async function hashPassword(user) {
try {
const hashedPassword = await bcrypt.hash(user.password, 12);
user.password = hashedPassword;
console.log(user);
} catch (err) {
console.log(err);
}
}
hashPassword(userAbc);
I did change the bcrypt-nodejs to bcryptjs then replace genSaltSyncAsync to genSaltSync and everyother Async to Sync and it worked.
I wrote a class which extends a Model, and I need create a method to compareSync password:
const { Model, DataTypes } = require('sequelize');
class User extends Model {
static init(sequelize) {
super.init({
username: DataTypes.STRING,
password: DataTypes.STRING,
role: DataTypes.STRING,
status: DataTypes.INTEGER
},
{
sequelize,
hooks: {
beforeCreate: (user) => {
const salt = bcrypt.genSaltSync();
user.password = bcrypt.hashSync(user.password, salt);
}
}
}
)
}
static associate(model) {
this.belongsToMany(models.Movie, { through: models.Ratings });
}
}
module.exports = User;
The hook is working, I thought to add after beforeCreate a:
instanceMethods: {
validPassword: function (password) {
return bcrypt.compareSync(password, this.password);
}
}
Using this class how I can define an user method?
I got this:
const { Model, DataTypes } = require('sequelize');
const bcrypt = require("bcrypt")
class User extends Model {
static init(sequelize) {
super.init({
username: DataTypes.STRING,
password: DataTypes.STRING,
role: DataTypes.STRING,
status: DataTypes.INTEGER
},
{
sequelize,
hooks: {
beforeCreate: (user) => {
const salt = bcrypt.genSaltSync();
user.password = bcrypt.hashSync(user.password, salt);
}
}
}
)
}
static associate(model) {
this.belongsToMany(models.Movie, { through: models.Ratings });
}
validPassword(password) {
return bcrypt.compareSync(password, this.password);
}
}
module.exports = User;
If I wanna use the method inside controller for example:
const login = async (req, res) => {
const { username, password } = req.params;
const user = await User.findOne({
where: { username }
});
if (!user) {
return res.status(400).send("User not find!")
}
if(!user.validPassword(password)){..}
res.send(user)
}
I want to create a method that validates the user's password by using bcrypt.compare()
here is the code below.
UserSchema.methods.validatePassword = async (data) => {
console.log(this.email); // returns undefined
console.log(this.first_name); // returns undefined
return await bcrypt.compare(data, this.password);
};
here is the UserSchema I created
const UserSchema = mongoose.Schema(
{
email: {
type: String,
required: true,
unique: true,
},
password: {
type: String,
required: true,
},
},
{ timestamps: { createdAt: 'created_at', updatedAt: 'updated_at' } }
);
when getting this.password in my schema .pre('save', ..) it works but shows undefined when I use schema methods. :(
here is the implementation of the method
const verifySignIn = async (req, res, next) => {
const { email, password } = req.body;
try {
const user = await User.findOne({ email });
if (!user) {
return res.status(404).json({
status: 'failed',
message: 'User Not found.',
});
}
const isValid = await user.validatePassword(password);
if (!isValid) {
return res.status(401).send({
message: 'Invalid Password!',
data: {
user: null,
},
});
}
next();
} catch (err) {
Server.serverError(res, err);
}
};
In the guide it says:
Do not declare methods using ES6 arrow functions (=>). Arrow functions explicitly prevent binding this, so your method will not have access to the document ...
So in this case, you just need to change UserSchema.methods.validatePassword = async (data) => {... to UserSchema.methods.validatePassword = async function(data) {...