GithubHelp home page GithubHelp logo

basuabhirup / secret-app Goto Github PK

View Code? Open in Web Editor NEW
1.0 1.0 2.0 139 KB

This is a practice project to add user authentication for accessing certain parts of a web app.

Home Page: https://secrets-basuabhirup.herokuapp.com/

JavaScript 46.87% CSS 9.79% EJS 43.34%
backend-development user-authentication mongodb mongoose ejs-templates express authentication nodejs expressjs node-js

secret-app's Introduction

Secret App

This project is a part of "The Complete 2021 Web Development Bootcamp" by The London App Brewery, instructed by Dr. Angela Yu and hosted on Udemy.

Table of contents

Objective of this Project:

  • To create an app where the registered users can share secrets anonymously.
  • To restrict access to the secrets by all the non-registered users.
  • To add authentication to the app so that individual users would sign up with a username and password.
  • To enable users to log into the app using their username and password to access to the secrets.

Steps I have followed:

Basic Server Setup:

  1. Initialized NPM inside the project directory using npm init -y command from the terminal.
  2. Used npm i express body-parser ejs mongoose dotenv command to install these dependencies for our project.
  3. Initialized the server with the following code inside the app.js file:
// Require necessary NPM modules:
require('dotenv').config();
const express = require('express');
const bodyParser = require('body-parser');
const ejs = require('ejs');
const mongoose = require('mongoose');

// Assign a port for localhost as well as final deployement:
const port = process.env.PORT || 3000;

// Initial setup for the server:
const app = express();
app.use(express.static('public'));
app.set('view engine', 'ejs');
app.use(bodyParser.urlencoded({extended: true}));


// Connect to a new MongoDB Database, using Mongoose ODM:

// Create a new collection inside the database to store data:



// Handle HTTP requests:

// Handle 'GET' requests made on the '/' route:
app.get('/', (req, res) => {
  res.render('home');
})

// Handle 'GET' requests made on the '/register' route:
app.get('/register', (req, res) => {
  res.render('register');
})

// Handle 'GET' requests made on the '/login' route:
app.get('/login', (req, res) => {
  res.render('login');
})



// Enable client to listen to the appropriate port:
app.listen(port, () => {
  console.log(`Server started on port ${port}`);
});

  1. Initialized a local git repository with git init command.
  2. Created a .gitignore file to prevent all the unwanted files from being tracked:
node_modules/
npm-debug.log
.DS_Store
.env

  1. Created a .env file to store all the encryption keys / database passwords as environment variables and made sure that the .env file is not being tracked by git:
DB_USER=my_user_name
DB_PASS=Abc123xyz789

  1. Connected the server to a new database named userDB in MongoDB Atlas Cluster, using Mongoose ODM:
// Connect to a new MongoDB Database, using Mongoose ODM:
mongoose.connect(`mongodb+srv://${process.env.DB_USER}:${process.env.DB_PASS}@cluster0.m5s9h.mongodb.net/userDB`);

  1. Created a new collection named users inside userDB to store the emails and passwords of all the users:
// Create a new collection named 'users' to store the emails and passwords of the users:
const userSchema = new mongoose.Schema({
  email: String,
  password: String
})
const User = new mongoose.model('User', userSchema);

Experiment with 6 different levels of security:

Level 1 Security: Login with registered Username and Password

  • Handled the HTTP POST requests made on the /register route, so that it creates a new user document in the database to store their emails and passwords:
app.post('/register', (req, res) => {
  const user = new User({
    email: req.body.username,
    password: req.body.password
  })
  user.save(err => {
    if(err) {
      console.log(err);
    } else {
      res.render('secrets');
    }
  })
})

  • Handled the HTTP POST requests made on the /login route, so that the registered users can seamlessly login to the app with their credentials:
app.post('/login', (req, res) => {
  User.findOne({email: req.body.username}, (err, user) => {
    if(err) {
      res.send(err);
    } else {
      if(user) {
        if(user.password === req.body.password) {
          res.render('secrets');
        } else {
          res.send("Please check your password and try again...");
        }
      } else {
        res.send("Please check your username and try again...");
      }
    }
  })
})

Level 2 Security: Database Encryption

  • Installed the mongoose-encryption NPM module using npm i mongoose-encryption command and required the same inside our app.js file:
const encrypt = require('mongoose-encryption');

  • Created a long unguessable secret key as an environment variable inside the .env file and added an encryption package to our userSchema to keep the users' passwords encrypted:
