displaying current number of unique users on website using nodejs - node.js

I'm trying to display number of current users on a particular page on my website. I got to know about socket.io for doing this exact particular thing but every solution on the internet had a separate server.js file to make it work. My question is, how do I implement this using app.js which I have already configured for my website's use, listening on port 3000. Sorry if my question is a little naive but I've recently started web development and don't know exactly how to do this.
My app.js is as follows:
//jshint esversion:6
require('dotenv').config();
const express = require('express');
const bodyParser = require('body-parser');
const ejs = require('ejs');
const _ = require('lodash');
const mongoose = require('mongoose');
const session = require('express-session');
const passport = require('passport');
const passportLocalMongoose = require('passport-local-mongoose');
const GoogleStrategy = require('passport-google-oauth20').Strategy;
const findOrCreate = require('mongoose-findorcreate');
const app = express();
app.set('view engine', 'ejs');
app.use(bodyParser.urlencoded({extended: true}));
app.use(express.static("public"));
//Passport Sessions setup
app.use(session({
secret: "Our little secret.",
resave: false,
saveUninitialized: false
}));
app.use(passport.initialize());
app.use(passport.session());
// Users DB
let conn = mongoose.createConnection("mongodb+srv://#cluster0.wrvir.mongodb.net/userDB", {useNewUrlParser: true, useUnifiedTopology: true});
// conn.set("useCreateIndex", true);
const userSchema = new mongoose.Schema ({
username: String,
// password:String,
googleId: String
});
userSchema.plugin(passportLocalMongoose);
userSchema.plugin(findOrCreate);
const User = conn.model("User", userSchema);
passport.use(User.createStrategy());
passport.serializeUser(function(user, done){
done(null, user.id);
});
passport.deserializeUser(function(id, done){
User.findById(id, function(err, user){
done(err, user);
});
});
// Google Authentication Strategy
passport.use(new GoogleStrategy({
clientID: process.env.CLIENT_ID,
clientSecret: process.env.CLIENT_SECRET,
callbackURL: "http://localhost:3000/auth/google/timeline",
userProfileURL: "https://www.googleapis.com/oauth2/v3/userinfo"
},
function(accessToken, refreshToken, profile, cb) {
console.log(profile);
User.findOrCreate({ googleId: profile.id, username: profile.displayName }, function (err, user) {
return cb(err, user);
});
}
));
// Stories DB
let conn2 = mongoose.createConnection("mongodb+srv://#cluster0.wrvir.mongodb.net/storiesDB", {useNewUrlParser: true, useUnifiedTopology: true});
const postSchema = {
title: String,
content: String
};
const Post = conn2.model("Post", postSchema);
app.get("/", function(req, res){
res.render("home");
});
app.get("/auth/google",
passport.authenticate("google", { scope: ["profile"] })
);
app.get("/auth/google/timeline",
passport.authenticate("google", { failureRedirect: '/login' }),
function(req, res) {
// Successful authentication, redirect to timeline.
res.redirect('/timeline');
});
app.get("/posts/:postid", function(req, res){
const requestedPostId = req.params.postid;
Post.findOne({_id: requestedPostId}, function(err, post){
res.render("post", {
title: post.title,
content: post.content
});
});
});
app.get("/compose", function(req, res){
res.render("compose");
});
app.post("/compose", function(req, res){
const post = new Post({
title: req.body.postTitle,
content: req.body.postBody
});
post.save(function(err){
if (!err){
res.redirect("/timeline");
}
});
});
app.get("/timeline", function(req, res){
if(req.isAuthenticated()){
Post.find({},function(err, posts){
res.render("timeline", {
startingContent: homeStartingContent,
posts: posts
});
});
} else {
res.redirect("/login");
}
});
app.get("/logout", function(req, res){
req.logout();
res.redirect("/");
});
app.listen(process.env.PORT || 3000, function(){
console.log("Server started on port 3000...");
});

