JS thinks function is undefined, even though it clearly exists - javascript

This question is related to Function is undefined, Bookshelf.js model function is not being recognized as a function
I am using Bookshelf.js to handle user register/login API-endpoints, built with NodeJS and ExpressJS. However, when POSTing to the login route, Bookshelf throws the following error:
Unhandled rejection TypeError: undefined is not a function
at \routes\index.js:217:36
at Strategy.strategy.success (\node_modules\passport\lib\middleware\authenticate.js:194:18)
at verified (\node_modules\passport-local\lib\strategy.js:83:10)
at null.<anonymous> (\config\passport.js:17:20)
...
The following is routes/index.js
var User = require('./models/User');
router.post('/login', function(req, res, next){
if(!req.body.username || !req.body.password){
return res.status(400).json({message: 'Please fill out all fields'});
}
passport.authenticate('local', function(err, user, info){
if(err){ return next(err); }
if(user){
return res.json({token: user.generateJWT()});
} else {
return res.status(401).json(info);
}
})(req, res, next);
});
The following is models/Users.js
var jwt = require('jsonwebtoken');
var bcrypt = require('bcrypt');
var bookshelf = require('../config/bookshelf');
var User = bookshelf.Model.extend({
tableName: 'users',
validPassword: function(password, encryptedPass) {
return new Promise(function (resolve, reject) {
bcrypt.compare(password, encryptedPass, function (err, match) {
if(err) return reject(err);
resolve(match);
});
});
},
generateJWT: function() {
// set expiration to 60 days
var today = new Date();
var exp = new Date(today);
exp.setDate(today.getDate() + 60);
return jwt.sign({
_id: this._id,
email: this.email,
exp: parseInt(exp.getTime() / 1000),
}, 'SECRET');
}
}, {
createPassword: function (password) {
return new Promise(function (resolve, reject) {
bcrypt.genSalt(10, function (err, salt) {
if (err) return reject(err);
bcrypt.hash(password, salt, function (err, hash) {
if (err) return reject(err);
resolve(hash);
});
});
});
}
});
module.exports = User;
and here is config/passport.js
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;
var User = require('../models/Users');
passport.use(new LocalStrategy(
function(email, password, done) {
new User({email: email}).fetch().then(function(data) {
var user = data;
var curr_user = this;
if(user === null) {
return done(null, false, {message: 'Invalid username or password'});
} else {
user = data.toJSON();
if (!curr_user.validPassword(password)) {
return done(null, false, {message: 'Invalid username or password'});
} else {
return done(null, user);
}
}
});
}
));
Is this an async/promise issue? Or have I gone wrong somewhere else?

Without seeing what is
at \routes\index.js:217:36, it's very difficult to begin to attempt to answer this question.
Edit: However, at Strategy.strategy.success does suggest that it is an issue with a Promise not being returned. In other words, you are probably doing something like
ThinkingYouAreReturningAPromiseInThisFunction
.success(function(result) {
// stuff
});
but are not in fact returning a Promise object in ThinkingYouAreReturningAPromiseInThisFunction(). In other words, success() is not a function. Generally, a Promise object would have a method like success(). But if no such object is being returned, then undefined is not a function. I would begin troubleshooting there.

Related

Bcrypt reset the hash and the passwords of the users are not good anymore each time the users POST data

