I'm using passport js for validate users. Here is my strategy.
passport.use(
new Strategy(opts, async ( payload , done) => {
try {
let user = await User.findById(payload.id);
if (!user) throw new Error("User not found");
if(user.role !== payload.role) throw new Error("Hacker");
return done(null, user.getUserInfo());
} catch (e) {
console.log(e);
done(null, false);
}
})
);
And if all OK, I'm returning a user.getUserInfo(). Now, here is my route:
router.get("url", passport.authenticate("jwt", { session: false }), async (req, res) => {
try {
} catch (e) {
console.log(e);
}
});
And now, when user pass the validator, how can I get this data from return done() statement, or it is unrealistic to take data from there. I'm a new in node js, so I don't know how to do that, or Is real to take data from passport.authenticate()?
In routes, Do:
{session: true}
and inside req.user, you will get the user data.
Otherwise, a work around can be to set user data in request just before done callback function.
req.userData = user.getUserInfo();
Related
I'm using passport js for validate users. Here is my strategy.
passport.use(
new Strategy(opts, async ( payload , done) => {
try {
let user = await User.findById(payload.id);
if (!user) throw new Error("User not found");
if(user.role !== payload.role) throw new Error("Hacker");
return done(null, user.getUserInfo());
} catch (e) {
console.log(e);
done(null, false);
}
})
);
And if all OK, I'm returning a user.getUserInfo(). Now, here is my route:
router.get("url", passport.authenticate("jwt", { session: false }), async (req, res) => {
try {
} catch (e) {
console.log(e);
}
});
And now, when user pass the validator, how can I get this data from return done() statement, or it is unrealistic to take data from there.
In routes, try - {session: true}
and inside req.user, you will get the user data.
Otherwise, a work around can be to set user data in request just before done callback function.
req.userData = user.getUserInfo();
I used MVC to make a NodeJS server and this is one of the controllers:
module.exports.create_user = async function (req, res) {
// console.log(req.body);
// console.log(req.user);
await Company.findOne({ user: req.body.user }, function (err, user) {
if (user) {
return res.redirect('/login');
}
else {
if (req.body.password == req.body.confirm_password) {
Company.create({
"country": req.body.country,
"username": req.body.user,
"password": req.body.password
});
}
else {
console.log('Passwords didnt match');
}
}
});
req.session.save(() => {
return res.redirect('/profile');
})
}
What this code supposed to do?
It searches if a user already exists; if yes, it will redirect to /login.
If no such user exists, it should create a new user and redirect to /profile.
What does this code do?
Regardless of whether the user exists or not, the code always redirects to /login. Also, a user is created in the database, so every time a new user wants to signup, the user needs to signup and then go to sign in to get access to /profile
What is the problem here which doesn't allow redirect to /profile? And how to fix it?
Let me know if you need anything else
Use username instead of user to find a user
Company.findOne({ username: req.body.user });
You are mixing callback style with async/await, await keyword does not affect on your, it will not wait until the query finished. await keyword just working when you wait for a Promise like object (then able object).
I guess you are using mongoose, the current version of mongoose supports Promise return style.
module.exports.create_user = async function (req, res) {
// console.log(req.body);
// console.log(req.user);
try {
// Use `username` instead of `user` to find a user
const user = await Company.findOne({ username: req.body.user }); // callback is not passed, it will return a Promise
if (user) {
return res.redirect('/login');
}
if (req.body.password == req.body.confirm_password) {
await Company.create({ // wait until user is created
"country": req.body.country,
"username": req.body.user,
"password": req.body.password
});
// then redirect page
req.session.save(() => {
return res.redirect('/profile');
});
} else {
console.log('Passwords didnt match');
// what happen when password didn't match
// return res.redirect('/login'); ???
}
} catch (error) {
// something went wrong
// return res.redirect('/login'); ???
}
}
passport.checkAuthentication = async function (req, res, next) {
console.log(req.user);
let auth_status = await req.isAuthenticated() ? "sucess" : "failure";
console.log(`Authentication ${auth_status}`);
// if the user is signed in, then pass on the request to the next function(controller's action)
if (await req.isAuthenticated()) {
return next();
}
// if the user is not signed in
return res.redirect('/login');
}
I did a but more work on this and possibly the controller is working fine and the problem could be in middleware. In the signup case discussed above, the middelware always logs 'Authentication failure' to console.
I have a /register router for signing up a user. I am using cookie-session (which is similar) instead of express-session for simplicity for now.
I am stuck on the part where I need to authenticate a user on sign up. I am confused about the functionality of req.sessions.save() and req.login(). I know req.login() is provided by passport.js, but I don't understand which one provides the req.session object.
I am new to passport.js and have read numerous articles, videos, and StackOverflow questions extensively to build up my knowledge. Honestly, the passport documentation has been quite a pain so far. I am still confused about how session initiation on signup should work. Many articles skipped the signup part. I thus request help on how to do it.
router.post('/register', (req, res, next) => {
console.log(req.body)
User.findOne({email: req.body.email}).then((currentUser) => {
if(currentUser){ // already exists
res.render('login')
} else { // if not, create user in our db
new User({
email: req.body.email
}).save().then((newUser) => {
passport.authenticate('local')(req, res, () => {
//>>>> //**This is where I don't know what to do**
req.session.save((err) => {
if (err) {
return next(err)
}
res.redirect('http://localhost:3000')
})
})
});
}
});
})
const express = require("express");
const router = express.Router();
const passport = require("passport");
router.post("/register", (req, res, next) => {
User.findOne({ email: req.body.email }).then((currentUser) => {
if (currentUser) { // already exists
res.render('login')
} else { // if not, create user in our db
new User({
email: req.body.email
}).save();
}
});
passport.authenticate("local", function (err, user, info) {
if (err) {
return res.status(400).json({ errors: err });
}
if (!user) {
return res.status(400).json({errors:"No user found."});
// or save User : new User({email: req.body.email}).save();
}
req.login(user, function (err) {
if (err) {
return res.status(400).json({ errors: err });
}
req.session.save((err) => {
if (err) {
return next(err)
}
res.redirect('http://localhost:3000')
});
return res.status(400).json({ success: `logged in ${user.id}` });
});
})(req, res, next);
});
module.exports = router;
passport.authenticate('local')(request, response, () => {
req.session.save((err) => {
if (err) {
return next(err)
}
res.redirect('http://localhost:3000')
})
}
I've have had a working version of mongoose instance methods working before. I'm not sure what is different about it this time around. The only thing I have done differently this time is that I separated the mongoose connection function outside of the server.js file into a config file that will be imported and call the connect() function.
I will mostly use this instance method in passport with the local strategy to log in the user. When I go to call my instance method on the user instance that was found by the previous UserModel.findOne({ email }) the verify(password) instance method is not called and does not throw any errors.
For testing purposes, I've tried to hard code a UserModel.findOne() right into connection field and I do get a user back. I then decided to call my instance method right off of the returned user instance named verify().
I have also attempted changing the name of the method to comparePassword, I've tried testing with statics to check if it is even called at all (which it wasn't), I've also tried to research other ways to import my schemas and models, and that does not seem to work. I have experimented with Async/Await hasn't changed the output
File: mongo.db.js
const connect = () => {
return new Promise((resolve, reject) => {
mongoose.connect(
config.get('DB.STRING'),
{ useCreateIndex: true, useNewUrlParser: true },
async (err) => {
if (err) reject(err)
resolve()
// TESTING INSTANCE METHODS
await mongoose.connection
.collection('users')
// HARD CODED TEST EMAIL
.findOne({ email: 'braden_feeney#hotmail.com' }, (err, result) => {
if (err) reject(err)
console.log(result)
console.log(result.verify('test1234'))
})
},
)
})
}
const close = () => {
return mongoose.disconnect()
}
export default { connect, close }
File: passport.config.js
passport.use(
new LocalStrategy(
{
usernameField: 'email',
passwordField: 'password',
},
async (email, password, done) => {
try {
// Find the user given the email
const user = await User.findOne({ email })
// If not found
if (!user) return done(null, false)
// Check if the password is correct
const isValididated = await user.verify(password)
// If not matched
if (!isValididated) return done(null, false)
// Return the user
done(null, user)
} catch (error) {
done(error, false)
}
},
),
)
File: users.model.js
const UserSchema = new Schema(
// HIDDEN FOR SECURITY
{ ... },
{ versionKey: false, timestamps: true },
)
// HIDDEN FOR SECURITY - PRE SAVE WORKS AS EXPECTED
UserSchema.pre('save', async function(next) { ... })
// THIS IS THE METHOD THAT SHOWS AS 'Not a Function'
UserSchema.methods.verify = function(password) {
bcrypt.compare(password, this.password, (err, res) => {
if (err) return new Error(err)
return res
})
}
export default model('User', UserSchema)
When I call user.verify(password) I expect to see a Boolean value either get returned from the function.
The actual result is an Error thrown stating
TypeError: user.verify is not a function
This section does not make sense:
async (email, password, done) => {
try {
const user = await User.findOne({ email })
if (user) // <-- if you have a user record
return done(null, false) // <-- you return!
// There was no valid user ... how would there be user.verify?
const isValididated = await user.verify(password)
if (!isValididated) return done(null, false)
done(null, user)
} catch (error) {
done(error, false)
}
}
You seem to return on valid user but calling user.verify when you do not have a user. So it seems your flow is somewhat problematic.
After tinkering around with how mongoose creates instance methods, I tried using another way to make the method work. I had to wrap the bcrypt.compare() in a promise because my code wasn't waiting for a response.
File: users.model.js
UserSchema.method('verify', function(password) {
return new Promise((resolve, reject) => {
bcrypt.compare(password, this.password, (err, res) => {
if (err) reject(err)
resolve(res)
})
})
})
I still prefer the way that is mentioned in my question because I believe it looks cleaner and is not relying on a string to be the method name.
All you have to do is just mark this function with "async".
// THIS IS THE METHOD THAT SHOWS AS 'Not a Function'
UserSchema.methods.verify = async function (password) {
bcrypt.compare(password, this.password, (err, res) => {
if (err) return new Error(err)
return res
})
}
& Another thing that you've to keep in mind is that when you're retrieving user from your DB make sure to use the "await" keyword there as well.
I was getting the same error and then I noticed that I forgot to use the "await" keyword and my code goes to the next line without even waiting for the "user" to arrive from DB and we don't have a user or instance and we're calling "compare" method on that instance which hasn't arrived from DB.
That's why you were getting error that comparePassword is not a function.
I have an API / express router:
router.post("/signup", async function (req, res) {
try {
var user = await controllers.user.register(req.body.username, req.body.password);
req.session.user = user;
res.json(user);
} catch (e) {
res.status(500).json("DB Error");
}
});
Currently, on error, it returns 500 DB error. This is my controller:
function register(username, password) {
return new Promise((resolve, reject) => {
User.findOne({ username: username }).lean().exec((e, doc) => {
if (e) reject(e);
if (doc) {
reject("Username already exists.");
} else {
var user = new User({ username, password: hash(password) });
user.save((e) => {
if (e) reject(e);
else {
delete user.password;
resolve(user);
}
});
}
});
});
}
What's the right way to return a 400 if username already exists, and a 500 if it was a database error?
Mongoose already uses promises, the use of new Promise is promise construction antipattern.
Express doesn't have the concept of controllers, there are only route handlers and middlewares. Since register should be very aware of the way it will be used in a response, there may be no need for another level of abstraction above route handler. There will be no problem when a function has access to handler parameters and can form a response in-place.
It can be:
router.post("/signup", async function (req, res) {
try {
const { body, password } = req.body;
const user = await User.findOne({ username: username }).lean();
if (user) {
res.status(400).json("Username already exists");
} else {
...
res.json(user);
}
} catch (e) {
res.status(500).json("DB Error");
}
});
In case route handler needs to be reused in multiple places with some variations, it could be refactored to higher-order function or some other helper that is aware of original req and res parameters.
You can change the way you are rejecting the Promise. I'd suggest something like:
function register(username, password) {
return new Promise((resolve, reject) => {
User.findOne({ username: username }).lean().exec((e, doc) => {
if (e) reject(500);
if (doc) {
reject(400);
} else {
var user = new User({ username, password: hash(password) });
user.save((e) => {
if (e) reject(500);
else {
delete user.password;
resolve(user);
}
});
}
});
});
}
And in the route:
router.post("/signup", async function (req, res) {
try {
var user = await controllers.user.register(req.body.username, req.body.password);
req.session.user = user;
res.json(user);
} catch (e) {
res.status(e).json(e == 400 ? "Username already exists." : "DB Error");
}
});