Note: What you see below is the updated description of my problem, because I have been going down a rabbit hole and finding the root cause of a problem.
So, I found what's causing it (read 'OLD DESCRIPTION' below to know the context), but I have zero idea why is it being caused. So, the thing is, apparently Node cannot find the utils.getHash function (I have a separate file called utils.js which exports the getHash function), so it is never called, and execution never moves forward.
utils.js
...
const getHash = (password) => {
return crypto.createHash('sha3-512').update(password).digest('hex')
}
...
module.exports = {
getHash: getHash
}
Someone help please :(
OLD DESCRIPTION
There's a weird problem I am facing. I wrote a backend API server in ExpressJS, and one of the task it performs is user authentication. I am using MongoDB as the database, and Mongoose to connect and perform operations on it.
The problem I am facing is that the checkUserCreds function does not proceed after a point (commented in code), and Express just returns a blank JSON response.
And I say it it's weird, because I tested with the SAME code just 2 days back, it worked correctly like it should.
user.js
userSchema.statics.checkUserCreds = function (email, password) {
return new Promise((resolve, reject) => {
// Execution goes upto '$and' line, then it goes nowhere; no exceptions are raised
User.findOne({
$and: [{ email: email }, { password: utils.getHash(password) }]
}, (err, userDoc) => {
if (err) {
reject({ status: "ERROR", message: err })
} else if (userDoc) { // If valid credential
console.log(`User with email '${email}' logged in`)
resolve({ status: "OK", message: "Login successful!" })
} else { // If invalid credential
reject({ status: "ERROR", message: "Invalid credential!" })
}
})
})
}
api.js
// Route - Login (POST: email, password)
router.post("/login", (req, res) => {
// If user is already logged in, reject further login
if (req.session.email) {
res.json({ status: "ERROR", message: "Already logged in!" }).status(403).end()
} else {
// Get data from body
var form = formidable()
form.parse(req, (err, fields, files) => {
if (err) {
res.json({ status: "ERROR", message: err }).status(500).end()
} else {
// Check if credentials are valid
User.checkUserCreds(fields.email, fields.password).then((result) => {
// This portion of code isn't reached either
req.session.email = fields.email
res.json(result).status(200).end()
}).catch((err) => {
res.json(err).status(401).end()
})
}
})
}
})
Can anyone tell me why this is happening?
Related
Hi so it's kind of complicated for me, hope anyone can help.
Here's the situation : i have an app divided server side with node/express and front side with Vuejs,
what I'm doing in the back is creating a user here's the code :
const createUser=(req, res, next) => {
console.log("register");
let con=req.con
let { email,password } = req.body;
console.log(req.body)
con.query(
`SELECT * FROM users
WHERE email = $1`,
[email],
(err, results) => {
if (err) {
console.log(err);
res.status(404).json({error: err});
}
console.log(results);
if (results.rows.length > 0) {
//throw new error_types.InfoError("user already exists");
res.status(200).json({error: "user already exists"});
} else {
const hashedPassword = bcrypt.hashSync(password, parseInt(process.env.BCRYPT_ROUNDS));
con.query('INSERT INTO users (email,password) VALUES ($1, $2)',
[email,password],
(err, results) => {
if (err) {
next(err);
}
res.json({info: "User inseted" });
}
);
}
}
);
}
so im checking if it already exists else register it in DB,all good here.
Now in my Vuejs part i have this :
REGISTER({ commit, dispatch, rootState }, { payload }) {
const {email,password} = payload
console.log(payload)
commit('SET_STATE', {
loading: true,
})
const register = mapAuthProviders[rootState.settings.authProvider].register
register(email,password)
.then(success => {
if (success) {
notification.success({
message: "Succesful Registered",
description: "You have successfully registered!",
})
router.push('/auth/login')
commit('SET_STATE', {
loading: false,
})
}
if (!success) {
commit('SET_STATE', {
loading: false,
})
}
})
},
Now the problem happens here as the registration is done all okay but when i use the same email again for another registration it said the same message successfully registred but do not get saved to DB now what i want is the message user aleady exists that appears.
Anyone can help me please?
Edited :added axios part
export async function register(email,password) {
return axios
.post('/register', {emailpassword,})
.then(response => {
if (response) {
const { token } = response.data
if (token) {
store.set('accessToken', token)
}
return response.data
}
return false
})
.catch(err => console.log(err))
}
Without seeing the actual source code that does the HTTP request from the client, it's hard to say exactly what the error handling looks like. The most obvious culprit is this:
res.status(200).json({error: "user already exists"});
You're responding with HTTP 200 OK when an error occurs. Typically, a client implementation will treat this as success. You should signal to clients that an error has occurred - for example, respond with a "409 Conflict". Also make sure the client's fetch() call (or whatever the client uses for talking to the server) does proper error handling (checks statusCode).
The code has another issue, however - a race condition. This is a case of a TOCTTOU (Time-of-Check to Time-of-Use), where a non-zero amount of time passes between the existence check (SELECT) and the INSERT. If two users are registering for the same e-mail at the same time, they could both get a success.
You should remove the check altogether and use uniqueness constraints offered by the database instead (UNIQUE INDEX). This way, the DB guarantees there can be no duplicates and you don't have to worry about race conditions.
I'm using node and express. What I want to do is to do is make a mix of res.render and res.redirect.
Thing is, res.render can only receive a .ejs file and data, and redirect will go to a specific URL. What I need to do is go to a specific URL (e.g. /reviews/new/:id), render a .ejs file and give some data to it.
This is my code. I can't use session or cookies for this project.
This are the routes, user enters to edit a review of some show. If it is a new review, the id of the show is in the URL, if the user is editing one, the ID of the review is on the URL. Either way, if something fails, I have to append something to this URL and send data.
router.get('/new/:id', controller.newReview);
router.post('/store', controller.checkBeforeStoringReview);
router.get('/edit/:id', controller.editReview);
router.post('/update', controller.checkBeforeUpdatingReview);
This is the function to check auth before updating.
checkBeforeUpdatingReview: function(req, res) { // auth before updating review (can't use session or cookies)
console.log(req.body)
DB
.User
.findOne(
{
where : {
email: req.body.email,
},
}
)
.then (function (results) {
if (results[0] != '') {
if (bcrypt.compareSync(req.body.password, results.password)) {
return module.exports.updateReview(req, res, results)
} else { // same as catch
return res.render('reviews/edit/', { // i'm missing the ID (which i have in req.params.id) at the end of the route
id : req.params.id,
errors : "Incorrect username or password",
email : req.body.email,
});
}
}
})
.catch (function (error) {
console.log(error)
return res.render('reviews/edit/', { // i'm missing the ID (which i have in req.params.id) at the end of the route
id : req.params.id,
errors : "An unexpected error happened",
email : req.body.email,
});
})
},
If everything's ok, as seen above, it goes directly to this function
updateReview: function(req, res, results) { // update review
console.log(req.body)
DB
.Review
.update(req.body,
{
where : {
id: req.body.review_id,
}
}
)
.then(function (results) {
return res.redirect('/series/detail/' + req.body.series_id)
})
.catch (error => {
return res.send(error)
})
},
TL;DR: If auth fails, should go back to the review url and send the data that was sent so that the user does not lose it.
So that's it, if I could use sessions/cookies I think I would be able to go back to the last route, but I can't for this.
Thanks in advance!
I'm new to the MEAN stack app and am having some trouble trying to send data from the server to the front end. However, I do have some communication going on, but this is all I can seem to do. In the server I have the json message being sent.
Server
router.route("/users/register").post((req, res) => {
registerLogin.findOne({ $or: [{ username }, { email }]}, (err, user) => {
if (err)
res.send(err);
else if (!username || !email || !password)
res.status(403).json({ registerErrRequired: "Fill out whole form." });
Front end
registerUser(username, email, password) {
const user = {
username: username,
email: email,
password: password
};
return this.http.post(`${this.uri}/users/register`, user)
.pipe(map(response => console.log(response)))
.subscribe(() => { this.router.navigate(["/users/login"]) }, (errResp) => {
errResp.error ? this.ngFlashMessageService.showFlashMessage({
messages: [errResp.error.registerErrRequired], // Where json message gets caught and shown to the browser
dismissible: true,
timeout: false,
type: 'danger'
}) : console.log("An unkown error occured.");
});
}
This works well, but I can't seem to do req/res other than using a flash message. My issue is wanting to use it in other ways than just flash messages. For example, if the user does not have a session, then I want them to navigate back to the the log in page. Here's what I tried but failed.
Server
// Middleware
const redirectLogin = ((req, res, next) => {
if (!req.session.user)
res.status(401).json({ loggedIn: false });
else
next();
});
// Route
router.route("/home").get(redirectLogin, (req, res) => {
Blog.find((err, docs) => {
if (err)
console.log(err);
else
res.json(docs);
});
});
Front end
homePage() {
// Here is where I would like to say, If session, then true, else navigate(["/users/login"])
if (loggedIn === false)
this.router.navigate(["/users/login"])
else
// Success
return this.http.get(`${this.uri}/home`);
}
The only way I found communication was through sending error flash messages, but nothing else.
What you can do is call an api to check whether the user is logged in or not in ngOnInit lifecycle hook,so every time your component loads you can check whether the session exists on backend and route accordingly.
export class App implements OnInit{
constructor(){
//called first time before the ngOnInit()
}
ngOnInit(){
//CheckLogin() is a method in your service which calls your backend api
this.http.post("your authentication url to check if session exits ",{username:username}).subscribe(data=>{
if (data["loggedIn"] === false)
this.router.navigate(["/users/login"])
})
}
}
Angular also has HTTP interceptors,you can solve this issue with jwt and http interceptors
So whilst learning JS and specifically the MEAN 2 stack i'm trying to build out a basic multi tenanted app. Im building out sign up routes in express and the flow i'm trying to achieve would be:
Sign up with company name, email and password. The info would go to save a new tenant, then return the _id of the new tenant and then use this new id, the email and the password to save a new user.
The closest is:
router.post('/', function (req, res, next) {
var tenant = new Tenant({
name: req.body.name
});
var newTenant;
tenant.save(function (err, tenant) {
if (err) {
return res.status(500).json({
title: 'An error has occured',
error: err
});
}
res.status(201).json({
message: 'Tenant created',
obj: tenant
});
return(tenant._id);
newTenant = tenant;
});
Tenant.findById(newTenant._id, function(err, tenant) {
if (err) {
return res.status(500).json({
title:'An error occured',
error: err
});
}
var user = new User({
email: req.body.email,
password: bcrypt.hashSync(req.body.password, 10),
active: req.body.active,
tenant: tenant
});
user.save(function (err, user) {
if (err) {
return res.status(500).json({
title: 'An error has occured',
error: err
});
}
res.status(201).json({
message: 'User created',
obj: user
});
});
});
});
module.exports = router;
I'm getting an error: cant set headers after they've been sent.
I think I know where i'm wrong, with returning the tenant info. I think Async is the answer but cant figure out how to implement it. Sorry if this is a stupid question or i'm missing something obvious, I'm super new to this and callbacks are doing my head in.
This is happening because res.status() sets headers as soon as it has fired. You try to do this multiple times both when checking for errors, and then you try to set the status code again in Tenant.findById().
You end up with a flow like:
if (err) set headers
set headers (again)
findById()
if (err) set headers
set headers (again)
You have to be careful when writing out your response that you only do it at the last point in your logic flow. You can also could set up a global err handler and throw new Error() and stop the flow of logic and handle the output immediately. If you don't, your code will continue to execute even though it encountered an error.
Another tip: callbacks don't work well with returns. And although you can arrange them to work, or implement a promise architecture instead, the simplest fix (and the easiest to learn) is to make your functions all asynchronous.
Try instead something like:
tenant.save(function (err, tenant, callback) {
// add a callback param to your fn ^
if (err) {
throw({
code: 500,
title: 'An error has occured',
error: err
});
} else {
// ^ add an else statement so you don't set the headers twice
// (because res.status() sets headers)
res.status(201).json({
message: 'Tenant created',
obj: tenant
});
}
callback(err, tenant);
// call your async function instead of return,
// and pass both err and tenant as params
// (one or the other may be undefined though, if it fails/succeeds)
});
... Create additional isolated functions (or even modules) for the rest of your tasks, then you can then call your function like this:
tenant.save(function(err, tenant) {
Tenant.findById(tenant._id, function(err, tenant) {
var user = new User({...})
user.save()
});
});
Im creating an a API using NodeJS with the express framework and mongodb to store my data.
I have a register function which does 3 main things.
Creates the new.
Creates a token and associates it with a user.
Sends an email.
module.exports.register = function(req, res) {
var input = req.body;
var token = uuid.v4();
// Create a new user
var user = new User ({
username: input.username,
email: input.email,
password: input.password,
active: false
});
user.save(function(err) {
if(err) return res.json({success: false, errors: 'Failed To Create User'});
});
// Create a new Token
var newToken = createToken('new', null, user._id);
// Assign New Token To New User
if(newToken) {
user.tokens.push(newToken._id);
user.save(function(err) {
if(err) return res.json({success: false, errors: 'Failed To Save User Token'});
});
}
// Send Email To User
var mailData = {
from: 'deleted#hotmail.com',
to: input.email,
subject: 'Activate Your Account',
text: 'http://localhost:8080/api/auth/activate/' + token
}
mail.messages().send(mailData, function(err, body) {
if(err) return res.json({ success: false, errors: 'Failed To Send Email' });
});
return res.json({
success: true,
status: 'Successfully Registered User, Check Email To Activate'
});
}
Now even if there are errors whilst creating the user or the token or sending an email. It's always going to return that it successfully registered a user. How can i restructure / handle this better?
I also have the problem where if the email fails to send the user and token will have already been created, how do i solve this issue? Would i just create a resend activation function?
You mention that it's always going to return that it successfully registered a user. It will also send the email even if the token creation failed.
One (not very pretty) way to do it would be to continue with the next step inside the callback function of the previous step:
user.save(function(err) {
if(err) {
return res.json({success: false, errors: 'Failed To Create User'});
} else {
// Create a new Token
var newToken = createToken('new', null, user._id);
// Assign New Token To New User
if(newToken) {
user.tokens.push(newToken._id);
user.save(function(err) {
if(err) {
return res.json({success: false, errors: 'Failed To Save User Token'});
} else {
// Send Email To User
var mailData = {
from: 'deleted#example.com',
to: input.email,
subject: 'Activate Your Account',
text: 'http://localhost:8080/api/auth/activate/' + token
}
mail.messages().send(mailData, function(err, body) {
if(err) {
return res.json({ success: false, errors: 'Failed To Send Email' });
} else {
return res.json({
success: true,
status: 'Successfully Registered User, Check Email To Activate'
});
}
});
}
});
}
}
});
As you can see, it looks like a callback-piramyd-of-doom very fast, but it only sends the success response when all the previous steps have completed.
You should also add the else case when the newToken is not created.
You should remove final return statement (from the end of your code) and return at the correct place inside each callback if there is no error.
If you send your response in the body of the function your callbacks will never get the chance to run. Therefore you must nest your callbacks and only call res.send if you are
returning early due to an error or
if everything is complete.
e.g.
// Create a new user
var user = new User ({
username: input.username,
email: input.email,
password: input.password,
active: false
});
user.save(function(err) {
if(err) return res.json({success: false, errors: 'Failed To Create User'});
// Create a new Token
var newToken = createToken('new', null, user._id);
// Assign New Token To New User
if(newToken) {
user.tokens.push(newToken._id);
user.save(function(err) {
if(err) return res.json({success: false, errors: 'Failed To Save User Token'});
// Send Email To User
var mailData = {
from: 'deleted#hotmail.com',
to: input.email,
subject: 'Activate Your Account',
text: 'http://localhost:8080/api/auth/activate/' + token
}
mail.messages().send(mailData, function(err, body) {
if(err) return res.json({ success: false, errors: 'Failed To Send Email' });
return res.json({
success: true,
status: 'Successfully Registered User, Check Email To Activate'
});
});
});
}
});
Asynchronous alternatives
Unfortunately, with node.js you should get used to and understand callbacks; even if you end up using something else most of the time. The way your code was structured was neater and logical but does not work in node.js because you have to wait for the callbacks to complete before you can return from your function.
However, callbacks are the default but one of the worst mechanisms for handling asynchronous logic. If you want to structure the code differently you have quite a few options. Here are just a couple:
Use promises instead of callbacks.
In your case your database library (mongoose? sequelize?) should have something built in that allows you to write your code like this:
user.save()
.then(function () {
// step 1
})
.then(funciton () {
// step 2
})
.done()
This style of programming is well worth learning and will make your code more readable than callbacks. callbacks vs promises
Use Koa instead of express.
Koa, is the next generation of express written by the same people. It uses generators instead of callbacks which means you can write code that looks more like this:
// this is just an example
var result = user.save();
if (result.error) return res.send({success : false, ...});
user.token = getNewToken();
user.update();
if (result.error) return res.send({success : false, ...});
return res.send({success : true, message : "Good news, no errors"});
Generators/(aka async functions) are the direction Javascript is moving in but there is a learning curve to start using. Behind the scenes there is something very complex going on to make asynchronous code appear exactly like synchronous code. Basically, the functions know how to pause execution until they are required again.
Start with callbacks
Like I say, callbacks are not that nice. However, you should get used to using them. They are the basic building blocks of node.js and it take a while to get comfortable with better alternatives. It's also important to get used to them because otherwise you won't appreciate why the alternatives are better.
Good luck and watch out for callbacks inside loops :)