I am trying to create a book tracker with the MERN stack. I want to save some information in session, that is, when user tries to register, he hits a route, where validation happens and an email is sent to check if email really exists. Now the username, email, password and a verification code, are saved in session (which is actually not working), so that the verify route can access it, and save the user to database is the verification code entered by the user is correct which was sent to the email. Now saving of the information is not working, here is my code:
app.js file (entry point)
import dotenv from "dotenv";
dotenv.config();
import express from "express";
import bodyParser from "body-parser";
import cors from "cors";
import mongoose from "mongoose";
import session from "express-session";
// import routes
import authRoutes from "./routes/auth.js";
// db config
mongoose
.connect(process.env.DB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
})
.then(() => console.log("DB Connected"))
.catch((err) => console.log(err));
// constants
const app = express();
const port = process.env.PORT || 5001;
// middlewares
app.use(bodyParser.json());
app.use(cors());
app.use(
session({
secret: process.env.SECRET,
resave: true,
saveUninitialized: true,
expires: new Date(Date.now() + 3600000),
proxy: true,
})
);
// using routes
app.use("/api", authRoutes);
app.listen(port, () =>
console.log(`Server is running on http://localhost:${port}/`)
);
routes/auth.js (auth related routes)
import dotenv from "dotenv";
dotenv.config();
import express from "express";
import securePin from "secure-pin";
import { User } from "../models/user.js";
import { transporter } from "../config/nodemailer.js";
const router = express.Router();
function validateEmail(email) {
return /^\w+([\.-]?\w+)*#\w+([\.-]?\w+)*(\.\w{2,3})+$/.test(email);
}
router.post("/register", (req, res) => {
const { username, email, password } = req.body;
//check email matches the pattern
if (!validateEmail(email)) {
return res.json({
status: "error",
message: "Email is not valid",
});
}
// check if email already exists
User.findOne({ email: email }, (err, user) => {
if (user) {
return res.json({
status: "error",
message: "Email already exists, please sign in",
});
}
});
// check username is taken
User.findOne({ username: username }, (err, user) => {
if (user) {
return res.json({
status: "error",
message: "Username already exists, please choose another one",
});
}
});
// check username is atleast 4 characters
if (username.length < 4) {
return res.json({
status: "error",
message: "Username must be atleast 4 characters",
});
}
// check if username contains invalid characters
if (username.match(/[^a-zA-Z0-9_]/)) {
return res.json({
status: "error",
message: "Username must not contain special characters or spaces",
});
}
// check password contains spaces
if (password.match(/\s/)) {
return res.json({
status: "error",
message: "Password must not contain spaces",
});
}
// check password is atleast 6 characters
if (password.length < 6) {
return res.json({
status: "error",
message: "Password must be atleast 6 characters",
});
}
// generate unique token
var charSet = new securePin.CharSet();
charSet.addLowerCaseAlpha().addUpperCaseAlpha().addNumeric().randomize();
const code = securePin.generateStringSync(6, charSet);
// save info in session
req.session.username = username;
req.session.email = email;
req.session.password = password;
req.session.code = code;
req.session.save();
// send token to user's email
const mailOptions = {
from: process.env.EMAIL_ADDRESS,
to: email,
subject: "no reply- book tracker confirmation",
text: `Your verification code is: ${code}.`,
};
transporter.sendMail(mailOptions, (err, info) => {
if (err) {
return res.json({
status: "error",
message: err.message.toString(),
});
}
return res.json({
status: "success",
message: "Email sent",
});
});
});
router.post("/register/verify", (req, res) => {
const { code } = req.body;
const { username, email, password } = req.session;
// check if code matches
if (code !== req.session.code) {
return res.json({
status: "error",
message: "Code is invalid",
});
}
// create user
const user = new User({
username,
email,
password,
});
// save user
// user.save((err) => {
// if (err) {
// return res.json({
// status: "error",
// message: err.message,
// });
// }
// return res.json({
// status: "success",
// message: "Account created successfully!",
// });
// });
console.log(user);
return res.json(user);
});
export default router;
Related
cookies just keeps adding on and on when I register a new user.
userRoutes.js:
const { registerUser, loginUser, registerVerify } = require("./userController");
const express=require('express')
const router=express.Router()
router
.route('/register')
.post(registerUser)
module.exports=router
userController.js:
const User = require("./userModel");
const validator = require("validator");
const jwt=require('jsonwebtoken')
const sendToken = function (email) {
let data = {
expiresIn: process.env.JWT_EXPIRE_SHORT,
email,
};
let token = jwt.sign(data, process.env.JWT_SECRET_KEY);
return token;
};
exports.registerUser = async (req, res, next) => {
try {
const { userName, email, password, confirmPassword } = req.body;
let user = [
{ userName, email, password, confirmPassword, createdAt: Date.now() },
];
//passwords do not match error
if (password !== confirmPassword) {
throw "Password doesn't match Confirm Password.";
}
//email not valid error
if (!validator.isEmail(email)) {
throw "Email not valid.";
}
//user already logged in error
if (await User.findOne({ email })) {
throw "User already exists. Please login";
}
//create user
await User.create(user);
//assign token
let token = sendToken(email);
//clear cookie
res.status(200).clearCookie('token').cookie(token, 'token', {maxAge: 360000}).json({
message: "Please fill the required text send via email",
token,
});
} catch (err) {
// console.log(err)
res.status(400).json({
message: "Signup Not Successfull",
err,
});
}
};
I've tried:
router
.route('/register',{ credentials: 'same-origin'})
.post(registerUser)
router
.route('/register',{ credentials: 'include'})
.post(registerUser)
router
.route('/register',{ withCredentials: true})
.post(registerUser)
Here is the correct way for creating a cookie:
res.cookie('token', token, {maxAge: 360000});
Note: You don't need to clear and create a new one.
when you send a cookie with the same key it overrides itself.
const router = require("express").Router();
const mongoose = require("mongoose");
const User = require("../models/Users");
const bcrypt = require("bcrypt");
const jwt = require("jsonwebtoken");
// Route 1: create new user at /api/createuser
router.post("/createuser", async (req, res) => {
try {
console.log(req.body);
const salt = await bcrypt.genSaltSync(10);
const hash = await bcrypt.hashSync(req.body.password, salt);
password = hash;
const user = new User({
name: req.body.name,
email: req.body.email,
password: password,
});
user
.save()
.then(() => {
res.json({ message: "User created successfully" });
})
.catch((err) => {
res.json({ message: "Error: " + err });
});
console.log(password);
} catch (err) {
res.json({ message: err });
}
});
// Route 2: Login user at /api/login
router.post("/login", async (req, res) => {
try {
console.log("login endpoint triggered");
const user = await User.findOne({ email: req.body.email });
if (!user) {
res.json({ message: "User does not exist" });
}
const passwordIsValid = await bcrypt.compare(
req.body.password,
user.password
);
if (!passwordIsValid) {
res.json({ message: "Invalid password" });
} else {
const data = {
id: user._id,
};
const token = await jwt.sign(data, process.env.SECRET);
res.json(token);
}
} catch (error) {
res.json({ message: error });
}
});
module.exports = router;
Whenever I am testing the login endpoint, my app crashes if i try to put incorrect password or unregistered email.
It says Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
I have only sent one response to the client even then it is showing this error.
In the terminal, it is showing error at catch block of login endpoint.
Can anyone look into it and tell me why am i getting this error.
Make sure that when the response is an error, the rest of the middleware is not executed any more. For example, put a return before the res.json statement:
if (!user) {
return res.json({ message: "User does not exist" });
}
Otherwise, the "Invalid password" json might get issued after this one, but two res.jsons in one response lead to the observed error.
I´m trying to set up a register and login server with node.js and mongoose. So I have create an user model and an user route. Can someone find the mistake why I can´t create an user. I connect to Postman under POST : localhost:3000/users/register
my user model:
const mongoose = require('mongoose');
const uniqueValidator = require('mongoose-unique-validator');
const bcrypt = require('bcryptjs');
const UserSchema = new mongoose.Schema({
email: {
type: String,
required: true,
minlength: 1,
trim: true, //calls .trim() on the value to get rid of whitespace
unique: true, //note that the unique option is not a validator; we use mongoose-unique-validator to enforce it
},
password: {
type: String,
required: true,
minlength: 8,
},
});
//this enforces emails to be unique!
UserSchema.plugin(uniqueValidator);
//this function will be called before a document is saved
UserSchema.pre('save', function(next) {
let user = this;
if (!user.isModified('password')) {
return next();
}
//we generate the salt using 12 rounds and then use that salt with the received password string to generate our hash
bcrypt
.genSalt(12)
.then((salt) => {
return bcrypt.hash(user.password, salt);
})
.then((hash) => {
user.password = hash;
next();
})
.catch((err) => next(err));
});
module.exports = mongoose.model('User', UserSchema);
my routes user:
const express = require('express');
const bcrypt = require('bcryptjs');
const User = require('../models/user');
const router = express.Router();
//util function to check if a string is a valid email address
const isEmail = (email) => {
if (typeof email !== 'string') {
return false;
}
const emailRegex = /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")#(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/;
return emailRegex.test(email);
};
router.post('/register', async (req, res) => {
try {
const { email, password } = req.body;
if (!isEmail(email)) {
throw new Error('Email must be a valid email address.');
}
if (typeof password !== 'string') {
throw new Error('Password must be a string.');
}
const user = new User({ email, password });
const persistedUser = await user.save();
res.status(201).json({
title: 'User Registration Successful',
detail: 'Successfully registered new user',
});
} catch (err) {
res.status(400).json({
errors: [
{
title: 'Registration Error',
detail: 'Something went wrong during registration process.',
errorMessage: err.message,
},
],
});
}
});
router.post('/login', async (req, res) => {
try {
const { email, password } = req.body;
if (!isEmail(email)) {
return res.status(400).json({
errors: [
{
title: 'Bad Request',
detail: 'Email must be a valid email address',
},
],
});
}
if (typeof password !== 'string') {
return res.status(400).json({
errors: [
{
title: 'Bad Request',
detail: 'Password must be a string',
},
],
});
}
//queries database to find a user with the received email
const user = await User.findOne({ email });
if (!user) {
throw new Error();
}
//using bcrypt to compare passwords
const passwordValidated = await bcrypt.compare(password, user.password);
if (!passwordValidated) {
throw new Error();
}
res.json({
title: 'Login Successful',
detail: 'Successfully validated user credentials',
});
} catch (err) {
res.status(401).json({
errors: [
{
title: 'Invalid Credentials',
detail: 'Check email and password combination',
errorMessage: err.message,
},
],
});
}
});
module.exports = router;
my server :
const express = require('express');
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser');
const mongoose = require('mongoose');
const dotenv = require("dotenv");
const app = express();
dotenv.config();
//other imports
const usersRoute = require('./routes/users');
//other app.use statements
//connect to db
mongoose.connect(
process.env.DB_CONNECTION,
{ useNewUrlParser: true,
useUnifiedTopology: true},
() => console.log('Database connected')
);
mongoose.Promise = global.Promise;
const port = process.env.PORT || 3000;
//sets up the middleware for parsing the bodies and cookies off of the requests
app.use(bodyParser.json());
app.use(cookieParser());
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
module.exports = { app };
what I only get is this.:
Cannot POST /users/register
You didn't specify the path prefix in your server file. You should define:
app.use("/users", usersRoute );
I was following a tutorial at Authentication in NodeJS With Express and Mongo - CodeLab #1
I got everything to work perfectly, but the tutorial does not address how to log out a user.
From what I can tell, the session is being saved on Mongoose Atlas, which is the database I am using. When I log a user in with Postman, I get a token back. But I am not sure how to configure the /logout route.
Here is my code:
//routes/user.js
const express = require("express");
const { check, validationResult } = require("express-validator");
const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken");
const router = express.Router();
const auth = require("../middleware/auth");
const User = require("../models/User");
/**
* #method - POST
* #param - /signup
* #description - User SignUp
*/
//Signup
router.post(
"/signup",
[
check("username", "Please Enter a Valid Username")
.not()
.isEmpty(),
check("email", "Please enter a valid email").isEmail(),
check("password", "Please enter a valid password").isLength({
min: 6
})
],
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
errors: errors.array()
});
}
const {
username,
email,
password
} = req.body;
try {
let user = await User.findOne({
email
});
if (user) {
return res.status(400).json({
msg: "User Already Exists"
});
}
user = new User({
username,
email,
password
});
const salt = await bcrypt.genSalt(10);
user.password = await bcrypt.hash(password, salt);
await user.save();
const payload = {
user: {
id: user.id
}
};
jwt.sign(
payload,
"randomString", {
expiresIn: 10000
},
(err, token) => {
if (err) throw err;
res.status(200).json({
token
});
}
);
} catch (err) {
console.log(err.message);
res.status(500).send("Error in Saving");
}
}
);
// Login
router.post(
"/login",
[
check("email", "Please enter a valid email").isEmail(),
check("password", "Please enter a valid password").isLength({
min: 6
})
],
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
errors: errors.array()
});
}
const { email, password } = req.body;
try {
let user = await User.findOne({
email
});
if (!user)
return res.status(400).json({
message: "User Not Exist"
});
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch)
return res.status(400).json({
message: "Incorrect Password !"
});
const payload = {
user: {
id: user.id
}
};
jwt.sign(
payload,
"randomString",
{
expiresIn: 3600
},
(err, token) => {
if (err) throw err;
res.status(200).json({
token
});
}
);
} catch (e) {
console.error(e);
res.status(500).json({
message: "Server Error"
});
}
}
);
// router.route("/logout").get(function (req, res, next) {
// if (expire(req.headers)) {
// delete req.user;
// return res.status(200).json({
// "message": "User has been successfully logged out"
// });
// } else {
// return next(new UnauthorizedAccessError("401"));
// }
// });
router.get("/me", auth, async (req, res) => {
try {
// request.user is getting fetched from Middleware after token authentication
const user = await User.findById(req.user.id);
res.json(user);
} catch (e) {
res.send({ message: "Error in Fetching user" });
}
});
router.get('/logout', isAuthenticated, function (req, res) {
console.log('User Id', req.user._id);
User.findByIdAndRemove(req.user._id, function (err) {
if (err) res.send(err);
res.json({ message: 'User Deleted!' });
})
});
module.exports = router;
function isAuthenticated(req, res, next) {
console.log("req: " + JSON.stringify(req.headers.authorization));
// if (!(req.headers && req.headers.authorization)) {
// return res.status(400).send({ message: 'You did not provide a JSON web token in the authorization header' });
//}
};
///middleware/auth.js
const jwt = require("jsonwebtoken");
module.exports = function (req, res, next) {
const token = req.header("token");
if (!token) return res.status(401).json({ message: "Auth Error" });
try {
const decoded = jwt.verify(token, "randomString");
req.user = decoded.user;
next();
} catch (e) {
console.error(e);
res.status(500).send({ message: "Invalid Token" });
}
};
///models/User.js
const mongoose = require("mongoose");
const UserSchema = mongoose.Schema({
username: {
type: String,
required: true
},
email: {
type: String,
required: true
},
password: {
type: String,
required: true
},
createdAt: {
type: Date,
default: Date.now()
}
});
// export model user with UserSchema
module.exports = mongoose.model("user", UserSchema);
So my question is, how can I implement a /logout route so that if the user clicks the logout button and invokes that route, their token is destroyed. I am only asking about the back-end part. I can handle using axios.
Thanks.
From what I see, you are not saving any session data or storing tokens anywhere - which is great. You are simply appending the token to your headers in requests to the API.
So the only thing you can do is possibly expire the token in the /logout route
and then ensure you delete the token on the client - could be localStorage, sessionStorage etc - your client code needs to kill the token so it cannot be included again.
Side note:
You are not extending the token lifetime anywhere, so even if the user keeps interacting on the website, the token expiration is not being updated. You will need to manually refresh the token/generate a new token to have a sliding expiration.
I would suggest you save the token in cookies rather. Set the cookie to HttpOnly, Secure, and specify the domain. This is far more secure and will allow you to also expire the cookie from the API. If any scripts you include get compromised, they can access all your users’ tokens easily.
Example:
import {serialize} from 'cookie';
import jsend from 'jsend';
...
const token = jwt.sign(
{
id: validationResult.value.id // whatever you want to add to the token, here it is the id of a user
},
privateKeyBuffer,
{
expiresIn: process.env.token_ttl,
algorithm: 'RS256'
});
const cookieOptions = {
httpOnly: true,
path: '/',
maxAge: process.env.token_ttl,
expires: new Date(Date.now() + process.env.token_ttl),
sameSite: process.env.cookie_samesite, // strict
domain: process.env.cookie_domain, // your domain
secure: process.env.cookie_secure // true
};
const tokenCookie = await serialize('token', token, cookieOptions);
res.setHeader('Set-Cookie', [tokenCookie]);
res.setHeader('Content-Type', 'application/json');
res.status(200).json(jsend.success(true));
Then in logout:
// grab from req.cookies.token and validate
const token = await extractToken(req);
// you can take action if it's invalid, but not really important
if(!token) {
...
}
// this is how we expire it - the options here must match the options you created with!
const cookieOptions = {
httpOnly: true,
path: '/',
maxAge: 0,
expires: 0,
sameSite: process.env.cookie_samesite, // strict
domain: process.env.cookie_domain, // your domain
secure: process.env.cookie_secure // true
};
// set to empty
const tokenCookie = await serialize('token', '', cookieOptions);
res.setHeader('Set-Cookie', [tokenCookie]);
res.setHeader('Content-Type', 'application/json');
res.status(200).json(jsend.success(true));
As you have used JWT the backend will always check 2 things
1. Proper token
2. If time is over for that particular (You should handle this one)
For 2nd point, if the user time is up than from frontend you may delete the token if you have stored the token in localstorage.
For logout when user clicks on logout just delete jwt from localstorage and redirect to login or other page
This question already has answers here:
E11000 duplicate key error index in mongodb mongoose
(22 answers)
Closed 3 years ago.
any time i try to register a user it gives me this error
I checked the db collection and no such duplicate entry exists, let me know what I am doing wrong ?
FYI - req.body.email and req.body.password are fetching values.
I also checked this post but no help STACK LINK
If I removed completely then it inserts the document, otherwise it throws error "Duplicate" error even I have an entry in the local.email
Server started on port 5000
MongoDB Connected
MongoError: E11000 duplicate key error collection: test.users index: email1_1 dup key: { email1: null }
{ driver: true,
name: 'MongoError',
index: 0,
code: 11000,
keyPattern: { email1: 1 },
keyValue: { email1: null },
errmsg: 'E11000 duplicate key error collection: test.users index: email1_1 dup key: { email1: null }',
[Symbol(mongoErrorContextSymbol)]: {}
}
Following is my user schema in user.js model
Schema
const mongoose = require('mongoose');
const UserSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
email: {type: String, unique: true, required: true
},
resetPasswordToken: String,
resetPasswordExpires: Date,
password: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
}
});
const User = mongoose.model('User', UserSchema);
module.exports = User;
Route
const express = require('express');
const router = express.Router();
const bcrypt = require('bcryptjs');
const passport = require('passport');
const async = require("async");
const nodemailer = require("nodemailer");
const crypto = require("crypto");
// Load User model
const User = require('../models/User');
const { forwardAuthenticated } = require('../config/auth');
// Login Page
router.get('/login', forwardAuthenticated, (req, res) => res.render('login'));
// Register Page
router.get('/register', forwardAuthenticated, (req, res) => res.render('register'));
// Register
router.post('/register', (req, res) => {
const { name, email, password, password2 } = req.body;
let errors = [];
if (!name || !email || !password || !password2) {
errors.push({ msg: 'Please enter all fields' });
}
if (password != password2) {
errors.push({ msg: 'Passwords do not match' });
}
if (password.length < 6) {
errors.push({ msg: 'Password must be at least 6 characters' });
}
if (errors.length > 0) {
res.render('register', {
errors,
name,
email,
password,
password2
});
} else {
User.findOne({ email: email }).then(user => {
if (user) {
errors.push({ msg: 'Email already exists' });
res.render('register', {
errors,
name,
email,
password,
password2
});
} else {
const newUser = new User({
name,
email,
password
});
bcrypt.genSalt(10, (err, salt) => {
bcrypt.hash(newUser.password, salt, (err, hash) => {
if (err) throw err;
newUser.password = hash;
newUser
.save()
.then(user => {
req.flash(
'success_msg',
'You are now registered and can log in'
);
res.redirect('/users/login');
})
.catch(err => console.log(err));
});
});
}
});
}
});
// Login
router.post('/login', (req, res, next) => {
passport.authenticate('local', {
successRedirect: '/dashboard',
failureRedirect: '/users/login',
failureFlash: true
})(req, res, next);
});
// Logout
router.get('/logout', (req, res) => {
req.logout();
req.flash('success_msg', 'You are logged out');
res.redirect('/users/login');
});
module.exports = router;
The thing is that, as I see from the error message, it seems like you have one entity in the DB which has no email(basically email = null). And because you've specified email field as unique, mongoDB thinks that not having an email is unique, so there can only be one entity with no email field in the collection. And you're trying to add another entity with no email, and eventually, you have an error, because this entity also has no email as a record in the DB.
You just need to verify if email is present, before sending it to DB, or implement other solution that fits for your business logic.
Hope it helps :)