I'm working on this little project and i'm having difficulties , Everything works fine in my api except that when i want to authenticate a user using a token , it says "unauthorized". any help would be much appreciated.
this is my passport.js file :
const jwtStrategy = require('passport-jwt').Strategy;
const extractJwt = require('passport-jwt').ExtractJwt;
const User = require('../models/user');
const config = require('../config/database');
module.exports = function(passport){
let opts = {};
opts.jwtFromRequest =
extractJwt.fromAuthHeaderWithScheme('jwt');
opts.secretOrKey = config.secret;
passport.use(new jwtStrategy(opts, function(jwt_payload, done)
{
User.getUserById(jwt_payload.data._id, function(err, user) {
if (err) {
return done(err, false);
}
if (user) {
return done(null, user);
} else {
return done(null, false);
}
});
}));
}
And this is my user.js file (i think that the fault is in the getUserById function :
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
const config = require('../config/database');
//User schema
const userSchema = mongoose.Schema({
name: {
type: String
},
email: {
type: String,
required: true
},
username: {
type: String,
required: true
},
password: {
type: String,
required: true,
}
});
const User = module.exports = mongoose.model('User', userSchema);
module.exports.getUserById = function(id, callback){
User.findById(id, callback);
}
module.exports.getUserByUsername = function(username, callback){
const query = {username: username}
User.findOne(query, callback);
}
module.exports.addUser = function(newUser, callback){
bcrypt.genSalt(10, function(err, salt){
bcrypt.hash(newUser.password,salt,function(err, hash){
if(err) throw err;
newUser.password= hash;
newUser.save(callback);
});
});
}
module.exports.comparePassword = function (candidatePassword,
hash, callback){
bcrypt.compare(candidatePassword, hash, function(err, isMatch){
if(err) throw err;
callback(null, isMatch);
});
}
Related
I have creating one ecommerce application, Inside this i have facing some issue regarding req.gravatar() is not a function.
Whenever I have send data through postman give me error, those error I have defined above.
account.js file code
const router = require('express').Router();
const jwt = require('jsonwebtoken');
const User = require('../models/user');
const config = require('../config');
var gravatar = require('gravatar');
router.post('/signup', (req, res, next) => {
let user = new User();
user.name = req.body.name;
user.email = req.body.email;
user.password = req.body.password;
user.picture = req.gravatar();
user.isSeller = req.body.isSeller;
User.findOne({ email: req.body.email }, (err, existingUser) => {
if(existingUser) {
res.json({
success: false,
message: 'Account with that email is already exist'
});
}
else{
user.save();
var token = jwt.sign({
user: user
}, config.secret, {
expiresIn: '7d'
});
res.json({
success: true,
message: 'Enjoy your token',
token: token
});
}
});
});
module.exports = router;
User.js file code
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const bcrypt = require('bcrypt-nodejs');
const crypto = require('crypto');
const UserSchema = new Schema({
email: { type: String, unique: true, lowercase: true },
name: String,
password: String,
picture: String,
isSeller: { type: Boolean, default: false },
address: {
add1: String,
add2: String,
city: String,
state: String,
country: String,
postalCode: String
},
created: { type: Date, default: Date.now }
});
UserSchema.pre('save', function(next) {
var user = this;
if(!user.isModified('password')) return next();
bcrypt.hash(user.password, null, null, function(err, hash) {
if(err) return next(err);
user.password = hash;
next();
});
});
UserSchema.methods.comparePassword = function(password) {
return bcrypt.compareSync(password, this.password);
}
UserSchema.methods.gravatar = function(size) {
if(!this.size) size = 200;
if(!this.email) {
return 'https://gravatar.com/avatar/?s' + size + '&d=retro';
}
else{
var md5 = crypto.createHash('md5').update(this.email).digest('hex');
return 'https://gravatar.com/avatar/' + md5 + '?s' + size + '&d=retro';
}
}
module.exports = mongoose.model('User', UserSchema);
please help me to solve this question as fast as possible.
because i am Amateur developer in node.js technology.
In the following line, gravatar is not an attribute of req and therefore cannot be invoked as a function
user.picture = req.gravatar();
I suppose that what you want to do, is something like:
user.picture = gravatar.url(user.email);
With this change, user.picture will contain the URL of the gravatar user profile picture for that email.
I keep getting this error while attempting to use my local strategy by passport lib.
TypeError: UserModel.findOne is not a function
I have looked around for hours but can't seem to find a solution that works for my problem. Here is my user.model and auth.services files.
user.model
const mongoose = require('mongoose');
const validator = require('validator');
const uniqueValidator = require('mongoose-unique-validator');
const jwt = require('jsonwebtoken');
const cryptor = require('../services/bcrypt.services');
const authServices = require('../services/auth.services');
const Schema = mongoose.Schema;
const UserSchema = new Schema({
email: {
type: String,
unique: true,
trim: true,
lowercase: true,
required: [true, 'Email is required'],
validate: {
validator: function(email) {
return validator.isEmail(email);
},
message: function (props) { `${props.value} is not an valid email` }
}
},
password: {
type: String,
trim: true,
minlength: [6, 'Password too short'],
lowercase: true,
required: true
},
conversations: [{ type: Schema.Types.ObjectID, ref: 'Conversation' }]
});
UserSchema.methods = {
async _hashPassword(password) {
this.password = await cryptor.hashAsync(password);
},
async authUser(password) {
return await cryptor.compareAsync(password, this.password);
},
createToken() {
return jwt.sign({
_id: this._id
}, authServices.privateKey);
},
toAuthJWT() {
return {
_id: this._id,
email: this.email,
token: this.createToken()
};
},
toJSON() {
return {
_id: this._id,
email: this.email
}
}
};
UserSchema.pre('save', async function (next) {
if (this.isModified('password')) {
this._hashPassword(this.password);
}
next();
});
UserSchema.plugin(uniqueValidator, { message: '{VALUE} already exists' });
module.exports = mongoose.model('User', UserSchema);
auth.services
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const JWTStrategy = require('passport-jwt').Strategy;
const ExtractJWT = require('passport-jwt').ExtractJwt;
const UserModel = require('../user/user.model');
const fs = require('fs');
// LocalStrategy
let localStrategy = new LocalStrategy({ usernameField: 'email', passwordField: 'password' }, function(email, password, done) {
UserModel.findOne({ email: email }, function(err, user) {
if (err) {
return done(err);
} else if(!user) {
// Invalid email
return done(null, false);
} else if(!user.authUser(password)) {
// Invalid password
return done(null, false);
}
return done(null, user);
});
});
passport.use(localStrategy);
// JWTStrategy
// eslint-disable-next-line no-undef
const privateKEY = fs.readFileSync(__dirname + '/private.key', 'utf8');
// eslint-disable-next-line no-undef
const publicKEY = fs.readFileSync(__dirname + '/public.key', 'utf8');
let jwtStrategy = new JWTStrategy({
jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken(),
secretOrKey: publicKEY
}, function(payload, done) {
UserModel.findById(payload._id, function(err, user) {
if (err) {
return done(err);
} else if(!user) {
return done(null, false);
}
return done(null, user);
});
});
passport.use(jwtStrategy);
module.exports = {
authLocal: passport.authenticate('local', { session: false }),
authJwt: passport.authenticate('jwt', { session: false }),
privateKey: privateKEY
};
I can't see why it can't find the UserModel.findOne function, or maybe i am missing something.
In your schema, you aren't extending the existing methods you are completely overwriting them i.e.
UserSchema.methods = { ... }
This line assigns a new object to methods which completely wipes out any existing functions (like findOne) that Mongoose provides. If you want to add static functions to the schema then you can extend statics
UserSchema.statics.myFunction = () => { ... }
Solved it. The problem was with the program flow. I used the auth.services in my user.model file to get ahold of my privateKey. This made me trying to use my model before it even were initialized.
I'm a beginner in NodeJS and I've tried to make an authentication form using NodeJS + express. I want to make a validation for my password (when "confirmpassword" is different than "password" it should return nothing. Unfortunately, I keep getting "data and salt arguments required". I tried in different ways, to put some conditions, but I keep getting this error. Any ideas how I should make it work?
Here is the file user.js:
const pool = require('./pool');
const bcrypt = require('bcrypt');
function User() {};
User.prototype = {
find : function(user = null, callback)
{
if(user) {
var field = Number.isInteger(user) ? 'id' : 'username';
}
let sql = `SELECT * FROM users WHERE ${field} = ?`;
pool.query(sql, user, function(err, result) {
if(err)
throw err
if(result.length) {
callback(result[0]);
}else {
callback(null);
}
});
},
create : function(body, callback)
{
var pwd = body.password;
var cpwd = body.confirmpassword;
// here i hash the pass
body.password = bcrypt.hashSync(pwd,10);
body.confirmpassword = bcrypt.hashSync(cpwd, 10);
if (body.password != body.confirmpassword){
callback(null);
}
else {
var bind = [];
for(prop in body){
bind.push(body[prop]);
}
let sql = `INSERT INTO users(username, fullname, password) VALUES (?, ?, ?)`;
pool.query(sql, bind, function(err, result) {
if(err) throw err;
callback(result.insertId);
});
}
},
login : function(username, password, callback)
{
this.find(username, function(user) {
if(user) {
if(bcrypt.compareSync(password, user.password)) {
callback(user);
return;
}
}
callback(null);
});
}
}
module.exports = User;
And the file pages.js:
const express = require('express');
const User = require('../core/user');
const router = express.Router();
const user = new User();
router.get('/', (req, res, next) => {
let user = req.session.user;
if(user) {
res.redirect('/home');
return;
}
res.render('index', {title:"My application"});
})
router.get('/home', (req, res, next) => {
let user = req.session.user;
if(user) {
res.render('home', {opp:req.session.opp, name:user.fullname});
return;
}
res.redirect('/');
});
router.post('/login', (req, res, next) => {
user.login(req.body.username, req.body.password, function(result) {
if(result) {
req.session.user = result;
req.session.opp = 1;
res.redirect('/home');
}else {
res.send('Username/Password incorrect!');
}
})
});
router.post('/register', (req, res, next) => {
let userInput = {
username: req.body.username,
fullname: req.body.fullname,
password: req.body.password
};
user.create(userInput, function(lastId) {
if(lastId) {
user.find(lastId, function(result) {
req.session.user = result;
req.session.opp = 1;
res.redirect('/home');
});
}else {
console.log('Error creating a new user ...');
}
});
});
router.get('/logout', (req, res, next) => {
if(req.session.user) {
req.session.destroy(function() {
res.redirect('/');
});
}
});
module.exports = router;
In userInput, you are not passing confirmpassword property.
let userInput = {
username: req.body.username,
fullname: req.body.fullname,
password: req.body.password
};
In create method, you are accessing it.
var cpwd = body.confirmpassword;
cpwd is null, and that's the reason for the error.
body.confirmpassword = bcrypt.hashSync(cpwd, 10);//**cpwd is null**
As per the docs, data is required argument and this cannot be null.
hashSync(data, salt)
data - [REQUIRED] - the data to be encrypted.
salt - [REQUIRED] - the salt to be used to hash the password.
I'm trying to create a API register route, but when I try to use User.function - AddUser, which I created in model file it says that there is unexpected token .. Here is the code:
router.post('/register', (req, res, next) => {
let newUser = new User({
username: req.body.username,
name: req.body.name,
email: req.body.email,
password: req.body.password,
photoUrl: req.body.photoUrl
})
User.addUser(newUser, (err, user) => {
if (err) {
res.send('Failed');
} else {
res.send('Registered');
}
});
});
Here is the model file code:
const mongoose = require('mongoose');
const schema = mongoose.Schema;
const bcryptjs = require('bcryptjs');
const userSchema = new schema({
username: { type: String, required: true },
name: { type: String },
email: { type: String, required: true },
password: { type: String, required: true },
photoUrl: { type: String }
});
const User = module.exports = mongoose.model('User', userSchema, 'users');
module.exports.addUser(newUser, callback) => {
bcrypt.genSalt(10, (err, salt) => {
bcrypt.hash(newUser.password, salt, (err, hash) => {
if (err) throw err;
newUser.password = hash;
newUser.save(callback);
});
});
}
In your model file code, add a = like following
module.exports.addUser = (newUser, callback) => {
bcrypt.genSalt(10, (err, salt) => {
bcrypt.hash(newUser.password, salt, (err, hash) => {
if (err) throw err;
newUser.password = hash;
newUser.save(callback);
});
});
}
I'm trying to implement passport.js and problem is in the way of exporting function in model:
User model file (user.js) looks like:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var passportLocalMongoose = require('passport-local-mongoose');
var bcrypt = require('bcrypt');
var userSchema = mongoose.Schema({
username: String,
password: String
});
userSchema.plugin(passportLocalMongoose);
userSchema.methods = {
getUserByUsername: function(username, callback){
var query = {username: username};
userSchema.findOne(query, callback);
/*userSchema.findOne(query, function(err, user) {
callback(err, user);
}); */
},
getUserById: function(id, callback){
userSchema.findById(id, callback);
},
comparePassword: function(candidatePassword, hash, callback){
bcrypt.compare(candidatePassword, hash, function(err, isMatch) {
if(err) throw err;
callback(null, isMatch);
});
}
}
module.exports = mongoose.model('User', userSchema);
I call the model (app.js):
// user schema/model
var User = require('./models/user.js');
and I'm trying to use exported functions in passport (app.js):
passport.use(new localStrategy(
function(username, password, done) {
User.getUserByUsername(username, function(err, user){
if(err) throw err;
if(!user){
return done(null, false, {message: 'Unknown User'});
}
User.comparePassword(password, user.password, function(err, isMatch){
if(err) throw err;
if(isMatch){
return done(null, user);
} else {
return done(null, false, {message: 'Invalid password'});
}
});
});
}));
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
User.getUserById(id, function(err, user) {
done(err, user);
});
});
I got TypeError: User.getUserByUsername is not a function
I tried to define a function regarding docs:
User.methods.getUserByUsername = function(username, callback){
var query = {username: username};
User.findOne(query, callback);
}
and the same error apperas in the console when I'm trying to login..
EDIT:
I've added:
var User = mongoose.model('User', userSchema);
module.exports = {
User: User
};
and now it works with methods definition:
module.exports.getUserByUsername()
so the final model file looks like:
// user model
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var passportLocalMongoose = require('passport-local-mongoose');
var bcrypt = require('bcrypt');
var userSchema = mongoose.Schema({
username: String,
password: String
});
userSchema.plugin(passportLocalMongoose);
var User = mongoose.model('User', userSchema);
module.exports = {
User: User
};
module.exports.createUser = function(newUser, callback){
bcrypt.genSalt(10, function(err, salt) {
bcrypt.hash(newUser.password, salt, function(err, hash) {
newUser.password = hash;
newUser.save(callback);
});
});
}
module.exports.getUserByUsername = function(username, callback){
var query = {username: username};
User.findOne(query, callback);
}
module.exports.getUserById = function(id, callback){
User.findById(id, callback);
}
module.exports.comparePassword = function(candidatePassword, hash, callback){
bcrypt.compare(candidatePassword, hash, function(err, isMatch) {
if(err) throw err;
callback(null, isMatch);
});
}
Thank you guys for your help.
Instead of
module.exports.getUserByUsername = ...
or
User.methods.getUserByUsername = ...
use
User.statics.getUserByUsername = ...
See http://mongoosejs.com/docs/2.7.x/docs/methods-statics.html
If you are new to NodeJS then understanding module.exports and exports can be a bit confusing. It's important to be crystal clear with this concept because you will frequently encounter this if yet get into serious nodejs development.
I am not going to explain how the exports work in nodejs here because there are plenty of tutorials in web. eg; Please read this.
Follow my code below to fix your issue.
//UserModel.js
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var bcrypt = require('bcrypt');
var passportLocalMongoose = require('passport-local-mongoose');
var userSchema = mongoose.schema({
username: String,
password: String
});
userSchema.plugin(passportLocalMongoose);
userSchema.methods = {
createUser: function(...){..},
getUserByUsername: function(..){..},
getUserById: function(..){..},
comparePassword: function(..),{}
}
module.exports = mongoose.model('User', userSchema);
You can access the methods from your UserSchema as:
//Controller.js
var User = require('path/to/UserModel.js');
User.createUser(..,..)
For more details you can refer to my repository and see how i am doing it here.