// Add the encryption package to our userSchema before creating the User model:
let secret = process.env.SECRET_STRING;
userSchema.plugin(encrypt, { secret: secret, encryptedFields: ['password'] });

  • mongoose-encryption package is smart enough to decrypt the passwords from the database whenever POST requests are created on the /login route. Therefore, anyone having access to the server code in app.js file can get access to all the users' passwords.

Level 3 Security: Hashing passwords with MD5

  • Installed the md5 module using npm i md5 command and required the same in our app.js file:
const md5 = require('md5');

  • Modified the user object inside the handler function of the POST requests made on the /register route, so that instead of storing the users' passwords in our database, we use our Hash function MD5 to turn that into an irreversible hash.
const user = new User({
    email: req.body.username,
    password: md5(req.body.password)
  })

  • Modified the logic inside the handler function of the POST requests made on the /login route:
if(user.password === md5(req.body.password)) {
  res.render('secrets');
}

Level 4 Security: Salting and Hashing passwords with bcrypt

  • Installed the bcrypt module using npm i bcrypt command and required the same in our app.js file:
const bcrypt = require('bcrypt');

  • Modified handler of POST requests made on the /register route, to turn the users' passwords into a complex hash using 10 rounds of salting:
app.post('/register', (req, res) => {
  bcrypt.hash(req.body.password, 10, (err, hash) => {
    const user = new User({
      email: req.body.username,
      password: hash
    })
    user.save(err => {
      if(err) {
        console.log(err);
      } else {
        res.render('secrets');
      }
    })
  })  
})

  • Modified handler of POST requests made on the /login route:
if(user) {
  bcrypt.compare(req.body.password, user.password, (err, result) => {
    if(result === true) {
      res.render('secrets');
    } else {
      res.send("Please check your password and try again...");
    }
  })
} else {
  res.send("Please check your username and try again...");
}

Level 5 Security: Add Cookies and Sessions using Passport.js

  • Installed the passport, passport-local, passport-local-mongooseand express-session modules using npm i passport passport-local passport-local-mongoose express-session command from terminal and required them inside our app.js file, maintaining order as following:
const session = require('express-session');
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const passportLocalMongoose = require('passport-local-mongoose');

  • Created a SECRET_KEY as an environment variable inside the .env file, configured session below all the app.use commands and initialized passport right below this:
app.use(session({
  secret: process.env.SECRET_KEY,
  resave: false,
  saveUninitialized: false
}))
app.use(passport.initialize());
app.use(passport.session());

  • Added passportLocalMongoose plugin to the userSchema before creating the User model, so that it can salt and hash the users' passwords before saving them to the database:
userSchema.plugin(passportLocalMongoose);

  • Added passport-local configurations right below the User model:
// Add passport-local Configuration:
passport.use(new LocalStrategy(User.authenticate()));

passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser());

  • Handled the HTTP POST requests made on the /register route, using some methods from the passport and the passportLocalMongoose packages:
app.post('/register', (req, res) => {
  User.register({username: req.body.username}, req.body.password, (err, user) => {
    if(err){
      res.send(`
        <h3 style="font-family:sans-serif; color:red;">${err.message}</h3>
        <button type="button" style="font-size:1rem; cursor:pointer;" onclick="window.location.href='/register'">Go back to Registration Page</buton>
        `);
    } else {
      passport.authenticate('local')(req, res, () => {
        res.redirect('/secrets');
      })
    }
  })
})

  • Handled the HTTP POST requests made on the /login route, using some methods from the passport and the passportLocalMongoose packages:
app.post('/login', passport.authenticate('local', {
  successRedirect: '/secrets',
  failureRedirect: '/login',
}));

  • Handled the HTTP GET requests made on the /secrets route, so that only the logged in authenticated users can get access to the secrets:
app.get('/secrets', (req, res) => {
  if(req.isAuthenticated()) {
    res.render('secrets');
  } else {
    res.redirect('/login');
  }
})

  • Handled the HTTP 'GET' requests made on the '/logout' route to deauthenticate the user and end the user session, using the req.logout() method from the passport module:
app.get('/logout', (req, res) => {
  req.logout();
  res.redirect('/');
})

