am trying to do login with graphql, i want to first of all check if the email exist in the database, then if the email exist, then i will compare the password with the bcrypt hash in the password, if it returns true, then i will update the token in the users table in the database, but when i tested it with this
mutation {
loginUser(user: {email: "kmd#vh.id", password: "1345968"}) {
password
}
}
it returned this instead of te token in the database
mutation {
loginUser(user: {email: "kmd#vh.id", password: "1345968"}) {
token
}
}
here is my code
import models from '../../../models/index.js';
import User from '../../types/user.js';
import UserInput from '../../inputs/user.js';
const jwt = require('jsonwebtoken')
const bcrypt = require('bcryptjs')
require('dotenv').config()
const fs = require('fs');
const path = require('path');
var privateKey = fs.readFileSync(path.join(__dirname, '../../key/private.key'), 'utf8');
export default {
type: User,
args: {
user: {
type: UserInput
}
},
resolve (_,args) {
return models.user.findOne({
where: {email: args.user.email}
}).then((fUser)=> {
bcrypt.compare(args.user.password, fUser.password, function (err, res) {
if (res) {
return models.user.findById(fUser.id).then((user) => {
return user.update({ token: jwt.sign({id:fUser.id, role:fUser.role, email:fUser.email}, privateKey, { expiresIn: '1h' }) });
});
} else {
console.log(err+" error")
}
})
});
}
};
pls ow can i make it return the token
Try using findByIdAndUpdate with {new: true} for updated document. So your code will be something like this
return models.user.findOne({
where: {email: args.user.email}
}).then((fUser)=> {
bcrypt.compare(args.user.password, fUser.password, function (err, res) {
if (res) {
return models.user.findByIdAndUpdate(fUser.id, {
$set: {
token: jwt.sign(
{ id:fUser.id, role:fUser.role, email:fUser.email },
privateKey,
{ expiresIn: '1h' }
)}}, { new: true })
} else {
console.log(err+" error")
}
})
});
Related
I'm trying to make e-mail verification using nodemailer and jwt key. The problem is that when I'm trying to verify jwt key, id of user is always undefined.
What am I doing wrong?
app.post("/api/createAccount", async (req, res) => {
const { login, password } = req.body;
const newUser = await user.create({
login: login,
password: password,
});
jwt.sign(
{ userId: newUser.id },
"SECRETKEY",
{
expiresIn: "7d",
},
(err, token) => {
const url = `http://localhost:5000/api/confirmMail/${token}`;
const options = {
from: "xxx",
to: login,
subject: "verifyacc",
html: `${url} `,
};
transporter.sendMail(options, function (err, info) {
if (err) {
console.log(err);
} else {
console.log(info);
}
});
}
);
});
app.get("/api/confirmMail/:token", async (req, res) => {
try {
const {
userId: { id },
} = jwt.verify(req.params.token, "SECRETKEY");
await user.update({ confirmed: 1 }, { where: { id: id } });
} catch (err) {
console.log(err);
}
return res.redirect("http://localhost:3000/login");
});
The error :
err: Error: WHERE parameter "id" has invalid "undefined" value
The payload of the token you are creating is
{
userId: "foobar"
}
But in the deconstruction during verification
const { userId: { id } } = jwt.verify(...);
you expect it to be
{
userId: {
id: "foobar"
}
}
because the deconstruction you used, roughly translates to
const tmp = jwt.verify(...);
const id = tmp.userId.id;
Thus id is of course undefined, as tmp.userId is (probably) a string or a number, which doesn't have an id property.
Use
const { userId: id} = jwt.verify(...);
await user.update({ confirmed: 1 }, { where: { id } });
which roughly translates to
const tmp = jwt.verify(...);
const id = tmp.userId;
or alternatively
const { userId} = jwt.verify(...);
await user.update({ confirmed: 1 }, { where: { id: userId } });
I am having issues posting to send a verification email. I am using ReactJS front end NodeJS backend and MongoDB database. I am running npm run dev to make sure my backend server is running when testing in Postman and gives me an error Cannot Post /api/verify-email I am not getting any other error code so having a hard time trouble shooting. I am using Send Grid API to send the verification email.
verifyEmailRoute.js
import { ObjectId } from 'mongodb';
import jwt from 'jsonwebtoken';
import { getDbConnection } from '../src/db';
export const verifyEmailRoute = {
path: 'http://localhost:8080/api/verify-email',
method: 'put',
handler: async (req, res) => {
const { verificationString } = req.body;
const db = await getDbConnection('react-auth-db');
const result = await db.collection('users').findOne({ verificationString });
if (!result) return res.status(401).json({ message: 'The email verification code is incorrect' });
const { _id: id, email, info } = result;
await db.collection('users').updateOne({ _id: ObjectId(id) }, { $set: { isVerified: true } });
jwt.sign({ id, email, isVerified: true, info }, process.env.JWT_SECRET, { expiresIn: '2d' }, (err, token) => {
if (err) {
return res.status(500).json(err);
}
res.status(200).json({ token });
});
},
};
emailVerificationLandingPage.js
import { useState, useEffect } from 'react';
import { useParams } from 'react-router-dom';
import { useToken } from '../src/auth/useToken';
import { EmailVerificationSuccess } from './EmailVerificationSuccess';
import { EmailVerificationFail } from './EmailVerificationFail';
export const EmailVerificationLandingPage = () => {
const [isLoading, setIsLoading] = useState(true);
const [isSuccess, setIsSuccess] = useState(false);
const [user, setUser] = useState(null);
const { verificationString } = useParams();
const [token, setToken] = useToken();
useEffect(() => {
const loadVerification = async () => {
try {
const response = await fetch.put('http://localhost:8080/api/verify-email', { verificationString });
const { token } = response.data;
setToken(token);
console.log(token);
setIsSuccess(true);
setIsLoading(false);
} catch (error) {
setIsSuccess(false);
setIsLoading(false);
}
}
loadVerification();
}, [setToken, verificationString]);
console.log(token);
if (isLoading) return <p>Loading...</p>;
if (!isSuccess) return <EmailVerificationFail />
return <EmailVerificationSuccess />
}
sendEmail.js
import sendgrid from '#sendgrid/mail';
sendgrid.setApiKey(process.env.SENDGRID_API_KEY);
export const sendEmail = ({ to, from, subject, text, html }) => {
const msg = { to, from, subject, text, html };
return sendgrid.send(msg);
}
signUpRoute.js
import bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';
import { v4 as uuid } from 'uuid';
import { getDbConnection } from '../src/db';
import { sendEmail } from '../src/util/sendEmail';
export const signUpRoute = {
path: '/api/signup',
method: 'post',
handler: async (req, res) => {
const { email, password } = req.body;
// Make sure there's no existing user with that email - no verification yet
const db = getDbConnection('react-auth-db');
const user = await db.collection('users').findOne({ email });
if (user) {
console.log("found user", user)
return res.sendStatus(409).json(user); // 409 is the "conflict" error code
}
// Generate the salt first
const salt = uuid();
const pepper = process.env.PEPPER_STRING;
// Encrypt the password
const passwordHash = await bcrypt.hash(salt + password + pepper, 10);
const verificationString = uuid();
// Store email and password hash in database (i.e. create user) - you'll also want to get the id
const startingInfo = {
firstName: '',
lastName: '',
dob: '',
}
const result = await db.collection('users').insertOne({
email,
isVerified: false,
verificationString,
passwordHash,
salt,
info: startingInfo,
});
const { insertedId } = result;
try {
await sendEmail({
to: email,
from: 'mdolly01#gmail.com',
subject: 'Welcome to Tool Right Please verify your email',
text: `
Thanks for signing up! To verify your email, you just need to click the link below:
http://localhost:3000/verify-email/${verificationString}
`
});
} catch (e) {
console.log(e);
throw new Error(e);
}
jwt.sign({
id: insertedId,
isVerified: false,
email, // Do NOT send the salt back to the user
info: startingInfo,
},
process.env.JWT_SECRET,
{
expiresIn: '2d',
},
(err, token) => {
if (err) {
return res.status(500).send(err);
}
return res.status(200).json({token});
});
},
};
I have this User schema:
email: {
type: String,
required: true
},
name: {
type: String,
required: true
},
password: {
type: String,
required: true
}
When you do a POST (/api/user-add), I want all the fields to be required. But when I do a login (/api/login) then I only need the email and password fields. My problem is, in my login code I eventually get to this function:
staffSchema.methods.generateToken = function(callback) {
var token = jwt.sign(this._id.toHexString(), config.SECRET);
this.token = token;
this.save(function(err, staff) {
if (err) return callback(err);
callback(null, staff);
});
}
And here it thows an error because the name field is required. How do I bypass this. I am looking for something like this I assume:
this.save(function(err, staff) {
if (err) return callback(err);
callback(null, staff);
}).ignoreRequired('name');
When You Login using JWT token this is a basic example to generate token and authenticate user without store token
Note :
Example to authenticate the user without store token in DB
*Login Method
const jwt = require('./jwt');
userCtr.authenticate = (req, res) => {
const {
email, password,
} = req.body;
const query = {
email: email,
};
User.findOne(query)
.then((user) => {
if (!user) {
//return error user not found.
} else {
if (passwordHash.verify(password, user.password)) { // verify password
const token = jwt.getAuthToken({ id: user._id });
const userData = _.omit(user.toObject(), ['password']); // return user data
return res.status(200).json({ token, userData });
}
//return error password not match
}
})
.catch((err) => {
});
};
*jwt.js
const jwt = require('jwt-simple');
const logger = require('./logger');
const jwtUtil = {};
jwtUtil.getAuthToken = (data) => {
return jwt.encode(data, process.env.JwtSecret);
};
jwtUtil.decodeAuthToken = (token) => {
if (token) {
try {
return jwt.decode(token, process.env.JwtSecret);
} catch (err) {
logger.error(err);
return false;
}
}
return false;
};
module.exports = jwtUtil;
*use middleware to prevent another route to access.
userRouter.post('/update-profile', middleware.checkUser, userCtr.updateProfile);
*middleWare.js
middleware.checkUser = (req, res, next) => {
const { headers } = req;
if (_.isEmpty(headers.authorization)) {
//return error
} else {
const decoded = jwt.decodeAuthToken(headers.authorization.replace('Bearer ', ''));
if (decoded) {
User.findOne({ _id: decoded.id })
.then((user) => {
if (user) {
req.user = user;
next();
} else {
//error
}
})
.catch((err) => {
//errror
});
req.user = decoded;
} else {
//error
}
}
};
I'm having problem with bcrypt-nodejs' compare function.
The compare function is returning the false value even the password is the right one.
I've tried everything I could and I don't know the what is wrong with my code.
My Folder Structure
src
-config
-config.js
-controller
-AuthenticationController.js
-models
-index.js
-User.js
-policies
-AuthenticationControllerPolicy.js
app.js
routes.js
package.json
I think the problem is with the User.js in models folder.
User.js
const Promise = require('bluebird')
const bcrypt = Promise.promisifyAll(require('bcrypt-nodejs'))
function hashPassword (user, options) {
const SALT_FACTOR = 8
if (!user.changed('password')) {
return
}
return bcrypt
.genSaltAsync(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)
}
User.associate = function (models) {
}
return User
}
AuthenticationController.js
const {User} = require('../models')
const jwt = require('jsonwebtoken')
const config = require('../config/config')
function jwtSignUser (user) {
const ONE_WEEK = 60 * 60 * 24 * 7
return jwt.sign(user, config.authentication.jwtSecret, {
expiresIn: ONE_WEEK
})
}
module.exports = {
async register (req, res) {
try {
const user = await User.create(req.body)
const userJson = user.toJSON()
res.send({
user: userJson
})
} catch (err) {
res.status(400).send({
error: 'This email account is already in use.'
})
}
},
async login (req, res) {
try {
const {email, password} = req.body
const user = await User.findOne({
where: {
email: email
}
})
console.log('user BEFORE', user)
if (!user) {
console.log('!user')
return res.status(403).send({
error: 'The login information was incorrect'
})
}
console.log('user AFTER', user)
const isPasswordValid = await user.comparePassword(password)
console.log('isPasswordValid BEFORE : ', isPasswordValid)
if (!isPasswordValid) {
console.log('isPasswordValid AFTER : ', isPasswordValid)
return res.status(403).send({
error: 'The login information was incorrect'
})
}
const userJson = user.toJSON()
res.send({
user: userJson,
token: jwtSignUser(userJson)
})
} catch (err) {
res.status(500).send({
error: 'An error has occured trying to log in'
})
}
}
}
route.js
const AuthenticationController = require('./controller/AuthenticationController')
const AuthenticationControllerPolicy = require('./policies/AuthenticationControllerPolicy')
module.exports = (app) => {
app.post('/register',
AuthenticationControllerPolicy.register,
AuthenticationController.register)
app.post('/login',
AuthenticationController.login)
}
You can also check the repo if you want.
GitHubRepo
The usage of bcrypt-nodejs appears to be correct. I would verify that both the password coming in and the hash in the database are what you expect them to be (particularly inside the comparePassword function) to rule out if it's a data issue or not.
I am experiencing a slight issue while trying to query mongodb using mongoose.
Here is a sample of my code:
...
resolve: (root, params) => {
var user = User.findOne({email: params.data.email}).exec()
return user;
}
...
This returns the following:
{
"data": {
"login": "{ _id: 596f4cc4f51fa12bf0f5a001,\n updatedAt: 2017-07-19T12:12:52.736Z,\n createdAt: 2017-07-19T12:12:52.736Z,\n email: 'myemail#gmail.com',\n password: '$2a$12$bhPG4TPGR6by/UBTeAnzq.lyxhfMAJnBymDbkFDIHWl5.XF2JG62O',\n __v: 0 }"
}
}
I have no idea why this is happening. Any help would be appreciated.
EDIT
Here is the full code :
var bcrypt = require('bcryptjs');
var _ = require('lodash');
var { GraphQLNonNull, GraphQLString } = require('graphql');
var jwt = require('jsonwebtoken');
var { UserInputType } = require('./UserSchema');
var User = require('./UserModel');
var login = {
type: new GraphQLNonNull(GraphQLString),
args: {
data: {
name: 'Data',
type: new GraphQLNonNull(UserInputType)
}
},
resolve: (root, params, { SECRET }) => {
var user = User.findOne({email: params.data.email}).exec();
var authorized = bcrypt.compareSync(params.data.password, user.password);
if (!authorized) throw new Error('Invalid password');
var token = jwt.sign({
user: _.pick(user, ['_id', 'name'])
}, SECRET, {expiresIn: '1y'});
return token;
}
};
This might be in the function which calls resolve and calls .then of this user promise. Can't tell for sure without more code...
resolve: (root, params, { SECRET }) => {
return new Promise(resolve => {
User.findOne({email: params.data.email}).exec().then(user => {
bcrypt.compare(params.data.password, user.password, (err, authorized) => {
if (!authorized) throw new Error('Invalid password');
var token = jwt.sign({
user: _.pick(user, ['_id', 'name'])
}, SECRET, {expiresIn: '1y'});
resolve(token);
});
});
}).then(token => token);
}
Decided to do this instead and voila! Solved my issue. Thanks for the help.