Server side
The `app.listen` function at the end of your code returns a `server` object, which can then be used to setup a socket like that:
const socketio = require('socket.io');
// ...
const PORT = process.env.PORT || 3000;
const server = app.listen(PORT, () => console.log(`Server started on port ${PORT}.`));
const io = socketio(server);
Then you could have the following logic to count your users and emit an event to every user, if the count changes:
let userCount = 0;
io.on('connection', socket => {
userCount++;
io.emit('user-count-change', userCount);
socket.on('disconnect', () => {
userCount--;
io.emit('user-count-change', userCount);
});
});
Optional
But since this code would constantly emit events to every user on every connection, the number of emits would grow exponentially. As a solution I'd fire events in an interval with a bit of delay in between:
const userCountUpdateDelay = 10000;
let userCount = 0;
io.on('connection', socket => {
userCount++;
socket.on('disconnect', () => { userCount--; });
socket.emit('user-count-change', userCount);
});
setInterval(() => {
io.emit('user-count-change', userCount);
}, userCountUpdateDelay);
Client side
Add this html somewhere to a your website:
<script src="/socket.io/socket.io.js"></script>
<script>
const socket = io();
socket.on('user-count-change', function (userCount) {
console.log(userCount);
// Display the user count...
});
</script>
Learn more
If you wanna learn more about socket.io, it has a great documentation, which you can find here

Related

req.isAuthenticated() always returns false

I am new to the Passport.js. Whenever I login or register through the routes, req.isAuthenticated() always returns false and the secrets view is never rendered. Here is my app.js:
`
require("dotenv").config();
const express = require("express");
const bodyParser = require("body-parser");
const session = require("express-session");
const passport = require("passport");
const LocalStrategy = require("passport-local")
const passportLocalMongoose = require("passport-local-mongoose");
const mongoose = require("mongoose");
mongoose.set("strictQuery", true);
uri = "mongodb://localhost:27017/usersDB";
const userSchema = new mongoose.Schema({
username: String,
password: String
});
userSchema.plugin(passportLocalMongoose);
const User = new mongoose.model("User", userSchema);
passport.use(new LocalStrategy(User.authenticate()));
passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser());
const app = express();
app.use(bodyParser.urlencoded({extended: true}));
app.use(express.static("public"));
app.set("view engine", "ejs");
app.use(session({
secret: "MyLittleSecret",
resave: false,
saveUninitialized: false
}));
app.use(passport.initialize());
app.use(passport.session());
app.get("/", function(req, res) {
res.render("home");
});
app.get("/secrets", function(req, res) {
if(req.isAuthenticated) {
res.render("secrets");
} else {
res.redirect("/login");
}
});
app.route("/register")
.get(function(req, res) {
res.render("register");
})
.post(async function(req, res) {
await User.register({username: req.body.username}, req.body.password, function(err, user) {
console.log("In the User.register callback function");
if(err) {
console.log(err);
res.redirect("/register");
} else {
passport.authenticate("local")(req, res, function() {
res.redirect("/secrets");
});
}
});
});
app.route("/login")
.get(function(req, res) {
res.render("login",{
errorMessage: ""
});
})
.post(async function(req, res) {
const userInstance = new User({
username: req.body.username,
password: req.body.password
});
req.login(userInstance, function(err) {
if(err) {
console.log(err);
} else {
passport.authenticate("local")(req,res,function() {
res.redirect("/secrets");
});
}
});
});
app.listen(3000, function() {
console.log("Server started listening to port 3000");
});
I searched on the web and tried the various orders of middleware. I could not solve the problem.

how to fix password: ValidatorError: Path `password` is required

I am trying to register a new user using mongoose and authenticate the user using passport but I keep getting this error password:validatorError path 'password ' is require. I cant seem to figure out how to solve this error.
I am newbie and am trying to learn authentication ,any assistance is highly appreciated.
const hbs = require("express-handlebars")
const mongoose = require("mongoose")
const session = require("express-session")
const passport = require("passport")
const passportLocalMongoose = require("passport-local-mongoose")
require("dotenv").config()
const app = express()
const PORT = process.env.PORT || "8000"
// middleware
app.engine("hbs", hbs({extname: '.hbs'}))
app.set("view engine", 'hbs')
app.use(express.static(__dirname + "/public"))
// for parsing
app.use(express.urlencoded({extended: false}))
// for testing
app.use(express.json())
// creates a session
app.use(session({
secret: "verysolidsecret",
resave: false,
saveUninitialized: false
}))
//initialize passport
app.use(passport.initialize())
//use passport to manage session
app.use(passport.session())
// conncet to local MongoDB
const url = 'mongodb://127.0.0.1:27017/authentication'
mongoose.connect(url, {
useNewUrlParser: true,
useUnifiedTopology: true
})
// log error or success message
mongoose.connection.once("open", () => {
console.log('connected to database',url)
})
mongoose.connection.on("error", err => console.log("connection", err))
// schema and model
const UserSchema = new mongoose.Schema({
username: {
type: String,
required: true
},
password: {
type: String,
required: true
}
})
// mongoose passport plugin to salt,hash and store the user into MongoDB
UserSchema.plugin(passportLocalMongoose)
// create mongoose model
const User = mongoose.model('User',UserSchema)
passport.use(User.createStrategy());
passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser());
// setup Routes
app.get("/",(req,res) => {
res.render("index", {title:"Home"})
})
app.get("/login", (req,res) =>{
res.render("login", {title: "Login"})
})
app.get("/index", (req,res) =>{
if (req.isAuthenticated()){
res.render("index", {title: "Index"})
}
else{
res.redirect("/login")
}
})
app.get("/register", (req,res) =>{
res.render("register", {title: "Register"})
})
// register user
app.post("/register", (req,res) =>{
User.register({username:req.body.Username},req.body.password, function(err, user){
if (err) {
console.log(err)
res.redirect("/register")
}
else{
passport.authenticate("local")(req,res, function(){
res.redirect("/index")
})
}
})
})
// login a user
app.post("/login", (req,res) =>{
})
app.listen(PORT, () => {
console.log(`server is up and running on PORT: ${PORT}`)
})```