Level 6 Security: Implementing Third Party Sign-in using OAuth 2.0

  • Installed the passport-google-oauth20 and mongoose-findorcreate modules using npm i passport-google-oauth20 mongoose-findorcreate command and required them inside our app.js file:
const GoogleStrategy = require('passport-google-oauth20').Strategy;
const findOrCreate = require('mongoose-findorcreate');

  • Created a new project in the Google Developers Console, generated OAuth 2.0 client ID with source URI as http://localhost:3000, redirect URI as http://localhost:3000/auth/google/secrets and stored the credentials in our .env file as environment variables GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET.
  • Added the findOrCreate function as a plugin to the userSchema before creating the User model:
userSchema.plugin(findOrCreate);

  • Configured passport-google-OAuth20 strategy:
// Add passport-google-OAuth20 strategy configuration:
passport.use(new GoogleStrategy({
    clientID: process.env.GOOGLE_CLIENT_ID,
    clientSecret: process.env.GOOGLE_CLIENT_SECRET,
    callbackURL: 'http://localhost:3000/auth/google/secrets',

  },
  function(accessToken, refreshToken, profile, cb) {
    const id = profile.id;
    const userEmail = profile.emails[0].value;
    User.findOrCreate({ username: userEmail, googleId: id },
      (err, user) => {
      return cb(err, user);
    })
  }
))

  • Added a googleId field to the userSchema:
const userSchema = new mongoose.Schema({
  username: String,
  password: String,
  googleId: String
})

  • Created Google 'Sign Up' and 'Sign In' buttons to make GET requests on the /auth/google route:
  • Handled GET requests made on the /auth/google route:
app.get('/auth/google', passport.authenticate('google', {scope: ['profile', 'email'] }));

  • Handled GET requests made on the authorized redirect route /auth/google/secrets:
app.get('/auth/google/secrets',
  passport.authenticate('google', { failureRedirect: '/login' }),
  (req, res) => {
    res.redirect('/secrets');
  });

  • Modified passport-local configurations and updated the methods of serialize and deserialize users to make them compatible with all OAuth strategies:
passport.serializeUser(function(user, done) {
  done(null, user.id);
});

passport.deserializeUser(function(id, done) {
  User.findById(id, function(err, user) {
    done(err, user);
  });
});

  • Modified styles of the login button using bootstrap-social stylesheets.
  • Similary, created Facebook Sign-in authentication using passport-facebook module, with the help of its official documentation page. Created a Test App in Facebook Developers Console to test the authentication.
  • Fixed the back button issue by adding the following code to the handler function of the GET requests made on the /secrets route:
res.set('Cache-Control', 'no-store');

Finishing Up the App:

  • Created a new 'secrets' field inside our userSchema to store the secrets.
const userSchema = new mongoose.Schema({
  username: String,
  password: String,
  googleId: String,
  facebookId: String,
  secrets: [{secret: String}]
})

  • Handled GET requests made on the /submit route:
app.get('/submit', (req, res) => {
  res.set('Cache-Control', 'no-store');
  if(req.isAuthenticated()) {
    res.render('submit');
  } else {
    res.redirect('/login');
  }
})

  • Handled POST requests made on the /submit route:
app.post('/submit',(req, res) => {
  User.findOne({ _id: req.user._id}, (err, user) => {
    if(err) {
      console.log(err);
    } else {
      if(user) {
        user.secrets.push({secret: req.body.secret});
        user.save(() => {
          res.redirect('/secrets')
        });
      }
    }
  })
})

  • Updated the template code inside secrets.ejs file:
<% users.forEach(user => { %>
  <% user.secrets.forEach(secret => { %>
    <p class="secret-text"><%= secret.secret %></p>
  <% }) %>
<% }) %>

  • Modified handler of GET requests made on the /secrets route:
app.get('/secrets', (req, res) => {
  res.set('Cache-Control', 'no-store');
  if(req.isAuthenticated()) {
    User.find({'secret': {$ne: null}}, (err, users) => {
      if(err) {
        console.log(err);
      } else {
        if (users) {
          res.render('secrets', {users: users});
        }
      }
    })
  } else {
    res.redirect('/login');
  }
})

  • Finally, created a Procfile, replaced all localhost:3000 links with the live deployment URIs in both the server code as well as in Google and Facebook's Developers Console, and made all other necessary changes to prepare the codebase for deployment via Heroku server.

secret-app's People

Contributors

basuabhirup avatar anushabanerjee avatar

Stargazers

Roman avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.