I am trying to create e registration for a new user with profile picture upload. But my form data is not passing to the route. I have added body-parser middleware but still, it seems something is wrong and I cannot find the reason.
Error:
D:\temp_project\smpms\routes\users.js:118
if (err) throw err;
^
Error: Illegal arguments: undefined, string
at _async (D:\temp_project\smpms\node_modules\bcryptjs\dist\bcrypt.js:214:46)
at Object.bcrypt.hash (D:\temp_project\smpms\node_modules\bcryptjs\dist\bcrypt.js:220:13)
at D:\temp_project\smpms\routes\users.js:117:28
at Immediate.<anonymous> (D:\temp_project\smpms\node_modules\bcryptjs\dist\bcrypt.js:153:21)
at processImmediate (internal/timers.js:456:21)
[nodemon] app crashed - waiting for file changes before starting...
app.js
const express = require("express");
const expressLayouts = require("express-ejs-layouts");
const mongoose = require("mongoose");
const passport = require("passport");
const flash = require("connect-flash");
const session = require("express-session");
const multer = require('multer');
const path = require('path');
var dotenv = require('dotenv').config();
const bodyParser = require('body-parser');
const app = express();
// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }))
// parse application/json
app.use(bodyParser.json())
app.use(express.static("public"));
// Passport Config
require("./config/passport")(passport);
// DB Config
const db = require("./config/keys").mongoURI;
// Connect to MongoDB
mongoose
.connect(db, { useNewUrlParser: true, useUnifiedTopology: true })
.then(() => console.log("MongoDB Connected"))
.catch((err) => console.log(err));
// EJS
app.use(expressLayouts);
app.set("view engine", "ejs");
// Express body parser
app.use(express.urlencoded({ extended: true }));
// Express session
app.use(
session({
secret: "secret",
resave: true,
saveUninitialized: true,
})
);
// Passport middleware
app.use(passport.initialize());
app.use(passport.session());
// Connect flash
app.use(flash());
// Global variables
app.use(function (req, res, next) {
res.locals.success_msg = req.flash("success_msg");
res.locals.error_msg = req.flash("error_msg");
res.locals.error = req.flash("error");
next();
});
// Routes
app.use("/", require("./routes/index.js"));
app.use("/users", require("./routes/users.js"));
const PORT = process.env.PORT || 5000;
app.listen(PORT, console.log(`Server started on port ${PORT}`));
User.js - User model
const mongoose = require('mongoose');
const UserSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true
},
password: {
type: String,
required: true
},
avatar:{
type:String,
required:true
},
date: {
type: Date,
default: Date.now
}
});
const User = mongoose.model('User', UserSchema);
module.exports = User;
User.js route:
const express = require("express");
const router = express.Router();
const bcrypt = require("bcryptjs");
const passport = require("passport");
const multer = require('multer');
const path = require('path');
const bodyParser = require('body-parser')
const app = express()
// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }))
// parse application/json
app.use(bodyParser.json())
// Load User model
const User = require("../models/User");
const {forwardAuthenticated} = require("../config/auth");
// Login Page
router.get("/login", forwardAuthenticated, (req, res) => {
res.render("login", {title: "Login", layout: "layout"});
});
// Register Page
router.get("/register", forwardAuthenticated, (req, res) => {
res.render("register", {title: "Register", layout: "layout"});
});
// Register
router.post("/register", (req, res) => {
//const {name, email, password, password2||avatar} = req.body;
const name = req.body.name
const email = req.body.email
const password = req.body.password;
const password2 = req.body.password2;
const avatar = req.body.avatar;
let errors = [];
//
// if (!name || !email || !password || !password2) {
// errors.push({msg: "Please enter all fields"});
// }
// if (password && password.length < 6) {
// errors.push({msg: "Password must be at least 6 characters"});
// }
// if (password != password2) {
// errors.push({msg: "Passwords do not match"});
// }
if (errors.length > 0) {
res.render("register", {
errors,
name,
email,
password,
password2,
title: "Register",
layout: "Layout",
});
} else {
User.findOne({email: email}).then((user) => {
if (user) {
errors.push({msg: "Email already exists"});
res.render("register", {
errors,
name,
email,
password,
password2,
title: "Register",
layout: "Layout",
});
} else {
const newUser = new User({
name,
email,
password,
});
//Set The Storage Engine
const storage = multer.diskStorage({
destination: './public/uploads/',
filename: function (req, file, cb) {
cb(null, file.fieldname + '-' + newUser._id + path.extname(file.originalname));
}
});
// Init Upload
const upload = multer({
storage: storage,
limits: {fileSize: 1000000},
fileFilter: function (req, file, cb) {
checkFileType(file, cb);
}
}).single('avatar');
// Check File Type
function checkFileType(file, cb) {
// Allowed ext
const filetypes = /jpeg|jpg|png|gif/;
// Check ext
const extname = filetypes.test(path.extname(file.originalname).toLowerCase());
// Check mime
const mimetype = filetypes.test(file.mimetype);
if (mimetype && extname) {
return cb(null, true);
} else {
cb('Error: Images Only!');
}
}
console.log(newUser);
newUser.avatar = storage;
console.log(newUser);
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;
Register.ejs is the file where the form is not passing data to the route.
<div class="row mt-5">
<div class="col-md-6 m-auto">
<div class="card card-body">
<h1 class="text-center mb-3">
<i class="fas fa-user-plus"></i> Register
</h1>
<% include ./partials/messages %>
<%= typeof msg != 'undefined' ? msg : '' %>
<form action="/users/register" method="POST" enctype="multipart/form-data">
<div class="form-group">
<label for="name">Name</label>
<input
type="name"
id="name"
name="name"
class="form-control"
placeholder="Enter Name"
value="<%= typeof name != 'undefined' ? name : '' %>"
/>
</div>
<div class="form-group">
<label for="email">Email</label>
<input
type="email"
id="email"
name="email"
class="form-control"
placeholder="Enter Email"
value="<%= typeof email != 'undefined' ? email : '' %>"
/>
</div>
<div class="form-group">
<label for="password">Password</label>
<input
type="password"
id="password"
name="password"
class="form-control"
placeholder="Create Password"
value="<%= typeof password != 'undefined' ? password : '' %>"
/>
</div>
<div class="form-group">
<label for="password2">Confirm Password</label>
<input
type="password"
id="password2"
name="password2"
class="form-control"
placeholder="Confirm Password"
value="<%= typeof password2 != 'undefined' ? password2 : '' %>"
/>
</div>
<div class="form-group">
<label for="">Profile Picture</label>
<input type="file" name="avatar" id="" class="form-control file-path validate">
</div>
<button type="submit" class="btn btn-primary btn-block">
Register
</button>
</form>
<p class="lead mt-4">Have An Account? Login</p>
</div>
</div>
</div>
It seems that you aren't using multer correctly. Usually multer configuration is set globally and above all the APIs and then we pass that middleware function generated by multer to that specific API which has multipart/form-data header in its request. You have to move your configuration for multer, outside of your API, then pass the upload as a middleware to your API. Also from your clientside, you have to pass data properly. A good answer about sending JSON alongside the File with FormData object can be found here. Also you can add text fields in FormData object from clientside and receive it as object in req.body according to multer documentation. Just make sure you're passing data from clientside to server correctly. Here is your router with multer middleware:
const express = require("express");
const router = express.Router();
const fs = require('fs');
const bcrypt = require("bcryptjs");
const passport = require("passport");
const multer = require('multer');
const path = require('path');
const bodyParser = require('body-parser')
const app = express()
// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }))
// parse application/json
app.use(bodyParser.json())
// Load User model
const User = require("../models/User");
const { forwardAuthenticated } = require("../config/auth");
// Login Page
router.get("/login", forwardAuthenticated, (req, res) => {
res.render("login", { title: "Login", layout: "layout" });
});
// Register Page
router.get("/register", forwardAuthenticated, (req, res) => {
res.render("register", { title: "Register", layout: "layout" });
});
const storage = multer.diskStorage({
destination: './public/uploads/'
});
// Init Upload
const upload = multer({
storage: storage,
limits: { fileSize: 1000000 },
fileFilter: function (req, file, cb) {
checkFileType(file, cb);
}
});
// Check File Type
function checkFileType(file, cb) {
// Allowed ext
const filetypes = /jpeg|jpg|png|gif/;
// Check ext
const extname = filetypes.test(path.extname(file.originalname).toLowerCase());
// Check mime
const mimetype = filetypes.test(file.mimetype);
if (mimetype && extname) {
return cb(null, true);
} else {
cb('Error: Images Only!');
}
}
// Register
router.post("/register", upload.single('avatar'), (req, res) => {
//const {name, email, password, password2||avatar} = req.body;
const name = req.body.name
const email = req.body.email
const password = req.body.password;
const password2 = req.body.password2;
let errors = [];
//
// if (!name || !email || !password || !password2) {
// errors.push({msg: "Please enter all fields"});
// }
// if (password && password.length < 6) {
// errors.push({msg: "Password must be at least 6 characters"});
// }
// if (password != password2) {
// errors.push({msg: "Passwords do not match"});
// }
if (errors.length > 0) {
res.render("register", {
errors,
name,
email,
password,
password2,
title: "Register",
layout: "Layout",
});
} else {
User.findOne({ email: email }).then((user) => {
if (user) {
errors.push({ msg: "Email already exists" });
res.render("register", {
errors,
name,
email,
password,
password2,
title: "Register",
layout: "Layout",
});
} else {
const directory = "/images/";
const newUser = new User({
name,
email,
password,
avatar: `./public/uploads/${req.file}`
});
const filePath = path.join(__dirname, "../public/uploads/");
fs.rename(filePath + req.file.filename, req.file.fieldname + '-' + newUser._id + path.extname(req.file.originalname), (error) => {
if (error) {
return console.log(`Error: ${error}`);
}
});
newUser.avatar = 'public/uploads/' + req.file.fieldname + '-' + newUser._id + path.extname(req.file.originalname);
console.log(newUser);
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;
It will save your file without any problem (if your configuration about directory and filename was okay).
Related
I've created a simple RESTapi with express and for file upload I'm using multer. Creating a new item / post works. My issue is with the put route and the function to handle saving and updating the post/gear.
When the product image field is left blank, it returns
undefined [Object: null prototype] {
title: 'joey',
price: '123',
weight: '123',
description: 'lorem'
}
(node:5293) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'path' of undefined
at /Users/Zack/Desktop/LeFIAT/lefiatV2/routes/gear.js:81:34
When all the fields in the product fields are filled out returns a duplicate key
MongoServerError: E11000 duplicate key error collection: lefiatv2.products index: slug_1 dup key: { slug: "joey" }
at /Users/Zack/Desktop/LeFIAT/lefiatV2/node_modules/mongodb/lib/operations/update.js:80:33
The object is being populated I just can't seem to find the correct path or set up for the function
{
fieldname: 'image',
originalname: 'TorridV2_Toggle__.jpg',
encoding: '7bit',
mimetype: 'image/jpeg',
destination: './public/products/',
filename: '2022-02-02T15:44:30.813ZTorridV2_Toggle__.jpg',
path: 'public/products/2022-02-02T15:44:30.813ZTorridV2_Toggle__.jpg',
size: 23382
} [Object: null prototype] {
title: 'joey',
price: '123',
weight: '123',
description: 'lorem'
}
edit.ejs
Edit Product
<form action="/gear/<%= product.slug %>?_method=PUT" method="POST" enctype="multipart/form-data">
<%- include('../partials/product_fields') %>
</form>
product_fields.ejs
<div class="form-group">
<label for="title">Title</label>
<input type="text" name="title" id="title" class="form-control" value="<%= product.title %>" required>
</div>
<div class="form-group">
<label for="price">Price</label>
<input type="number" min="1" step="0.01" name="price" id="price" class="form-control"
required><%= product.price %></input>
</div>
<div class="form-group">
<label for="weight">Weight</label>
<input type="number" min="1" step="0.01" name="weight" id="weight" class="form-control"
required><%= product.weight %></input>
</div>
<div class="form-group">
<label for="description">Description</label>
<textarea name="description" id="description" class="form-control" required><%= product.description %></textarea>
</div>
<div class="form-group">
<label for="image">Upload Images</label>
<input type="file" name="image" id="image" class="form-control-file" multiple>
</div>
Cancel
<button type="submit" class="btn btn-primary"> Save </button>
product.js modal
const { date } = require('joi')
const mongoose = require('mongoose')
const slugify = require('slugify')
const productSchema = new mongoose.Schema({
title: {
type: String,
required: true,
},
description: {
type: String,
required: true,
},
price: {
type: Number,
required: true
},
weight: {
type: Number,
required: true,
},
slug: {
type: String,
required: true,
unique: true
},
createdAt: {
type: Date,
default:Date.now
},
image: {
type: String,
data: Buffer
}
})
productSchema.pre('validate', function(next) {
if (this.title) {
this.slug = slugify(this.title, { lower: true, strict: true })
}
next()
})
module.exports = mongoose.model('Product', productSchema)
gear.js route
const express = require('express')
const multer = require('multer')
const Product = require('./../models/product')
const router = express.Router()
const storage = multer.diskStorage({
// destination for file
destination: function (req, file, callback) {
callback(null, './public/products/')
},
// add back the extension
filename: function (req, file, callback) {
callback(null, new Date().toISOString() + file.originalname)
},
})
const fileFilter = (req, file, cb) => {
// reject a file
if (file.mimetype === 'image/jpeg' || file.mimetype === 'image/png') {
cb(null, true);
} else {
cb(null, false);
}
};
// upload parameters for multer
const upload = multer({
storage: storage,
limits: {
fileSize: 1024*1024*3,
},
fileFilter: fileFilter
})
router.get('/', async (req, res) => {
const products = await Product.find().sort({ createdAt: 'desc' })
res.render('gear/index', { products: products })
})
router.get('/new', (req, res) => {
res.render('gear/new', { product: new Product() })
})
router.get('/edit/:id', async (req, res) => {
const product = await Product.findById(req.params.id)
res.render('gear/edit', { product: product })
})
router.get('/:slug', async (req, res) => {
const product = await Product.findOne({ slug: req.params.slug })
if(product == null) res.redirect('/gear')
else res.render('gear/show', { product: product })
})
router.post('/', upload.single('image'), async (req, res, next) => {
req.product = new Product()
next()
}, saveProductAndRedirect('new'))
router.put('/:id', upload.single('image'), async (req, res, next) => {
console.log(req.file, req.body)
req.product = await Product.findOneAndUpdate({ slug: req.params.slug })
next()
}, saveProductAndRedirect(`edit`))
router.delete('/:id', async (req, res) => {
await Product.findByIdAndDelete(req.params.id)
res.redirect(`/gear`)
})
function saveProductAndRedirect(path) {
return async (req, res) => {
let product = req.product
product.title = req.body.title
product.description = req.body.description
product.price = req.body.price
product.weight = req.body.weight
product.image = req.file.path
try {
if (! req.file || ! req.file.path) {
return res.sendStatus(400);
}
product = await product.save()
res.redirect(`/gear/${product.slug}`)
} catch(err) {
console.log(err)
res.render(`gear/${path}`, { product: product })
}
}
}
module.exports = router
server.js
if (process.env.NODE_ENV !== 'production') {
require('dotenv').config()
}
const express = require('express')
const path = require('path');
const ejsMate = require('ejs-mate');
const session = require('express-session');
const methodOverride = require('method-override');
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const mongoSanitize = require('express-mongo-sanitize');
const expressLayouts = require('express-ejs-layouts')
const ExpressError = require('./utils/ExpressError');
const indexRouter = require('./routes/index')
const tripsRouter = require('./routes/trips')
const gearRouter = require('./routes/gear')
const aboutRouter = require('./routes/about')
const blogRouter = require('./routes/blog')
const app = express()
app.engine('ejs', ejsMate)
app.set('view engine', 'ejs')
app.set('views', __dirname + '/views')
app.use('/public', express.static(__dirname + '/public'));
app.use(express.urlencoded({ extended: false }));
app.use(methodOverride('_method'));
app.set('layout', 'layouts/layout')
app.use(expressLayouts)
app.use(express.static(path.join(__dirname, 'public')))
app.use(mongoSanitize({
replaceWith: '_'
}))
const MongoDBStore = require('connect-mongo')(session)
const mongoose = require('mongoose')
mongoose.connect(process.env.DATABASE_URL, {
useNewUrlParser: true })
const db = mongoose.connection
db.on('error', error => console.error(error))
db.once('open', () => console.log('Connected to Mongoose'))
const secret = process.env.SECRET || 'thisshouldbeabettersecret!';
const dbUrl = process.env.DATABASE_URL || 'mongodb://localhost:3000/lefiatv2'
const store = new MongoDBStore({
url: dbUrl,
secret,
touchAfter: 24 * 60 * 60
});
store.on('error', function (e) {
console.log('session store error', e)
})
const sessionConfig = {
store,
name: 'session',
secret,
resave: false,
saveUninitialized: true,
cookie: {
httpOnly: true,
secure: true,
expires: Date.now() + 1000 * 60 * 60 * 24 * 7,
maxAge: 1000 * 60 * 60 * 24 * 7
}
}
app.use(session(sessionConfig));
app.use(passport.initialize());
app.use(passport.session());
app.use('/', indexRouter)
app.use('/trips', tripsRouter)
app.use('/gear', gearRouter)
app.use('/about', aboutRouter)
app.use('/blog', blogRouter)
app.use((err, req, res, next) => {
const { statusCode = 500 } = err;
if (!err.message) err.message = 'Oh No, Something Went Wrong!'
res.status(statusCode).render('error', { err })
})
const port = process.env.PORT || 3000;
app.listen(port, () => {
console.log(`Serving on port ${port}`)
})
use optional chaining on your req.file.path so it would be like this
req.file?.path (kinda)
the error is telling you - hey where is the path?, there is no path in undefined - so your result if not passing the file will be
undefined.path
here is some example (i use nestjs):
async updateCommit(
#Res() res,
#Param('user_id') user_id: string,
#Param('repo_id') repo_id: string,
#Param('commited_id') commited_id: string,
#Body() updatedCommit: UpdateCommitDto,
#UploadedFile() file: UpdateCommitDto["file"]): Promise<User> {
Object.assign(updatedCommit, {
file: file?.path,
createdAt: false,
updatedAt: Date.now
})
const response = await this.commitService.updateCommit(user_id, repo_id, commited_id, updatedCommit)
return res.status(HttpStatus.OK).json({ response })}
The result :
Before update
After update
sorry for the image ( low reputation lol )
I have added login and registration features to a page in my application in views/employee/login.hbs. When I enter in registration details (email, username, password and conf password), I hit the register button to hopefully be redirected to the views/employee/home.hbs page. However, when I complete this action my MongoDB Compass Community database receives the registration details that I have entered successfully but my browser states 'The site cannot be reached' and URL is "localhost:3000/employee/employee/login" when it should be "localhost:3000/employee/home". I have added in the code below that would be in use. I have also added the the folder and file structure below with the folders open to show this display. I am new to Node js so I am sorry if this is an easy fix.
I have tried changing the routes in the "router.post('/employee/login', function (req, res, next) {" in the controller file or the "form action="/employee/login" method="post" to other ways but for the ones I used got an error of - Cannot POST /employee/login, or similar (Cannot POST / etc)
controllers/employeeController.js
router.get('/home', (req, res) => {
res.render('employee/home', {layout: 'main.hbs'});
});
router.get('/login', (req, res) => {
res.render('employee/login', {layout: 'main.hbs'});
});
//POST route for updating data
router.post('/employee/login', function (req, res, next) {
// confirm that user typed same password twice
if (req.body.password !== req.body.passwordConf) {
var err = new Error('Passwords do not match.');
err.status = 400;
res.send("passwords dont match");
return next(err);
}
if (req.body.email &&
req.body.username &&
req.body.password &&
req.body.passwordConf) {
var userData = {
email: req.body.email,
username: req.body.username,
password: req.body.password,
}
User.create(userData, function (error, user) {
if (error) {
return next(error);
} else {
req.session.userId = user._id;
return res.redirect('employee/home');
}
});
} else if (req.body.logemail && req.body.logpassword) {
User.authenticate(req.body.logemail, req.body.logpassword, function (error, user) {
if (error || !user) {
var err = new Error('Wrong email or password.');
err.status = 401;
return next(err);
} else {
req.session.userId = user._id;
return res.redirect('employee/home');
}
});
} else {
var err = new Error('All fields required.');
err.status = 400;
return next(err);
}
})
// GET for logout logout
router.get('/logout', function (req, res, next) {
if (req.session) {
// delete session object
req.session.destroy(function (err) {
if (err) {
return next(err);
} else {
return res.redirect('employee/login');
}
});
}
});
module.exports = router;
models/user.js (should be fine since database recieves data but shown for further context)
var UserSchema = new mongoose.Schema({
email: {
type: String,
unique: true,
required: true,
trim: true
},
username: {
type: String,
unique: true,
required: true,
trim: true
},
password: {
type: String,
required: true,
}
});
//authenticate input against database
UserSchema.statics.authenticate = function (email, password, callback) {
User.findOne({ email: email })
.exec(function (err, user) {
if (err) {
return callback(err)
} else if (!user) {
var err = new Error('User not found.');
err.status = 401;
return callback(err);
}
bcrypt.compare(password, user.password, function (err, result) {
if (result === true) {
return callback(null, user);
} else {
return callback();
}
})
});
}
//hashing a password before saving it to the 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;
server.js
mongoose.Promise = global.Promise;
mongoose.connect('mongodb://localhost:27017/testForAuth', { useNewUrlParser: true, useCreateIndex: true, useFindAndModify: false }, (err) => {
if (!err) { console.log('MongoDB Connection Succeeded.') }
else { console.log('Error in DB connection : ' + err) }
});
var db = mongoose.connection;
//handle mongo error
db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', function () {
// we're connected!
});
//use sessions for tracking logins
app.use(session({
secret: 'work hard',
resave: true,
saveUninitialized: false,
store: new MongoStore({
mongooseConnection: db
})
}));
const employeeController = require('./controllers/employeeController');
var app = express();
//setting up morgan middleware
app.use(logger('dev'));
app.use(bodyparser.urlencoded({
extended: false
}));
app.use(bodyparser.json());
app.set('views', path.join(__dirname, '/views/'));
app.engine('hbs', exphbs({ extname: 'hbs', defaultLayout: 'mainLayout', layoutsDir: __dirname + '/views/layouts/' }));
app.set('view engine', 'hbs');
//serving blank favicon to keep from throwing 404 errors
app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')))
//setting up static path for serving static files
app.use(express.static(path.join(__dirname, 'public')));
//Bringing in the routes
const index = require('./routes/index');
const api = require('./routes/api');
app.use('/', index);
app.use('/api', api);
app.listen(3000, () => {
console.log('Express server started at port : 3000');
});
app.use('/employee', employeeController.router);
views/employee/login.hbs (handlebars)
<h3>Login</h3>
<div class="login-form">
<form action="employee/login" method="post">
<input type="text" name="logemail" placeholder="E-mail" required="">
<input type="password" name="logpassword" placeholder="Password" required="">
<div class="tp">
<input type="submit" value="LOGIN NOW">
</div>
</form>
</div>
</div>
</div>
<div class="agile">
<div class="signin-form profile">
<h3>Register</h3>
<div class="login-form">
<form id="login2" action="employee/login" method="post" onsubmit="event.preventDefault(); validateMyForm();">
<input type="text" name="email" placeholder="E-mail" required="">
<input type="text" name="username" placeholder="Username" required="">
<input type="password" name="password" placeholder="Password" id="password" required="" >
<input type="password" name="passwordConf" placeholder="Confirm Password" id="password_conf" required="">
<input type="submit" value="REGISTER">
</form>
</div>
<p> By clicking register, I agree to your terms</p>
</div>
</div>
<div class="clear"></div>
</div>
<script type="text/javascript">
var passWordField = document.getElementById('password')
var passWordConfirmField = document.getElementById('password_conf')
var form = document.getElementById('login2')
function validateMyForm(){
if(passWordField.value != passWordConfirmField.value){
alert("Passwords do not match. Please try again.");
} else {
form.submit()
}
}
</script>
</body>
</html>
Take away any lines of code with
req.session.userId = user._id;
Also, change anything with res.redirect as such
return res.redirect('employee/home');
to res.render, as such
return res.render('employee/home');
I am a newbie to nodejs and am trying to build a login system.I have added the code to serialize the user from passport documentation in config/passport.js.But still I am getting the error:Failed to serialize user into session when I enter a valid email and password to login.Can anyone point out what I am doing wrong?
app.js
const express = require('express');
const expressLayouts = require('express-ejs-layouts');
const mongoose = require('mongoose');
const flash = require('connect-flash');
const session = require('express-session');
const passport = require('passport');
const app = express();
//Passport config
require('./config/passport')(passport);
//DB config
require('dotenv').config(); //for setting environment variables on server
const uri = process.env.ATLAS_URI;
mongoose.connect(uri,{useNewUrlParser:true ,useUnifiedTopology: true})
.then(() => console.log("mongodb connected"))
.catch(err => console.log(err));
//EJS
app.use(expressLayouts);
app.set('view engine','ejs');
//Express Bodyparser
app.use(express.urlencoded({extended:true}));
//Express session
app.use(session({
secret: 'secret',
resave: true,
saveUninitialized: true,
}));
//Passport middleware for authentication and login
app.use(passport.initialize());
app.use(passport.session());
//connect flash
app.use(flash());
//global variables to create flash messages
app.use((req,res,next) => {
res.locals.success_msg = req.flash('success_msg');
res.locals.error_msg = req.flash('error_msg');
res.locals.error = req.flash('error');
next();
});
//Routes
app.use('/',require('./routes/index'));
app.use('/users',require('./routes/users'));
const PORT = process.env.PORT || 5000;
app.listen(PORT,console.log(`Server started on port ${PORT}`));
config/passport.js
const localStrategy = require('passport-local').Strategy;
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
//load user model
const User = require('../models/User');
module.exports = function(passport) {
passport.use(
new localStrategy({usernameField:'email'},(email,password,done) => {
//Check if email is registered
User.findOne({email:email})
.then( user => {
if(!user){
return done(null,false,{message:'This email is not registered'});
}
//if email exists we compare password
bcrypt.compare(password,user.password,(err,isMatch) => {
if(err) throw err;
if(isMatch){
return done(null,true);
}
else{
return done(null,false,{message:'Wrong Password!'});
}
});
}
)
.catch(err => console.log(err));
})
);
//Serialize and deserialize user
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
User.findById(id, function(err, user) {
done(err, user);
});
});
};
routes/users.js
const express = require('express');
const router = express.Router();
const User = require('../models/User');
const bcrypt = require('bcryptjs');
const passport = require('passport');
//Login page
router.get('/login',(req,res)=>res.render('login'));
//Register
router.get('/register',(req,res)=>res.render('register'));
//Registration Handling
router.post('/register',(req,res) =>{
const { name, email, password, password2 } = req.body;
let errors = [];
//Check required fields
if(!name || !email ||!password || !password2){
errors.push({msg:'Please fill required fields'});
}
//check if passwords equal
if(password !== password2){
errors.push({msg:'Passwords do not match'});
}
//check password length
if(password.length<6){
errors.push({msg:'Password should be atleast 6 characters'});
}
if(errors.length>0){
res.render('register',{
errors,
name,
email,
password,
password2
});
}else{
//Validation passed
//check if email already exists
User.findOne({email:email})
.then(user => {
if(user){
errors.push({msg:'Email already registered'});
res.render('register',{
errors,
name,
email,
password,
password2,
});
}
else{
const newUser = new User({
name,
email,
password,
});
//Hash password
bcrypt.genSalt(10,(err,salt) => {
if(err) throw err;
bcrypt.hash(newUser.password,salt,(err,hash) => {
if(err) throw err;
//set password to hash
newUser.password = hash
//save user
newUser.save()
.then( user => {
req.flash('success_msg','Thanks for registering!You can login now');
res.redirect('/users/login');
})
.catch(err => {
console.log(err);
});
});
});
}
});
}
});
//Login handling
router.post('/login',(req,res,next) => {
passport.authenticate('local',{
successRedirect:'/dashboard',
failureRedirect:'/users/login', //these routes are wrt host
failureFlash: true,
})(req,res,next);
});
module.exports = router
I passed the wrong argument to done() when the password was a match in passport.js.
I used
if(isMatch){
return done(null,true);
}
where it should have been
if(isMatch){
return done(null,user);
}
When I am trying to send POST request, it returns 404, although all routes is right.
I've rummaged through tons of similar questions but I didn't find anything that seems to solve my problem.
Here's a code:
App.js
const http = require('http');
const url = require('url');
const express = require('express');
const mongoose = require('mongoose');
const path = require('path');
const bodyParser = require('body-parser');
const passport = require('passport');
mongoose.connect('mongodb://localhost:27017/mydb');
let db = mongoose.connection;
db.once('open', () => {
console.log('Connected to Database');
});
db.on('error', (err) => {
console.log(err);
});
const server = express();
server.use(express.static('dist', { extensions: ['html'] }));
let users = require('./routes/users');
server.use(users);
server.use(function (req, res, next) {
res.status(404).sendFile(path.join(__dirname+'/dist/404.html'));
});
const port = process.env.port || 3000;
server.listen(port, () => {
console.log(`Server has been established on port ${port}`)
});
./models/user.js
const User = mongoose.Schema({
name: {
type: String,
required: true
},
lastname: {
type: String,
required: true
},
login: {
type: String,
required: true
},
password: {
type: String,
required: true
},
b_day: {
type: String,
required: true
},
b_month: {
type: String,
required: true
},
b_year: {
type: String,
required: true
},
gender: {
type: String,
required: true
}
});
const user = module.exports = mongoose.model('User', User);
./routes/users.js
const router = express.Router();
const bcrypt = require('bcryptjs');
const passport = require('passport');
let User = require('../models/user');
//Register Form
router.get('/signup', (req, res) => {
console.log(res);
res.render('signup');
});
//Register Process
router.post('signup', (req, res) => {
const name = req.body.name;
const lastname = req.body.lastname;
const login = req.body.login;
const password = req.body.password;
const password2 = req.body.repeat_password;
const b_day = req.body.b_day;
const b_month = req.body.b_month;
const b_year = req.body.b_year;
const gender = req.body.gender;
req.checkBody('name', 'Name is required').notEmpty();
req.checkBody('lastname', 'Lastname is required').notEmpty();
req.checkBody('login', 'Login is required').notEmpty();
req.checkBody('password', 'Password is required').notEmpty();
req.checkBody('password2', 'Passwords do not match').equals(req.body.password);
req.checkBody('b_day', 'Birth day is required').notEmpty();
req.checkBody('b_month', 'Birth month is required').notEmpty();
req.checkBody('b_year', 'Birth year is required').notEmpty();
req.checkBody('gender', 'Gender is required').notEmpty();
let errors = req.validationErrors();
if(errors) {
res.render('signup', {
errors:errors
});
} else {
let newUser = new User({
name:name,
lastname:lastname,
login:login,
password:password,
gender:gender,
b_day:b_day,
b_month:b_month,
b_year:b_year
});
bcrypt.genSalt(10, (err, salt) => {
bcrypt.hash(newUser.password, salt, (err, hash) => {
if(err) {
console.log(err);
}
newUser.password = hash;
newUser.save((err) => {
if(err) {
console.log(err);
return;
} else {
req.flash('success', 'You are now registered');
res.redirect('signin');
}
});
});
});
}
});
router.get('signin', (req, res) => {
res.render('signin');
});
router.post('signin', (req, res, next) => {
passport.authenticate('local', {
successRedirect:'profile',
failureRedirect:'signin',
failureFlash: true
})(req, res, next);
});
router.get('logout', (req, res) => {
res.render('logout');
req.flash('success', 'You are logged out');
res.redirect('signin');
});
module.exports = router;
And also project structure
├── dist
├── routes
│ └── users.js
├── models
│ └── user.js
└── app.js
I expect it will process all data from sign up form and then redirect to sign in page.
Your routes are correct. Error 404 only comes if a route is not found. In your case, its happening because you have not added "/" before calling signup(post request) and signin routes as well in users.js.
Right now your api url is becoming like this :
localhost:3000/userssignin
which should be :
localhost:3000/users/signin
So, your routes should be :
router.post('/signup', (req, res) => {
router.get('/signin', (req, res) => {
router.post('/signin', (req, res) => {
i don't know, but from a first view, i think that you are missing dash before routes
router.get('signin', (req, res) => { // '/singin'
res.render('signin');
});
router.post('signin', (req, res, next) => { // '/singin'
passport.authenticate('local', {
successRedirect:'profile',
failureRedirect:'signin',
failureFlash: true
})(req, res, next);
});
router.get('logout', (req, res) => { // '/logout'
res.render('logout');
req.flash('success', 'You are logged out');
res.redirect('signin');
});
I have been browsing the forum but I am unable to find the error in my code , it always results in false and I am not able to solve it , Please help me...
Is there any other way to authenticate user instead of passport so that I can use it.I am adding more sentences as it is not allowing me to post my query,Sorry..
const http = require("http"),
hostname = "127.0.0.1",
port = 3000,
bodyParser = require("body-parser"),
mongoose = require("mongoose"),
express = require("express"),
passport = require("passport"),
localStrategy = require("passport-local"),
passportLocalMongoose = require("passport-local-mongoose"),
User = require("./models/user");
app = express();
mongoose.connect("mongodb://localhost/drive", { useNewUrlParser: true });
app.set("view engine", "ejs");
app.use(express.static("public"));
app.use(passport.initialize());
app.use(passport.session());
app.use(
require("express-session")({
secret: "Beta tumse na ho payega",
resave: false,
saveUninitialized: false
})
);
passport.use(new localStrategy(User.authenticate()));
passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser());
app.use(bodyParser.urlencoded({ extended: true }));
const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader("Content-Type", "text/plain");
});
app.get("/", function(req, res) {
res.render("index");
});
app.get("/register", function(req, res) {
res.send("hello");
});
app.get("/login", function(req, res) {
res.render("login");
});
app.post(
"/login",
passport.authenticate("local", {
successRedirect: "/",
failureRedirect: "/login"
}),
function(req, res) {}
);
app.get("/logout", function(req, res) {
req.logout();
res.redirect("/");
});
function isLoggedIn(req, res, next) {
if (req.isAuthenticated()) {
return next();
} else {
console.log("Not logged");
res.redirect("/login");
}
}
app.get("/secret", isLoggedIn, function(req, res) {
res.send("You are logged in");
});
app.post("/register", function(req, res) {
if (req.body.password === req.body.cpassword) {
User.register(
new User({ username: req.body.username }),
req.body.password,
function(err, user) {
if (err) console.log(err);
else
passport.authenticate("local")(req, res, function() {
res.send("signed up");
});
}
);
} else res.send("Password Mismatch");
});
//DRIVE SCHEMA
//var driveSchema = mongoose.Schema({
// title: String,
// created: { type: Date, default: Date.now }
//});
app.listen(port, hostname, function() {
console.log("Server is running at " + hostname + "/" + port);
});
//./models/user.js file
const mongoose = require("mongoose"),
passportLocalMongoose = require("passport-local-mongoose");
var UserSchema = new mongoose.Schema({
username: String,
password: String
});
UserSchema.plugin(passportLocalMongoose);
module.exports = mongoose.model("User", UserSchema);
I have used passport and jwt library, to authenticate and maintain session for the user. There is no need to maintain user session on server side.
apis/apis.js : This file has all apis end points. /login url will authenticate user using passport and send a token to the client using jwt
const passport = require('passport')
const expRoute = require('express').Router();
let exporter = process.exporter;
expRoute.post('/login', (req, res, next) => {
passport.authenticate(
'local',
{
// successRedirect: '/',
// failureRedirect: '/login',
successFlash: 'Welcome!',
failureFlash: 'Invalid username or password.'
},
(err, user, info) => {
if (err) {
return res.status(500).json(err)
}
else if (user) {
return res.status(200).json({
token: exporter.generateToken(user)
})
}
else {
return res.status(400).json(info)
}
}
)(req, res, next);
})
expRoute.get('/view', exporter.authenticateToken, (req, res) => {
let param = req.finalTokenExtractedData
if (param && exporter.isObjectValid(param, 'tokenId', true, true)) {
let condition = {
_id: param.tokenId
}
let options = {
_id: 0,
password: 0,
__v: 0
}
process.USER.findOne(condition, options) // mongo find
.then((data) => {
res.status(200).json({
result: data,
msg: 'success'
})
})
.catch((mongoErr) => {
exporter.logNow(`USER mongo Error: ${mongoErr}`)
res.status(400).json({
msg: 'user not found'
})
})
}
else {
res.status(404).json({
msg: 'invalid token'
})
}
})
module.exports = expRoute
common/env.js: This file will initialise all connections such mongo, require few files will be used as global
process.CONFIG = require('../configs/config.json')
process.exporter = require("../lib/exporter.js")
process.dbInit = (globalName, mongoUrl, collectionName) => {
require("../models/db-init.js")(mongoUrl, collectionName)
.then((modelObj) => {
process[globalName] = modelObj // will be used as global
})
.catch((dbInitErr) => {
process.exporter.logNow(`dbInit Error: ${dbInitErr}`)
process.exit()
});
}
lib/exporter.js: This file has exporter class which consist of all major functions like mongo connection, authenticateToken, verifyPassword, etc.
const fs = require('fs'),
redis = require("redis"),
path = require("path"),
mongoose = require('mongoose'); mongoose.set('useCreateIndex', true);
const Schema = mongoose.Schema;
var bcrypt = require('bcryptjs')
var jwt = require('jsonwebtoken')
class Exporter {
mongoConnection(mongoURI, schemaObj) {
return new Promise(async (resolve, reject) => {
if (!mongoURI || typeof mongoURI == 'undefined' || mongoURI.length < 1)
return reject('invalid mongo connection url');
return resolve(mongoose.createConnection(mongoURI, { useNewUrlParser: true }))
})
}
createMongoSchema(schemaObj) {
return (new Schema(schemaObj));
}
createMongoModel(mongoDB, collectionName, newSchema) {
if (newSchema)
return mongoDB.model(collectionName, newSchema)
return mongoDB.model(collectionName)
}
authenticateToken(req, res, next) {
const bearerHeader = req.header('authorization')
if (typeof bearerHeader != 'undefined') {
const bearer = bearerHeader.split(' ')
const bearerToken = bearer[1]
jwt.verify(bearerToken, process.CONFIG.jwt.token.activated, (err, data) => {
if (err)
res.status(400).json({
msg: "Invalid token or please try to login again"
})
else {
process.exporter.getSingleHashKeysValuesFromRedis('expired_token', bearerToken)
.then((redisTokendata) => {
if (redisTokendata)
res.status(400).json({
msg: "token expired"
})
else {
req.finalTokenExtractedData = data
// if (req.originalUrl.trim() == process.logoutURL.trim())
req.jwtToken = {
token: bearerToken,
secret: process.CONFIG.jwt.token.activated
}
next()
}
})
.catch((redisTokenError) => {
process.exporter.logNow(`redis token error: ${redisTokenError}`)
res.status(400).json({
msg: "Some went wrong while checking token. Please try later."
})
})
}
})
}
else
res.status(400).json({
msg: "invalid token"
})
}
generateToken(data) {
let expiry = new Date();
// expiry.setDate(expiry.getDate() + 7)
expiry.setMinutes(expiry.getMinutes() + 5)
return jwt.sign({
tokenId: data._id,
exp: parseInt(expiry.getTime() / 1000),
}, process.CONFIG.jwt.token.activated)
}
createPassword(password) {
return new Promise((resolve, reject) => {
if (typeof password == 'undefined' && password == '')
return reject('password empty')
bcrypt.hash(password, 10, async (bErr, hash) => {
if (bErr)
reject(bErr)
else
resolve(hash)
})
})
}
verifyPassword(enteredPassword, savePassword) {
return bcrypt.compareSync(enteredPassword, savePassword)
}
}
module.exports = (new Exporter());
index.js: This is file which you will execute node index.js.
const express = require('express');
const path = require('path');
const bodyParser = require('body-parser');
const passport = require('passport');
require('./common/env')
require('./configs/passport')
const app = express()
const cors = require('cors')
app.use(cors());
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: true}))
app.use(passport.initialize())
app.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
});
let apis = require('./apis/api')
app.use('/user', apis)
/**
* 404 Handler
*/
app.use((req, res, next)=>{
return res.status(404).send("Endpoint "+req.url +" not found");
})
/**
* if any error or exception occurred then write into a JS file so that app can be restarted
*/
process.on('uncaughtException', (err) => {
console.error(err.stack);
});
app.listen(3000, function(server) {
console.log("App listening at 3000");
});
When index.js file has been executed successfully and listening to a port 3000, then use this URL http://localhost:3000/user/login for authentication, it will accept your username and password, then authenticate using passport and send a token to the client as response. The token can contain user data in encrypted form and have expiry time.
Reference link: https://github.com/arjun-707/login-logout-jwt-nodejs
You need the express-session module.
server.js
// Require a possible config.js file with your configuration variables
const Config = require('./models/config.js');
// If the config module is formed as a class
const config = new Config();
const express = new('express');
const app = express();
const session = require('express-session');
const MongoStore = require('connect-mongo')(session);
const uuid = require('uuid/v4');
const mongoose = require('mongoose');
// Require your custom passport local strategy file
const passport = require('./models/sessions');
mongoose.connect('mongodb://localhost:27017/your_db_name', {
useNewUrlParser: true
});
// Set the session options
app.use(session({
// Use UUIDs for session IDs
genid: (req) => {
return uuid()
},
// If you want to store the session in MongoDB using mongoose
// Require your personal mon
store: new MongoStore({
mongooseConnection: mongoose.connection
}),
// Define the session secret in env variable or in config file
secret: process.env.SESSION_SECRET || config.sessionSecretKey,
resave: false,
saveUninitialized: true
}));
// Initialize the passport module
app.use(passport.initialize());
// Tell to passport to use session
app.use(passport.session());
./models/session.js
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const bcrypt = require('bcrypt-nodejs');
// Your custom MongoDB connection module
const db = require('./db');
// Configure passport.js to use the local strategy
passport.use(new LocalStrategy({
usernameField: 'username'
},
(username, password, done) => {
db.User.find({
username: username // But it could use email as well
}).then(res => {
const user = JSON.parse(JSON.stringify(res[0]));
if (!user) {
return done(null, false, {
message: 'Invalid credentials.\n'
});
}
if (!bcrypt.compareSync(password, user.password)) {
return done(null, false, {
message: 'Invalid credentials.\n'
});
}
return done(null, user);
}).catch(error => done(error));
}
));
// Tell passport how to serialize the user
passport.serializeUser((user, done) => {
done(null, user._id);
});
// Tell passport how to deserialize the user
passport.deserializeUser((id, done) => {
db.User.find({
_id: id
}).then(res => {
const response = typeof res !== undefined && res.length != 0 ? JSON.parse(JSON.stringify(res[0])) : null;
done(null, response)
}).catch(error => done(error, false))
});
// Export passport for external usage
module.exports = passport;
./models/db.js
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const uuid = require('uuid/v4');
// Connect to MongoDB
mongoose.connect('mongodb://localhost:27017/your_db_name', {
useNewUrlParser: true
});
// Define the models container
let models = {};
// Prepare your user schema
const userSchema = Schema({
username: {
type: String,
required: true
},
password: {
type: String,
required: true
}
});
// Assign the user schema
models.User = mongoose.model('User', userSchema);
// Export for external usage
module.exports = models;
/*
In that way you can wrap in "models" all schemas:
const newSchema = Schema({
name: String
});
models.Newschema = mongoose.model('Newschema', newSchema);
module.exports = models;
And use it externally like:
const db = require('./models/db');
db.User.find({}).then(docs => {}).catch(err => {});
db.Newschema.find({}).then(docs => {}).catch(err => {});
Etc....
*/
./models/config.js
class Config {
constructor() {
this.sessionSecretKey = "my-awesome-secretkey";
/* And other configurations */
}
}
module.exports = Config;
/*
Externally use like:
const Config = require('./models/config');
const config = new Config();
let sessionSecretKey = config.sessionSecretKey;
*/
Then you can use the req.isAuthenticated() after the login:
// POST the username and password to '/login' router
app.post('/login', (req, res, next) => {
passport.authenticate('local', (err, user, info) => {
if (info) {
return res.send(info.message)
}
if (err) {
return next(err);
}
if (!user) {
return res.sendStatus(404); // Is a shortcut
// OR -> res.status(404).end();
// OR -> res.status(404).send('Not found'); as you like
}
req.login(user, (err) => {
if (err) return next(err);
// Store the user object retrieved from MongoDB in `req.session`
req.session.user = user;
return res.sendStatus(200); // Is a shortcut
// OR -> res.status(200).end();
// OR -> res.status(200).send('OK'); as you like
})
})(req, res, next);
});
// The logout logic
app.get('/logout', verifySession, function (req, res) {
req.session.destroy(function (err) {
req.logout();
res.redirect('/');
});
});
// Verify the session to protect private routers
function verifySession(req, res, next) {
if (req.isAuthenticated()) {
next();
} else {
// Forbidden
res.redirect('/');
// OR -> res.sendStatus(403);
// OR -> res.status(403).end();
// OR -> res.status(403).send('Forbidden'); as you like
}
}
Of course you have to run npm install with all the dependencies required defined in package.json file or manually instal with npm i express-session#latest --s for all the dependencies required: npm i module-name#latest --s.
Don't forget the
const server = app.listen(config.port || 3000, () => {
console.log(`Server running on ${server.address().port} port.`);
});
At the end of server.js file. I hope that it will be useful for you.