Mongoose connection sockets closed

I'm using nodejs and MongoDB for the first time and I'm having trouble when I want to connect to the DB.
I read the quickstart from mongoosejs but it still doesn't work, here is my code (right now, all my code is only in one file) :
/***** Utilities *****/
var express = require('express');
var app = express();
var bodyParser = require('body-parser');
var path = require('path');
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;
var mongo = require('mongodb');
var mongoose = require('mongoose');
app.listen(8080);
mongoose.connect('mongodb://localhost:8080/users');
var db = mongoose.connection;
db.on('error', function (error) {
console.log('/**************** ERROR **************\\');
console.log(error);
});
db.on('open', function () {
console.log("Hello World!");
});
var Schema = mongoose.Schema;
var UserSchema = new Schema({
pseudo: String,
password: String,
email: String
});
var Users = mongoose.model('users', UserSchema);
app.use(bodyParser.urlencoded({
extended: true
}));
app.use(express.static(path.join(__dirname, 'public')));
app.set('views', __dirname + '/views');
app.use(passport.initialize());
app.use(passport.session());
/***** Passport *****/
passport.serializeUser(function (user, done) {
done(null, user);
});
passport.deserializeUser(function (user, done) {
done(null, user);
});
/***** Building routes *****/
app.get('/', function (req, res) {
res.send('Hello World!');
});
app.post('/', function (req, res) {
var pseudo = req.body.pseudo;
console.log("/******************************\\") console.log(pseudo);
res.send('Hello ' + pseudo + '!');
});
app.get('/users', function (req, res) {
Users.find(function (error, users) {
if (error) console.log(error);
else res.render('users.jade', {
users: users
})
});
})
app.post('/create_user', function (req, res) {
var user = new Users({
pseudo: req.body.pseudo,
password: req.body.password,
email: req.body.email
});
user.save(function (error, user) {
if (error) console.log(error);
else res.render('create_user.jade');
});
});
app.get('/login', function (req, res) {
passport.authenticate('users', {
successRedirect: '/create_user',
failureRedirect: '/login',
}) res.render('login.jade
);
});
I'm trying to get data from the login page and store them into one mongo database but I'm when I start this file, mongo returns the following message:
MongoError: server localhost:8080 sockets closed
name: 'MongoError',
message: 'server localhost:8080 sockets closed'
I really don't know how to correct this error.
Where am I wrong? Is it because of a possible asynchronous problem?

express-session won't log out

