0

I'm encountering an issue where users remain logged in even after their accounts have been deleted by an admin in my Express.js application using MongoDB for user data storage.

When an admin deletes a user's account, the user remains logged in and can still access the system. This occurs even if the admin performs the deletion action while the user is actively logged in on another tab or session.

Steps to Reproduce:

  • Log in as a user on one browser tab.
  • Open an admin page in another tab.
  • Delete the user's account using the admin functionality.
  • Return to the user's logged-in page/tab and refresh the page.
  • Notice that the user is still logged in and can access the system.
// app.js 

const createError = require('http-errors');
const express = require('express');
const path = require('path');
const cookieParser = require('cookie-parser');
const logger = require('morgan');
const exphbs = require('express-handlebars');
const connectDB = require('./config/connection');
const session = require('express-session');
const MongoStore = require('connect-mongo');

const userRoutes = require('./routes/userRoutes');
const adminRoutes = require('./routes/adminRoutes');

const app = express();

// View engine setup with Handlebars
app.set('views', path.join(__dirname, 'views'));
app.engine('.hbs', exphbs.engine({
    extname: '.hbs',
    defaultLayout: 'main',
    layoutsDir: path.join(__dirname, 'views', 'layout'),
    partialsDir: path.join(__dirname, 'views', 'partials'),
    runtimeOptions: {
        allowProtoPropertiesByDefault: true,
        allowProtoMethodsByDefault: true
    }
}));
app.set('view engine', '.hbs');

// Middleware setup
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use(session({
    secret: 'your-secret-key',
    resave: false,
    saveUninitialized: true,
    store: MongoStore.create({ mongoUrl: 'mongodb://localhost:27017/your-db' }),
    cookie: { sameSite: 'strict', maxAge: 180 * 60 * 1000 }
}));


// Connect to the database
connectDB();

// Routes
app.use('/admin', adminRoutes);
app.use('/', userRoutes);


// adminController.js

const Admin = require('../models/admin');
const User = require('../models/users');
const bcrypt = require('bcrypt');

exports.showLogin = (req, res) => {
  if (req.session.admin) {
    res.redirect('/admin/view-users');
  } else {
    res.render('admin/login', { loginErr: req.session.adminLoginErr, isAdmin: true });
    req.session.adminLoginErr = false;
  }
};

exports.login = async (req, res) => {
  try {
    const { username, password } = req.body;
    const admin = await Admin.findOne({ username });

    if (admin) {
      const isMatch = await bcrypt.compare(password, admin.password);
      if (isMatch) {
        req.session.admin = { username: admin.username, loggedIn: true, isAdmin: true };
        res.redirect('/admin/view-users');
      } else {
        req.session.adminLoginErr = true;
        res.redirect('/admin');
      }
    } else {
      req.session.adminLoginErr = true;
      res.redirect('/admin');
    }
  } catch (err) {
    console.error('Error in admin login:', err);
    res.status(500).json({ error: 'Internal Server Error' });
  }
};

exports.viewUsers = async (req, res) => {
  try {
    const users = await User.find().lean();
    const successMessage = req.session.successMessage;
    req.session.successMessage = null;
    res.render('admin/view-users', { isAdmin: req.isAdmin, admin: req.session.admin, users, successMessage });
  } catch (err) {
    console.error('Error fetching users:', err);
    res.status(500).json({ error: 'Internal Server Error' });
  }
};

exports.showAddUserForm = (req, res) => {
  res.render('admin/add-users', { isAdmin: req.isAdmin, admin: req.session.admin });
};

exports.addUser = async (req, res) => {
  try {
    const { username, email, password } = req.body;
    const hashedPassword = await bcrypt.hash(password, 10);
    const newUser = new User({ username, email, password: hashedPassword });
    await newUser.save();
    req.session.successMessage = 'User added successfully';
    res.redirect('/admin/view-users');
  } catch (err) {
    console.error('Error adding user:', err);
    res.status(500).json({ error: 'Internal Server Error' });
  }
};

exports.showEditUserForm = async (req, res) => {
  try {
    const user = await User.findById(req.params.id).lean();
    res.render('admin/edit-user', { isAdmin: req.isAdmin, admin: req.session.admin, user });
  } catch (err) {
    console.error('Error fetching user:', err);
    res.status(500).json({ error: 'Internal Server Error' });
  }
};

exports.editUser = async (req, res) => {
  try {
    const { username, email, password } = req.body;
    const updatedUser = { username, email };
    if (password) {
      updatedUser.password = await bcrypt.hash(password, 10);
    }
    await User.findByIdAndUpdate(req.params.id, updatedUser);
    req.session.successMessage = 'User updated successfully';
    res.redirect('/admin/view-users');
  } catch (err) {
    console.error('Error updating user:', err);
    res.status(500).json({ error: 'Internal Server Error' });
  }
};

