Is it possible to do a find inside a find on Node with Mongoose?
I'm letting a user change their email address, but I have to make sure the email hasn't been used by any other user before I save the new email.
I want to do this:
/*************************************************************
* MY_USER - PROFILE UPDATE
*************************************************************/
app.put('/api/myuser/info', auth, function(req, res) {
serverLog.log(req, production, {});
// User ID
var myUserID = req.session.passport.user._id;
if ( myUserID && validateID(myUserID) ) {
User.findOne({
_id: myUserID
}, function(err, data) {
if (err) throw err;
if (data == null) {
res.sendStatus(401);
console.log(401);
}
// Data
else {
// Update Email
if (req.body.email) {
// Check valid email
if ( validateEmail(req.body.email) ) {
console.log('validateEmail');
// Check Unique Email
User.findOne({
'local.user.info.email': email
}, function(err, user) {
if(err) return err;
if ( user ) {
// Email already in use
res.status(400).send('ERROR: Email Already in Use');
return;
}
console.log('uniqueEmail ' + true);
// Update email
user.local.user.info.email = req.body.email;
})
}
// Bad Email
else {
res.status(400).send('ERROR: Not a propper Email');
return;
}
}
// SAVE USER DATA
if ( info_sent ) {
user.save(function(err, data) {
if (err) throw err;
res.setHeader('Content-Type', 'application/json');
res.status(200).send(JSON.stringify(data.local.user.info, null, 3));
return;
});
}
// NO INFO WAS SENT
else {
res.status(400).send('ERROR: No information was sent.');
return;
}
}
});
}
// Bad / No User ID
else {
res.sendStatus(401);
}
});
I find the user, then check if email is in use
How would one go about doing this?
This doesn't work because you do not save your user in the callback function that checks if email already exists. Also consider using Promise to avoid callback hell
Anyway, you can do it like this:
// Check Unique Email
User.findOne({'local.user.info.email': email }, (err, user) => {
if (err) throw err;
if (user) {
return res.status(400).send('ERROR: Email Already in Use');
} else { // SAVE USER DATA
if (info_sent) {
user.save((err, data) => {
if (err) throw err;
res.setHeader('Content-Type', 'application/json');
return res.status(200).send(JSON.stringify(data.local.user.info, null, 3));
});
} else {
return res.status(400).send('ERROR: No information was sent.');
}
}
Related
I have the below controller which I use to manage the login system on my app. Is working in all cases except the one when I insert the wrong username.
I have inserted the below conditional statement to handle the error:
if (error === null) {
res.status(401).render('login', {
message: 'Email or Password is incorrect'
})
}
But when I insert it, I receive the following message on the terminal:
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
const jwt = require('jsonwebtoken');
const mysql = require('mysql');
const bcrypt = require('bcryptjs');
const { promisify } = require('util');
var db_config = {
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASS,
database: process.env.DB_NAME
};
var connection;
function handleDisconnect() {
connection = mysql.createConnection(db_config); // Recreate the connection, since
// the old one cannot be reused.
connection.connect(function (err) { // The server is either down
if (err) { // or restarting (takes a while sometimes).
console.log('error when connecting to db:', err);
setTimeout(handleDisconnect, 2000); // We introduce a delay before attempting to reconnect,
} // to avoid a hot loop, and to allow our node script to
}); // process asynchronous requests in the meantime.
// If you're also serving http, display a 503 error.
connection.on('error', function (err) {
console.log('db error', err);
if (err.code === 'PROTOCOL_CONNECTION_LOST') { // Connection to the MySQL server is usually
handleDisconnect(); // lost due to either server restart, or a
} else { // connnection idle timeout (the wait_timeout
throw err; // server variable configures this)
}
});
}
handleDisconnect();
// code in case the user leave the login space empty
exports.login = async (req, res) => {
try {
const { email, password } = req.body;
if (!email || !password) {
return res.status(400).render('login', {
message: 'Please provide an email and password'
})
}
connection.query('SELECT * FROM login WHERE email=?', [email], async (error, results) => {
// conditional statement to handle the wrong username error
if (error === null) {
res.status(401).render('login', {
message: 'Email or Password is incorrect'
})
}
//conditional if statement to compare password in database and password inserted by the client
if (!results || !(await bcrypt.compare(password, results[0].password))) {
res.status(401).render('login', {
message: 'Email or Password is incorrect'
}) //conditional statement to fetch the id of the client and signign in with sign() function
} else {
const id = results[0].id;
const token = jwt.sign({ id }, process.env.JWT_SECRET, {
expiresIn: process.env.JWT_EXPIRES_IN
});
// console.log('the token is:' + token);
const cookieOptions = {
expires: new Date(
Date.now() + process.env.JWT_COOKIE_EXPIRES * 24 * 60 * 60 * 1000 // 24 hours converted in milliseconds to set the expiration cookies to 24 hours
),
httpOnly: true
}//setting of cookies on the browser and redirecting to the user interface page
res.cookie('jwt', token, cookieOptions);
res.status(200).redirect('/ui');
}
});
} catch (error) {
console.log("this is the error:", error)
}
}
exports.register = (req, res) => {
// Destructor
const { name, email, password, passwordConfirm } = req.body;
//query that order to MySQL to get the user email only once
connection.query('SELECT email FROM login WHERE email = ?', [email], async (error, results) => {
if (error) {
console.log(error);
}
if (results.length > 0) {
return res.render('register', {
message: 'That email is already in use'
})
} else if (password !== passwordConfirm) {
return res.render('register', {
message: 'Password do not match'
});
}
let hashedPassword = await bcrypt.hash(password, 8);
// console.log(hashedPassword);
connection.query('INSERT INTO login SET ?', { name: name, email: email, password: hashedPassword }, (error, results) => {
if (error) {
console.log(error);
} else {
// console.log(results);
return res.render('register', {
message: 'User registered'
});
}
})
});
}
exports.isLoggedIn = async (req, res, next) => {
// console.log(req.cookies);
if (req.cookies.jwt) {
try {
//1)verify the token
decoded = await promisify(jwt.verify)(req.cookies.jwt,
process.env.JWT_SECRET
);
//2) Check if the user still exists
connection.query('SELECT * FROM login WHERE id = ?', [decoded.id], (error, result) => {
// console.log(result);
if (!result) {
return next();
}
req.user = result[0];
return next();
});
} catch (error) {
console.log(error);
return next();
}
} else {
next();
}
}
exports.logout = async (req, res) => {
res.clearCookie('jwt');
res.status(200).redirect('/');
}
Thanks in advance for suggestions or correction to the right path.
Do not forget to return the response or the function will continue.
return res.status(401).render('login', {
message: 'Email or Password is incorrect'
});
You need to halt the execution of the function by putting the return keyword or the code will run through the other statements.
if (error === null) {
return res.status(401).render('login', {
message: 'Email or Password is incorrect'
})
}
Or
if (error === null) {
res.status(401).render('login', {
message: 'Email or Password is incorrect'
})
return
}
I got it, or at least is working. I deleted the first if statement and modify the remain one as below. instead of !results I edited it to results == "" . So if results is empty it will render the login with te alert message.
if (results == "" || !(await bcrypt.compare(password, results[0].password))) {
res.status(401).render('login', {
message: 'Email or Password is incorrect'
}) //conditio
I have a "user.json" file that looks like this. using java script I have to parse this file and then check this email and file combinations against user input
{
"something#somewhere.com": "maybe",
"john#beatles.uk": "lennonj!",
"paul#beatles.uk": "mccartney",
"mick#rollingstones.uk": "jaggerm!"
}
function lookUpCredentials(filePath, cb) {
fs.readFile(filePath, (err, fileData) => {
if (err) {
return cb && cb(err);
}
try {
const object = JSON.parse(fileData);
return cb && cb(null, object);
} catch (err) {
return cb && cb(err);
}
});
}
lookUpCredentials("./user.json", (err, data) => {
if (err) {
console.log(err);
return;
}
credentials = data;
});
app.post('/login', (req, res) => {
let username = req.body.username;
let password = req.body.password;
})
My question is How could I go about checking whether the input information is contained in the user.json file ?
You have to call lookupCredentials() in with the handler provided to app.post().
After the check you give back an answer to client via res.send() resp. res.end(). For each situation you want to stop request and give the client an answer.
The lookup and credentials check could look as follows:
app.post('/login', (req, res) => {
// If you do not change the variable later on, use const !
const username = req.body.username
const password = req.body.password
// respond if provided parameters are not given or emtpy.
if(!username || !password) {
res.status(400).end('No username or No password provided.')
return
}
// now use your created function to lookup the data:
// first read in file via
lookUpCredentials('./user.json', (err, credentials) => {
if (err) {
console.error(err)
// always send a response on error!
res.status(500).end('Error when looking up credentials file.')
return
}
// since you have an object with usernames as keys, we simply
// check if there is a value in the object
const stored_password = credentials[username]
// when no password was found, the username was not listed! => bad username
if(!stored_password) {
console.error('bad username', username)
res.status(403).end('Bad username')
return
}
// Check the password!
// when missmatched, inform client!
if(stored_password !== password) {
console.error('bad password received for user', username)
res.status(403).end('Bad password')
return
}
// No checks failed! We now can inform user he is logged in!
res.status(200).end('Login succeceded for user', username)
});
})
I'm using Facebook chat api to create a simple cli script that will reply to messages that are sent to my facebook account. I'm trying to assign and get the user name and my name to use them inside the reply but they are always undefined. I think that the object property aren't assigned correctly. Is there a fix for this?
require('dotenv').config();
const fs = require('fs');
const fb = require('facebook-chat-api');
const path = require('path');
const appStateFile = path.format({ dir: __dirname, base: 'appstate.json' });
let currentUser = null;
if( !fs.existsSync(appStateFile) ){
//debug .env
console.log(process.env);
fb({email: process.env.FB_EMAIL, password: process.env.FB_PWD}, (err, api) => {
if(err){
return console.log(err);
}
console.log(api);
api.setOptions({
listenEvents: true
});
fs.writeFileSync(appStateFile, JSON.stringify(api.getAppState()));
let id = api.getCurrentUserID();
api.getUserInfo(id, (err, profile) => {
console.log(profile); // profile is logged correctly
currentUser = profile;
});
api.listenMqtt( (err, event) => {
if(err){
return console.log(err);
}
if(event.type === 'message'){
console.log(event.body)
api.getUserInfo(event.senderID, (err, user) => {
if(err){
return console.log(err);
}
console.log(user); // user object is logged correctly
api.sendMessage('...', event.threadID)
});
}
});
});
}else{
fb({appState: JSON.parse(fs.readFileSync(appStateFile))}, (err, api) => {
if(err){
return console.log(err);
}
console.log(api);
api.setOptions({
listenEvents: true
});
let id = api.getCurrentUserID();
api.getUserInfo(id, (err, profile) => {
console.log(profile);
currentUser = profile;
});
api.listenMqtt( (err, event) => {
if(err){
return console.log(err);
}
if(event.type === 'message'){
console.log(event.body)
api.getUserInfo(event.senderID, (err, user) => {
if(err){
return console.log(err);
}
console.log(user)
api.sendMessage(`FB Pager v1.0.\nHi ${user.name}!Your message was forwarded with an email to ${currentUser.name}.`, event.threadID)
});
}
});
});
}
I think the problem here is that api.getUserInfo is asynchronous.
So you would need to nest them to get it to work.
Or you can try this, since getUSerInfo allows you to add an array of user ids to get the data for:
api.listenMqtt((err, event) => {
if (err) {
return console.log(err);
}
if (event.type === "message") {
const currentUserId = api.getCurrentUserID();
const senderId = event.senderID;
api.getUserInfo([currentUserId, senderId], (err, ret) => {
if(err) return console.error(err);
// Ret should contain the two users
// See: https://github.com/Schmavery/facebook-chat-api/blob/master/DOCS.md#getUserInfo
console.log(ret);
});
}
});
Nesting user calls method:
api.listenMqtt((err, event) => {
if (err) {
return console.log(err);
}
if (event.type === "message") {
let currentUserId = api.getCurrentUserID();
api.getUserInfo(currentUserId, (err1, signedInUser) => {
if (err1) {
return console.log(err);
}
api.getUserInfo(event.senderID, (err2, userInMessage) => {
if (err2) {
return console.log(err);
}
console.log(signedInUser, userInMessage)
api.sendMessage("...", event.threadID);
});
});
}
});
After a lot of debug I've found the correct way to access the needed informations. Since the user informations after that are retrived are mapped to another object that is the userId, the only way to access to each property is to use a for loop. Initially I was thinking that this can be avoided but unfortunately it's necessary otherwise using only dot notation will result in undefined. This is how I've solved
api.getUserInfo(userId, (err, user) => {
let username;
if(err){
return console.log(err);
}
for(var prop in user){
username = user[prop].name;
}
api.sendMessage(`Hello ${username!}`, event.threadID);
});
I want to pass my own data from passport.use to passport.authenticate.
I thought that the info parameter in
passport.authenticate('local', function(err, user, info)
could be used for that.
So is there a way to doing this ?
My auth route
passport.authenticate('local-register', (err, user, info) => {
if (err) {
return next(err); // 500 status
}
console.log(info);
if (info) {
console.log('rendering info ' + info);
return res.render('auth/register', { info });
} else {
if (!user) {
return res.status(409).render('auth/register');
}
req.login(user, err => {
if (err) {
console.log(err);
return next(err);
}
return res.redirect('auth/profile');
});
}
})(req, res, next);
});
My config file
module.exports = passport => {
passport.use(
'local-register',
new LocalStrategy(
{
...
},
(req, email, password, done) => {
// Check if form is filled out correctly
let errors = [];
//check for same email
SCUser.findOne({ 'local.email': req.body.email }, (err, local) => {
if (local) errors.push({ text: 'Email already in use.' });
//check for same passwords
...
//check for password length
...
//abort if errors are found
if (errors.length > 0) {
const info = {
errors: errors,
...,
};
console.log(`returning info ${info}`);
return done(null, info);
}
//form is filled in correctly create a user
else {
...
}
...
Random things I've tried so far:
Adding , form_validate behind info & changing the required variables to form_validate doesn't pass it through to the auth file.
There are probably better ways to handle form validation, haven't looked that up yet, if you have any suggestions please tell me, but I still kind of want to know if it would be possible to pass custom objects through passports methods.
Take a look at the example from the documentation:
passport.use(new LocalStrategy(
function(username, password, done) {
User.findOne({ username: username }, function (err, user) {
if (err) { return done(err); }
if (!user) {
return done(null, false, { message: 'Incorrect username.' });
}
if (!user.validPassword(password)) {
return done(null, false, { message: 'Incorrect password.' });
}
return done(null, user);
});
}
));
If you determine that the username/pass are not correct, you can call done with the last parameter being any object that you wish
return done(null, false, { message: "Incorrect password", otherData: "my other data"});
And, if it is a success, you can pass the user, but there is no reason you can't add more data to the user, or pass something completely different.
return done(null, {username: "user123", otherData: myData, customString: "myString"});
So I'm trying to create a sign up route that checks to see if the user exists first and i have the database call in a separate function that needs to return true or false when it's done. The problem is i'm not very familiar with callbacks and the whole asynchronous thing everything that i have searched for does not seem to work keeps giving me.
TypeError: callback is not a function
This is my code any help or direction would be appreciated.
function pullUserFromDatabase(username, callback) {
console.log(username); //for debug
mongodb.connect(url, function(err, db) {
if(err) {
console.log("didn't get far" + err) //for debug
}
var collection = db.collection(username);
collection.findOne({username}, function(err, item) {
if(err) {
console.log("nope it broke" + err) //for debug
} else {
console.log("it worked" + JSON.stringify(item)) //for debug
callback(true);
}
});
});
}
app.post("/signup", function(req, res) {
var username = req.headers["username"],
password = req.headers["password"],
randomSalt = crypto.randomBytes(32).toString("hex"),
passwordHashOutput = crypto.createHash('sha256').update(password + randomSalt).digest("hex");
if(!username || !password) {
res.send("Username or password not provided.")
} else if(pullUserFromDatabase(username)) {
res.send("User exist.")
}
});
You need to use the callback as follows:
function pullUserFromDatabase(data, callback) {
console.log(data.username); //for debug
mongodb.connect(url, function(err, db) {
if(err) {
console.log("didn't get far" + err) //for debug
}
var collection = db.collection(data.collection);
collection.find({"username": data.username}).count(function (err, count) {
callback(err, !! count);
});
});
};
app.post("/signup", function(req, res) {
var username = req.headers["username"],
password = req.headers["password"],
randomSalt = crypto.randomBytes(32).toString("hex"),
passwordHashOutput = crypto.createHash('sha256').update(password + randomSalt).digest("hex");
if(!username || !password) {
res.send("Username or password not provided.")
}
var data = {
username: username,
collection: "collectionName"
}
if(!username || !password) {
res.send("Username or password not provided.")
}
pullUserFromDatabase(data, function(err, exists) {
if (err) {
res.send(400, "Error - " + err);
}
else if(exists) {
res.send(200, "User exists.");
}
res.send(200, "User does not exist.");
});
});
The reason that callback is undefined is because you didn't pass a 2nd argument to pullUserFromDatabase(username) Provide a 2nd argument, eg. pullUserFromDatabase(username, function(result) {/* do something here with the result variable */})
If you're not very familiar with aync & callbacks, you might find it more intuitive to use promises, but that comes with its own learning curve.
In the context of the original code, this looks like:
...
if(!username || !password) {
res.send("Username or password not provided.");
return;
}
pullUserFromDatabase(username, function(result) {
if(result) {
res.send("User exist.");
} else {
// TODO: Handle this case. If res.send() is never called, the HTTP request won't complete
}
});
...
Also, you need to ensure your callback is always invoked. Add callback(false):
console.log("nope it broke" + err); //for debug
callback(false);
Do a similar step after "didn't get far" and then return so the callback doesn't get invoked multiple times.