The code
app.js:
var express = require('express');
var session = require('express-session');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var mongoStore = require('connect-mongo')(session);
var mongoose = require('mongoose');
var passport = require('passport');
var config = require('./config');
var routes = require('./routes');
var mongodb = mongoose.connect(config.mongodb);
var app = express();
// view engine setup
app.set('views', config.root + '/views');
app.set('view engine', 'jade');
app.engine('html', require('ejs').renderFile);
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
extended: false
}));
app.use(cookieParser());
app.use(express.static(config.root + '/public'));
app.use(session({
name: 'myCookie',
secret: 'tehSecret',
resave: true,
saveUninitialized: true,
unset: 'destroy',
store: new mongoStore({
db: mongodb.connection.db,
collection: 'sessions'
})
}));
app.use(passport.initialize());
app.use(passport.session());
app.use('/', routes);
app.set('port', config.port);
var server = app.listen(app.get('port'), function() {
if (config.debug) {
debug('Express server listening on port ' + server.address().port);
}
});
routes.js:
var express = require('express');
var router = express.Router();
var config = require('../config');
var userController = require('../controllers/user');
var authController = require('../controllers/auth');
router.get('/', function(req, res) {
res.render('index', {
title: config.app.name
});
});
router.route('/users')
.post(userController.postUsers)
.get(authController.isAuthenticated, userController.getUsers);
router.get('/signout', userController.signout);
module.exports = router;
models/user.js:
var mongoose = require('mongoose');
var bcrypt = require('bcrypt-nodejs');
var UserSchema = new mongoose.Schema({
username: {
type: String,
unique: true,
required: true
},
password: {
type: String,
required: true
}
});
// Execute before each user.save() call
UserSchema.pre('save', function(callback) {
var user = this;
// Break out if the password hasn't changed
if (!user.isModified('password')) return callback();
// Password changed so we need to hash it
bcrypt.genSalt(5, function(err, salt) {
if (err) return callback(err);
bcrypt.hash(user.password, salt, null, function(err, hash) {
if (err) return callback(err);
user.password = hash;
callback();
});
});
});
UserSchema.methods.verifyPassword = function(password, cb) {
bcrypt.compare(password, this.password, function(err, isMatch) {
if (err) return cb(err);
cb(null, isMatch);
});
};
// Export the Mongoose model
module.exports = mongoose.model('User', UserSchema);
controllers/user.js:
var config = require('../config');
var User = require('../models/user');
exports.postUsers = function(req, res) {
if (config.debug)
console.log("user.postUsers()");
var user = new User({
username: req.body.username,
password: req.body.password
});
user.save(function(err) {
if (err)
return res.send(err);
if (config.debug)
console.log("saved");
res.json({
message: 'New user created!'
});
});
};
exports.getUsers = function(req, res) {
if (config.debug)
console.log("user.getUsers()");
User.find(function(err, users) {
if (err)
return res.send(err);
if (config.debug)
console.log("users", users);
res.json(users);
});
};
exports.signout = function(req, res) {
if (config.debug)
console.log("user.signout()");
res.clearCookie('myCookie');
req.session.destroy(function(err) {
req.logout();
res.redirect('/');
});
};
controllers/auth.js:
var passport = require('passport');
var BasicStrategy = require('passport-http').BasicStrategy;
var config = require('../config');
var User = require('../models/user');
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
User.findById(id, function(err, user) {
done(err, user);
});
});
passport.use(new BasicStrategy(
function(username, password, done) {
User.findOne({
username: username
}, function(err, user) {
if (err) {
return done(err);
}
// No user found with that username
if (!user) {
return done(null, false);
}
// Make sure the password is correct
user.verifyPassword(password, function(err, isMatch) {
if (err) {
return done(err);
}
// Password did not match
if (!isMatch) {
return done(null, false);
}
// Success
return done(null, user);
});
});
}
));
exports.isAuthenticated = passport.authenticate('basic', {
session: false
});
The problem
/signout route does not end the current session. In the req.session.destroy callback the req.session is undefined, yet a new GET request to /users acts like the session is valid.
Can someone help clear this problem out?
If, like me, you came here as a result of question title rather than full details- the answer is req.session.destroy(). I think the logout function is particular to passport.js and will not work if you are using standard express-session.
Solution
controllers/user.js:
exports.signout = function(req, res) {
if (config.debug)
console.log("user.signout()");
req.logout();
res.send(401);
};
Btw. don't mind the session(s) still being in DB immediately after the logout. Mongod checks and clears those out after 60 s.
in sign out api without using req.session.destroy() try req.logout();. I hope it will work.
In my case the server-side code was fine. It was the client-side code where I wasn't including the withCredentials parameter when making the http request.
Below is the correct working code.
// server side (nodejs)
authRouter.post("/logout",
passport.session(),
checkAuthenticationHandler,
async (req, res, next) => {
req.logOut(err => {
if (err) next(err)
res.status(http.statusCodes.NO_CONTENT).end()
})
})
// client side (reactjs)
export const logout = async () => {
const _response = await axios({
method: 'post',
url: `${authApi}/auth/logout`,
withCredentials: true
})
}