I made a login with bcrypt.
I also made a page where users can edit their information, like their bio etc.
Each time an user edit his bio on this page the hash from bcrypt change, which is normal i suppose, but the user login back, the password is wrong...
I used the same model for mongoDb for the user when he log in and when he edit his data.
I started node.js recently so I apologize if my question is stupid,,,
The controller code with the Post :
app.post('/settings-user', mid.requiresLogin, function(req, res, next){
User.findById(req.session.userId, function (err, user) {
// todo: don't forget to handle err
if (!user) {
return res.redirect('/edit');
}
// good idea to trim
var bio = req.body.bio.trim();
// validate
if (!bio) { // simplified: '' is a falsey
req.flash('error', 'One or more fields are empty');
return res.redirect('/settings-user'); // modified
}
// no need for else since you are returning early ^
user.bio = bio;
// don't forget to save!
user.save(function (err) {
// todo: don't forget to handle err
res.redirect('/settings-user/');
});
});
});
The User model :
app.post('/settings-user', mid.requiresLogin, function(req, res, next){
User.findById(req.session.userId, function (err, user) {
// todo: don't forget to handle err
if (!user) {
return res.redirect('/edit');
}
// good idea to trim
var bio = req.body.bio.trim();
// validate
if (!bio) { // simplified: '' is a falsey
req.flash('error', 'One or more fields are empty');
return res.redirect('/settings-user'); // modified
}
// no need for else since you are returning early ^
user.bio = bio;
// don't forget to save!
user.save(function (err) {
// todo: don't forget to handle err
res.redirect('/settings-user/');
});
});
});
The User model :
var mongoose = require('mongoose');
var bcrypt = require('bcrypt');
var UserSchema = new mongoose.Schema({
email: {
type: String,
unique: true,
required: true,
trim: true
},
name: {
type: String,
required: true,
trim: true
},
password: {
type: String,
required: true
},
bio: {
type: String
}
});
// authenticate input against database documents
UserSchema.statics.authenticate = function(email, password, callback) {
User.findOne({ email: email })
.exec(function (error, user) {
if (error) {
return callback(error);
} else if ( !user ) {
var err = new Error('User not found.');
err.status = 401;
return callback(err);
}
bcrypt.compare(password, user.password , function(error, result) {
if (result === true) {
return callback(null, user);
} else {
return callback();
}
})
});
}
// hash password before saving to database
UserSchema.pre('save', function(next) {
var user = this;
bcrypt.hash(user.password, 10, function(err, hash) {
if (err) {
return next(err);
}
user.password = hash;
next();
})
});
var User = mongoose.model('User', UserSchema);
module.exports = User;
the pug file :
div
form(method='post', action='/settings-user')
label ADD BIO
br
input(type='text', name='bio', placeholder='Enter something', required='')
input(type='submit', value='Add Bio')
</body>
If anyone could help,,,
thank you!

getting error "user.save is not a function" while trying to update a doc

I am updating a document according to the code
var express = require('express');
var router = express.Router();
var user = require('./user');
router.put('/:username/email',
function(req, res, next) {
console.log("control check");
next();
},
email.acquire,
function(req, res) {
console.log("control check");
var username = req.params.username;
var address = req.body.email;
console.log(address);
user.find({
'username': username
}, function(err, user) {
if (err) {
throw (err);
console.log('ERROR WHILE PUT EMAIL');
} else {
console.log('success while PUT email');
user.email = address;
user.save(function(err, updatedUser) {
if (err) throw (err);
res.status(200).send(updatedUser)
});
}
});
});
module.exports = router;
but am getting the error:
events.js:183
throw er; // Unhandled 'error' event
^
TypeError: user.save is not a function
the code for user.js is
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var userSchema = new Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true
},
username: {
type: String,
required: true
},
password: {
type: String,
required: true
}
});
var user = mongoose.model('user', userSchema);
module.exports = user;
I already tried to create an object out of the model but to no avail.
and yes there exist a collection called "user".
The user returned from the find() callback will be an array of mongoose documents, hence why it is complaining. Either use the findOne() method which returns a single Mongoose document that has the save method or use findOneAndUpdate() for an atomic update.
You also need to be unambigious with variable naming since you have duplicate user variables, one for the mongoose model and another for the callback parameter.
Using findOneAndUpdate() follows:
user.findOneAndUpdate(
{ 'username': username },
{ '$set': { 'email', address } },
{ 'new': true /*, 'upsert': true */ }
function(err, updatedUser) {
if (err) {
throw (err);
console.log('ERROR WHILE PUT EMAIL');
} else {
console.log('success while PUT email');
res.status(200).send(updatedUser)
}
}
);
User.find returns array of the result, Use findOne. Don't use the same variable name it creates confusion.
user.findOne({
'username': username
}, function (err, userData) {
if (err) {
throw (err);
console.log('ERROR WHILE PUT EMAIL');
} else {
if (userData) {
console.log('success while PUT email');
userData.email = address;
userData.save(function (err, updatedUser) {
if (err) throw (err);
res.status(200).send(updatedUser)
});
}else{
res.status(200).send('Some response')
}
}
});

NodeJS responds before function is done