exports.searchUsers = async (req, res) => {
  try {
    const query = req.query.query;
    const users = await User.find({
      $or: [
        { username: { $regex: query, $options: 'i' } },
        { email: { $regex: query, $options: 'i' } }
      ]
    }).lean();

    res.render('admin/view-users', {
      isAdmin: req.isAdmin,
      admin: req.session.admin,
      users,
      successMessage: `Search results for '${query}'`
    });
  } catch (err) {
    console.error('Error searching users:', err);
    res.status(500).json({ error: 'Internal Server Error' });
  }
};

exports.deleteUser = async (req, res) => {
  try {
    await User.findByIdAndDelete(req.params.id);
    req.session.successMessage = 'User deleted successfully';
    res.redirect('/admin/view-users');
  } catch (err) {
    console.error('Error deleting user:', err);
    res.status(500).json({ error: 'Internal Server Error' });
  }
};

exports.logout = (req, res) => {
  req.session.admin = null;
  res.redirect('/admin');
};
// userController.js

const User = require('../models/users');
const bcrypt = require('bcrypt');

exports.index = (req, res) => {
    res.render('user/index', { user: req.session.user });
  };
  

exports.showLogin = (req, res) => {
  if (req.session.user) {
    res.redirect('/');
  } else {
    res.render('user/login', { loginErr: req.session.userLoginErr });
    req.session.userLoginErr = false;
  }
};

exports.login = async (req, res) => {
  try {
    const { username, password } = req.body;
    const user = await User.findOne({ username });

    if (user) {
      const isMatch = await bcrypt.compare(password, user.password);
      if (isMatch) {
        req.session.user = { username: user.username, loggedIn: true };
        res.redirect('/');
      } else {
        req.session.userLoginErr = true;
        res.redirect('/login');
      }
    } else {
      req.session.userLoginErr = true;
      res.redirect('/login');
    }
  } catch (err) {
    console.error('Error in user login:', err);
    res.status(500).json({ error: 'Internal Server Error' });
  }
};

exports.showSignup = (req, res) => {
  res.render('user/signup', { signupErr: req.session.signupErr });
  req.session.signupErr = null;
};

exports.signup = async (req, res) => {
    try {
      const { username, email, password } = req.body;
  
      // Validation: Check for empty or whitespace-only fields
      if (!username.trim() || !email.trim() || !password.trim()) {
        req.session.signupErr = 'All fields are required and cannot be empty.';
        return res.redirect('/signup');
      }
  
      // Additional Validation: Check if the username is valid
      const usernameRegex = /^(?=.*[a-zA-Z0-9])[a-zA-Z0-9_.-]+$/;
      if (!usernameRegex.test(username)) {
        req.session.signupErr = 'Username must contain at least one alphanumeric character and can only contain letters, numbers, underscores, dots, and dashes.';
        return res.redirect('/signup');
      }
  
      // Check if the username or email already exists
      const existingUser = await User.findOne({ $or: [{ username }, { email }] });
      if (existingUser) {
        req.session.signupErr = 'Username or Email already exists.';
        return res.redirect('/signup');
      }
  
      const hashedPassword = await bcrypt.hash(password, 10);
      const newUser = new User({ username, email, password: hashedPassword });
      await newUser.save();
      req.session.user = { username: newUser.username, loggedIn: true };
      res.redirect('/');
    } catch (err) {
      console.error('Error in user signup:', err);
      res.status(500).json({ error: 'Internal Server Error' });
    }
  };

exports.logout = (req, res) => {
  req.session.user = null;
  res.redirect('/login');
};


1
  • 1
    You will have a collection name sessions that MongoStore automatically creates. If admin are deleting the user then at the same time you will need to remove the deleted users session from the sessions collection. Otherwise you will need to wait for the TTL index to clean up and remove expired sessions documents from the collection. As an aside you should use req.session.destroy() during the logout function as opposed to req.session.user = null to completely destroy the session.
    – jQueeny
    Commented Jul 9 at 13:08

1 Answer 1

0

Instead of updating req.session.user = null; to "null" you can destroy session which is recommended. Here is the part of code that need to be updated

'exports.logout = (req, res) => {
 req.session.destroy(function(err) {
    if (err) {
        console.log(err);
    } else {
        res.redirect('/login');
};'

Not the answer you're looking for? Browse other questions tagged or ask your own question.