NodeJS using Passport and Facebook

I almost have Passport authenticating via Facebook, however the FacebookStrategy callback in my Model is never being called so not able to get profile information. I must be missing something, but not having any luck figuring it out.
My App.js
var express = require('express');
var http = require('http');
var path = require('path');
var mongoose = require('mongoose');
var fs = require('fs');
var io = require('socket.io');
var config = require("./server/config");
var passport = require("passport");
//init Express
var app = express();
var allowCrossDomain = function(req, res, next) {
res.header('Access-Control-Allow-Origin', "*");
res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
res.header('Access-Control-Allow-Headers', 'X-Requested-With,content-type');
res.header('Access-Control-Allow-Credentials', true);
next();
}
//client code could be found here
var clientDir = path.join(__dirname, 'www');
//set up Node app configurations
app.configure(function(){
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.cookieParser());
app.use(express.bodyParser());
app.use(express.session({
secret: 'adasdasdasdasdasdasdasdasdasdasd'
}));
app.use(passport.initialize());
app.use(passport.session());
app.use(express.methodOverride());
app.use(allowCrossDomain);
app.use(app.router);
app.use(express.static(clientDir));
});
// Mongo startup
var mongoUri = config.development.mongoUrl;
var db = mongoose.connect(mongoUri);
mongoose.set('debug', true);
mongoose.connection.on("open", function() {
console.log("Mongo Open on: " + mongoUri);
init();
});
mongoose.connection.on("error", function() {
console.log("Mongo ERROR");
});
var init = function(){
var models = {
User: require('./server/models/User')(app, mongoose, config, passport)
};
//load up routes
require('./server/routes/FacebookAuth')(app, models, config, passport);
}
// Create an http server
app.server = http.createServer(app);
//go to 'client' index page
app.get('/', function(req, res){
res.sendfile(path.join(clientDir, 'index.html'));
});
var portNum = process.env.PORT || 3004;
app.server.listen(portNum, function(){
var addr = app.server.address();
console.log(' app listening on http://' + addr.address + ':' + addr.port);
});
My Route
module.exports = function (app, models, config, passport) {
app.get('/auth/facebook', passport.authenticate('facebook', {
scope: ['read_stream', 'publish_actions']
}));
app.get('/auth/facebook/callback',
passport.authenticate('facebook', {
successRedirect: '/#/maker/123456',
failureRedirect: '/#/login'
}));
}
My Model
module.exports = function (app, mongoose, config, passport) {
var FacebookStrategy = require('passport-facebook').Strategy;
var UserSchema = new mongoose.Schema({
fbId: String,
name: String,
email: {
type:String,
lowercase: true
}
});
console.log("+++++++++++++ User Model");
var User = mongoose.model('User', UserSchema);
passport.serializeUser(function(user, done) {
done(null, user);
});
passport.deserializeUser(function(obj, done) {
done(null, obj);
});
passport.use(new FacebookStrategy({
clientID : config.development.fb.appId,
clientSecret: config.development.fb.appSecret,
callbackURL: config.development.fb.url + '/#/maker/12345'
},
function(acccessToken, refreshToken, profile, done){
//THIS IS NEVER BEING CALLED!
console.log("################################");
process.nextTick(function(){
return done(null, profile);
console.log("################################");
})
}
));
/*****************
* Public API
*****************/
return {
User: User,
/***GET***/
//Get All Items
//Get "one" item by Id
findById: function (id, items, callback) {
User.findById(id, items, function (err, doc) {
callback(doc);
});
},
//Get All Items
findAll: function (callback) {
User.find({}, function (err, doc) {
callback(doc);
});
}
}
}
I misunderstood who the strategy worked
This is what I have
passport.use(new FacebookStrategy({
clientID : config.development.fb.appId,
clientSecret: config.development.fb.appSecret,
callbackURL: config.development.fb.url + '/#/maker/12345'
It should actually have have a callback URL of
/auth/facebook/callback
then the redirect happens in the callback

Resources