I'm writing an API with NodeJS and Express for a schoolproject and I'm struggling with the following:
The function getAuthUserId decodes the JWT token and gets the Id from user in the mongoDB server.
I call this function in a REST call "/user/authTest". But when I call this, the server responds before the database can return the Id, and the variable UId is undefined. As you can see, the Id is actually found. Any ideas on how i can fix this?
The API call code:
apiRoutes.post('/user/authTestID', function(req, res) {
var UId = getAuthUserId(req, res);
console.log(UId);
if (UId) {
res.sendStatus(200);
}else{
res.sendStatus(400);
}
});
The function:
function getAuthUserId(req, res) {
var user = new User();
var token = user.getToken(req.headers);
if (token) {
var decoded = jwt.decode(token, config.secret);
User.findOne({
name: decoded.name
}, function(err, user) {
if (err) throw err;
if (!user) {
res.status(403).send({success: false, msg: 'Authentication failed. User not found.'});
return false
} else {
console.log('Auth for ' + user.name + ' ' + user._id);
return user._id
}
});
} else {
res.status(403).send({success: false, msg: 'No token provided.'});
return '';
}
}
The output of the terminal:
[nodemon] restarting due to changes...
[nodemon] starting `node server.js`
Connected to MongoDB
undefined
::ffff:192.168.0.111 - POST /user/authTestID HTTP/1.1 400 11 - 175.006 ms
Auth for test 58f8954c3602b80552b6f1fb
Thanks in advance!
You need to make it a promise, like this.
API
apiRoutes.post('/user/authTestID', function(req, res) {
getAuthUserId(req, res).then(function (UId) => {
console.log(UId);
if (UId) {
res.sendStatus(200);
}else{
res.sendStatus(400);
}
});
}, function(err) {
console.log(err.msg)
res.status(err.status).send(err.msg);
});
Function
function getAuthUserId(req, res) {
return new Promise(function(resolve, reject){
var user = new User();
var token = user.getToken(req.headers);
if (token) {
var decoded = jwt.decode(token, config.secret);
User.findOne({
name: decoded.name
}, function(err, user) {
if (err) throw err;
if (!user) {
reject({status: 403, msg: 'Authentication failed. User not found.'});
} else {
console.log('Auth for ' + user.name + ' ' + user._id);
resolve(user._id)
}
});
} else {
reject({status: 403, msg: 'No token provided.'});
}
})
}
getAuthUserId get's the value in a CALLBACK !!! . You can't expect it to return values from it. As quick thing you can do something as below.
apiRoutes.post('/user/authTestID', function (req, res) {
var user = new User();
var token = user.getToken(req.headers);
if (token) {
var decoded = jwt.decode(token, config.secret);
User.findOne({
name: decoded.name
}, function (err, user) {
if (err) throw err;
if (!user) {
return res.status(403).send({success: false, msg: 'Authentication failed. User not found.'});
} else {
console.log('Auth for ' + user.name + ' ' + user._id);
return res.send(user._id)
}
});
} else {
return res.status(403).send({success: false, msg: 'No token provided.'});
// return '';
}
});
Try using Promise library like Bluebird
James' comment looks like a good, thorough resource on async calls. As others have mentioned, you cannot return values within a callback. You can use a Promise library, or you can change your getAuthUserId function to take a callback and have your console.log logic in there:
Example:
API call code:
apiRoutes.post('/user/authTestID', function(req, res) {
getAuthUserId(req, res, function(UId) {
// we're in a your new callback
console.log(UId);
if (UId) {
res.sendStatus(200);
}else{
res.sendStatus(400);
}
});
});
Function Code
// note new callback param
function getAuthUserId(req, res, callback) {
var user = new User();
var token = user.getToken(req.headers);
if (token) {
var decoded = jwt.decode(token, config.secret);
User.findOne({
name: decoded.name
}, function(err, user) {
if (err) throw err;
if (!user) {
res.status(403).send({success: false, msg: 'Authentication failed. User not found.'});
callback(false) // no more return, call callback with value
} else {
console.log('Auth for ' + user.name + ' ' + user._id);
callback(user._id) // no more return, call callback with value
}
});
} else {
res.status(403).send({success: false, msg: 'No token provided.'});
callback(''); // no more return, call callback with value
}
}

create a custom passport-jwt strategy middleware callback

I want to create a custom middleware for passport-jwt to handle authentication.
here is what I have done to create my own middleware :
var models = require('../models');
var passport = require("passport");
var passportJWT = require("passport-jwt");
var config = require("../config/config.json");
var ExtractJwt = passportJWT.ExtractJwt;
var Strategy = passportJWT.Strategy;
var params = {
secretOrKey: config.jwtSecret,
jwtFromRequest: ExtractJwt.fromAuthHeader()
};
/**
* jwt authentication strategy
*/
var strategy = new Strategy(params, function(payload, done) {
models.User.findById(payload.id)
.then((user)=>{
if (user) {
return done(null, {
id: user.id,
username : user.username
});
} else {
return done(new Error("User not found"), false);
}
}).catch((err)=>{
return done(err, false);
});
});
passport.use(strategy);
module.exports = {
initialize: function() {
return passport.initialize();
},
authenticate: (req, res, next)=>{
passport.authenticate('jwt', { session: false }, (err, user, info)=>{
if (err) { return next(err); }
if (!user) { return res.send("Custom Unauthorised").end(); }
// edit as per comment
//return res.send("Test Route Accessed").end();
req.user = user; // Forward user information to the next middleware
next();
})(req, res, next);
}
};
but everytime I type 'npm start' to run the app I face this error :
if (request.headers[AUTH_HEADER]) {
^
TypeError: Cannot read property 'headers' of undefined.
the authorization header is set in the request.
yes I did Find the answer here it is :
first define the strategy logic:
var strategy = new Strategy(params, function (payload, done) {
//finding the user in the database
console.log(payload);
models.users.findById(parseInt(payload.userId))
.then((user) => {
//if the user is found
if (user) {
return done(null, {
id: user.id,
username: user.username
});
} else {
return done(new Error("User not found"), null);
}
}).catch((err) => {
console.log(err);
return done(new Error("uncaught error! try again later"), null);
})
});
then make passport use that strategy"
passport.use(strategy);
and finally export the initialization function and the middleware function
module.exports = {
initialize: function () {
return passport.initialize();
},
authenticate: function (req, res, next) {
return passport.authenticate("jwt", {
session: false
}, (err, user, info) => {
if (err) {
console.log(err);
return next(err);
}
if (!user) {
return res.json({
status: 'error',
error: 'ANOTHORIZED_USER'
});
}
// Forward user information to the next middleware
req.user = user;
next();
})(req, res, next);
}
};
and then you can call the function authenticate defined above as a middleware in your routes.
here is an example :
//import the express router
var express = require('express');
var router = express.Router();
//here I am importing the functions defined above, I put them in the config folder
var jwt_login_strategy = require('../config/jwt-login-strategy');
//and finally use the jwt_login_strategy as a middleware
router.post('something', jwt_login_strategy.authenticate, your_other_middleware(req, res, next)=>{...});
you have to call the authenticate function without adding parentheses, just like this jwt_login_strategy.authenticate.
hope it will solve your problem as it did for mine.

Why auth.isAuthenticated() is not working in MEAN.JS?

I have made simple signup, signin and article using MEAN.JS with jsonwebtoken.
In signup page after user entering all values i am passing values to server through signup api. The server side I am creating jsonwebtoken and am passing to client side
exports.create = function (req, res, next) {
var newUser = new User(req.body);
newUser.provider = 'local';
newUser.role = 'user';
newUser.save(function(err, user) {
if (err) return validationError(res, err);
var token = jwt.sign({
_id: user._id
}, config.secrets.session, {
expiresInMinutes: 60 * 5
});
res.json({
token: token
});
});
};
After getting that token client calling some 'me' api (I did not understand what is that me is passing)
client side signup controller:
$scope.register = function(form) {
Auth.createUser({
username: $scope.user.name,
useremail: $scope.user.email,
password: $scope.user.password
})
};
auth.service:
createUser: function(user, callback) {
var cb = callback || angular.noop;
return User.save(user,
function(data) {
$cookieStore.put('token', data.token);
currentUser = User.get();
return cb(user);
},
function(err) {
this.logout();
return cb(err);
}.bind(this)).$promise;
}
user.service :
.factory('User', function ($resource) {
return $resource('/api/users/:id/:controller', {
id: '#_id'
},
{
changePassword: {
method: 'PUT',
params: {
controller:'password'
}
},
get: {
method: 'GET',
params: {
id:'me'
}
}
});
});
After signup:
get: {
method: 'GET',
params: {
id:'me'
}
}
I did not understand this. In server side 'me' api looking like this
route:
router.get('/me', auth.isAuthenticated(), controller.me);
controller :
exports.me = function(req, res, next) {
var userId = req.user._id;
User.findOne({
_id: userId
}, '-salt -hashedPassword', function(err, user) {
if (err) return next(err);
if (!user) return res.status(401).send('Unauthorized');
res.json(user);
});
};
auth.service:
var validateJwt = expressJwt({ secret: config.secrets.session });
/**
* Attaches the user object to the request if authenticated
* Otherwise returns 403
*/
function isAuthenticated() {
return compose()
// Validate jwt
.use(function(req, res, next) {
// allow access_token to be passed through query parameter as well
if(req.query && req.query.hasOwnProperty('access_token')) {
req.headers.authorization = 'Bearer ' + req.query.access_token;
}
validateJwt(req, res, next);
})
// Attach user to request
.use(function(req, res, next) {
User.findById(req.user._id, function (err, user) {
if (err) return next(err);
if (!user) return res.status(401).send('Unauthorized');
req.user = user;
next();
});
}).use(function (err, req, res, next) {
if (err.name === 'UnauthorizedError') {
var e = [];
e.push(err);
return res.status(401).send(e);
}
});
}
I want to know what they are passing in the 'me' api and how I'm getting 'req.user._id' in exports.me function. If I want to make the 'me' api (my own), how can I pass this my token?
The server side console I'm getting this: GET /api/users/me 200 876ms - 339b.

Categories