├── public ├── img │ ├── astromap.jpg │ └── history-of-astrology.png ├── style.css └── main.js ├── .gitignore ├── config ├── database.js └── passport.js ├── package.json ├── README.md ├── views ├── index.ejs ├── connect-local.ejs ├── login.ejs ├── signup.ejs └── profile.ejs ├── LICENSE ├── app ├── models │ └── user.js └── routes.js └── server.js /public/img/astromap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeeDaniels/Express-Auth-App/HEAD/public/img/astromap.jpg -------------------------------------------------------------------------------- /public/img/history-of-astrology.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeeDaniels/Express-Auth-App/HEAD/public/img/history-of-astrology.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | npm-debug.log 15 | node_modules 16 | -------------------------------------------------------------------------------- /config/database.js: -------------------------------------------------------------------------------- 1 | // config/database.js 2 | module.exports = { 3 | 4 | 'url' : 'mongodb://astrodb:libra94@ds129811.mlab.com:29811/astrodb' // looks like mongodb://:@mongo.onmodulus.net:27017/Mikha4ot 5 | 6 | }; 7 | -------------------------------------------------------------------------------- /public/style.css: -------------------------------------------------------------------------------- 1 | body{ 2 | padding-top:80px; 3 | word-wrap:break-word; 4 | 5 | } 6 | img{ 7 | height: 200px; 8 | } 9 | body{ 10 | background-color: black; 11 | color:#98C6DD; 12 | } 13 | .jumbotron{ 14 | background-color: white; 15 | } 16 | h1{ 17 | font-family: fantasy; 18 | } 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-authentication", 3 | "main": "server.js", 4 | "dependencies": { 5 | "bcrypt-nodejs": "latest", 6 | "body-parser": "~1.15.2", 7 | "connect-flash": "~0.1.1", 8 | "cookie-parser": "~1.4.3", 9 | "ejs": "~2.5.2", 10 | "express": "~4.14.0", 11 | "express-session": "~1.14.1", 12 | "method-override": "~2.3.6", 13 | "mongoose": "~4.13.1", 14 | "morgan": "~1.7.0", 15 | "nodemon": "^1.17.5", 16 | "passport": "~0.3.2", 17 | "passport-facebook": "~2.1.1", 18 | "passport-google-oauth": "~1.0.0", 19 | "passport-local": "~1.0.0", 20 | "passport-twitter": "~1.0.4" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Astrology Forum w/ User Authorization 3 | A forum for fans of pastrology to connect with a message board on the profile page. 4 | 5 | ![alt text](https://i.imgur.com/PmkM4gQ.png) 6 | 7 | ## How It's Made: HTML, CSS, JavaScript, Node, Express, MongoDB, EJS template 8 | 9 | ## Optimizations: I plan to add more styling and editing features to this project. 10 | 11 | ## Lessons Learned: 12 | - Creating a database 13 | - How to use EJS templates 14 | - How to set up user Authorization 15 | 16 | ## Installation 17 | 18 | 1. Clone repo 19 | 2. run `npm install` 20 | 21 | ## Usage 22 | 23 | 1. run `npm run savage` 24 | 2. Navigate to `localhost:8000` 25 | 26 | ## Credit 27 | 28 | Modified from Scotch.io's auth tutorial 29 | -------------------------------------------------------------------------------- /views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | AstroPortal 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 |
13 |

AstroPortal

14 | 21 Savage Profile Photo 15 | 16 |

Login or Signup:

17 | 18 | Login 19 | Signup 20 | 21 |
22 | 23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 RC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /views/connect-local.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 Savage Fan Site 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 | 13 |

Add Local Account

14 | 15 | <% if (message.length > 0) { %> 16 |
<%= message %>
17 | <% } %> 18 | 19 | 20 |
21 |
22 | 23 | 24 |
25 |
26 | 27 | 28 |
29 | 30 | 31 |
32 | 33 |
34 | 35 |

Go back to profile

36 | 37 | 38 |
39 |
40 | 41 | 42 | -------------------------------------------------------------------------------- /views/login.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | AstroPortal 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 | 13 |

Login

14 | 15 | <% if (message.length > 0) { %> 16 |
<%= message %>
17 | <% } %> 18 | 19 | 20 |
21 |
22 | 23 | 24 |
25 |
26 | 27 | 28 |
29 | 30 | 31 |
32 | 33 |
34 | 35 |

Need an account? Signup

36 |

Or go home.

37 | 38 | 39 |
40 |
41 | 42 | 43 | -------------------------------------------------------------------------------- /app/models/user.js: -------------------------------------------------------------------------------- 1 | // load the things we need 2 | var mongoose = require('mongoose'); 3 | var bcrypt = require('bcrypt-nodejs'); 4 | 5 | // define the schema for our user model 6 | var userSchema = mongoose.Schema({ 7 | 8 | local : { 9 | email : String, 10 | password : String 11 | }, 12 | facebook : { 13 | id : String, 14 | token : String, 15 | name : String, 16 | email : String 17 | }, 18 | twitter : { 19 | id : String, 20 | token : String, 21 | displayName : String, 22 | username : String 23 | }, 24 | google : { 25 | id : String, 26 | token : String, 27 | email : String, 28 | name : String 29 | } 30 | 31 | }); 32 | 33 | // generating a hash 34 | userSchema.methods.generateHash = function(password) { 35 | return bcrypt.hashSync(password, bcrypt.genSaltSync(8), null); 36 | }; 37 | 38 | // checking if password is valid 39 | userSchema.methods.validPassword = function(password) { 40 | return bcrypt.compareSync(password, this.local.password); 41 | }; 42 | 43 | // create the model for users and expose it to our app 44 | module.exports = mongoose.model('User', userSchema); 45 | -------------------------------------------------------------------------------- /views/signup.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 Savage Fan Site 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 | 13 |

Signup

14 | 15 | <% if (message.length > 0) { %> 16 |
<%= message %>
17 | <% } %> 18 | 19 | 20 |
21 |
22 | 23 | 24 |
25 |
26 | 27 | 28 |
29 | 30 | 31 |
32 | 33 |
34 | 35 |

Already have an account? Login

36 |

Or go home.

37 | 38 |
39 |
40 | 41 | 42 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | // server.js 2 | 3 | // set up ====================================================================== 4 | // get all the tools we need 5 | var express = require('express'); 6 | var app = express(); 7 | var port = process.env.PORT || 8080; 8 | var mongoose = require('mongoose'); 9 | var passport = require('passport'); 10 | var flash = require('connect-flash'); 11 | 12 | var morgan = require('morgan'); 13 | var cookieParser = require('cookie-parser'); 14 | var bodyParser = require('body-parser'); 15 | var session = require('express-session'); 16 | 17 | var configDB = require('./config/database.js'); 18 | 19 | var db 20 | 21 | // configuration =============================================================== 22 | mongoose.connect(configDB.url, { useMongoClient: true }, (err, database) => { 23 | if (err) return console.log(err) 24 | db = database 25 | require('./app/routes.js')(app, passport, db); 26 | }); // connect to our database 27 | 28 | 29 | 30 | require('./config/passport')(passport); // pass passport for configuration 31 | 32 | // set up our express application 33 | app.use(morgan('dev')); // log every request to the console 34 | app.use(cookieParser()); // read cookies (needed for auth) 35 | app.use(bodyParser.json()); // get information from html forms 36 | app.use(bodyParser.urlencoded({ extended: true })); 37 | app.use(express.static('public')) 38 | 39 | app.set('view engine', 'ejs'); // set up ejs for templating 40 | 41 | // required for passport 42 | app.use(session({ 43 | secret: 'rcbootcamp2018a', // session secret 44 | resave: true, 45 | saveUninitialized: true 46 | })); 47 | app.use(passport.initialize()); 48 | app.use(passport.session()); // persistent login sessions 49 | app.use(flash()); // use connect-flash for flash messages stored in session 50 | 51 | 52 | // routes ====================================================================== 53 | //require('./app/routes.js')(app, passport, db); // load our routes and pass in our app and fully configured passport 54 | 55 | // launch ====================================================================== 56 | app.listen(port); 57 | console.log('The magic happens on port ' + port); 58 | -------------------------------------------------------------------------------- /public/main.js: -------------------------------------------------------------------------------- 1 | var thumbUp = document.getElementsByClassName("fa-thumbs-up"); 2 | var trash = document.getElementsByClassName("fa fa-times"); 3 | var thumbsDown = document.getElementsByClassName("fa fa-thumbs-down"); 4 | 5 | Array.from(thumbUp).forEach(function(element) { 6 | element.addEventListener('click', function(){ 7 | const name = this.parentNode.parentNode.childNodes[1].innerText 8 | const msg = this.parentNode.parentNode.childNodes[3].innerText 9 | const thumbUp = parseFloat(this.parentNode.parentNode.childNodes[5].innerText) 10 | fetch('messagesUp', { 11 | method: 'put', 12 | headers: {'Content-Type': 'application/json'}, 13 | body: JSON.stringify({ 14 | 'name': name, 15 | 'msg': msg, 16 | 'thumbUp':thumbUp, 17 | }) 18 | }) 19 | .then(response => { 20 | if (response.ok) return response.json() 21 | }) 22 | .then(data => { 23 | console.log(data) 24 | window.location.reload(true) 25 | }) 26 | }); 27 | }); 28 | 29 | Array.from(thumbsDown).forEach(function(element) { 30 | element.addEventListener('click', function(){ 31 | const name = this.parentNode.parentNode.childNodes[1].innerText 32 | const msg = this.parentNode.parentNode.childNodes[3].innerText 33 | const thumbUpCount = parseFloat(this.parentNode.parentNode.childNodes[5].innerText) 34 | console.log('works') 35 | fetch('messagesDown', { 36 | method: 'put', 37 | headers: {'Content-Type': 'application/json'}, 38 | body: JSON.stringify({ 39 | 'name': name, 40 | 'msg': msg, 41 | 'thumbUp':thumbUpCount, 42 | }) 43 | }) 44 | .then(response => { 45 | if (response.ok) return response.json() 46 | }) 47 | .then(data => { 48 | console.log(data) 49 | window.location.reload(true) 50 | }) 51 | }); 52 | }); 53 | 54 | Array.from(trash).forEach(function(element) { 55 | element.addEventListener('click', function(){ 56 | const name = this.parentNode.parentNode.childNodes[1].innerText 57 | const msg = this.parentNode.parentNode.childNodes[3].innerText 58 | fetch('messages', { 59 | method: 'delete', 60 | headers: { 61 | 'Content-Type': 'application/json' 62 | }, 63 | body: JSON.stringify({ 64 | 'name': name, 65 | 'msg': msg 66 | }) 67 | }).then(function (response) { 68 | window.location.reload() 69 | }) 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /views/profile.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | AstroPortal 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 18 | 19 |
20 | 21 | 22 |
23 |
24 |

Local

25 | 26 | <% if (user.local.email) { %> 27 |

28 | id: <%= user._id %>
29 | email: <%= user.local.email %>
30 | password: <%= user.local.password %> 31 |

32 | 33 | Unlink 34 | <% } else { %> 35 | Connect Local 36 | <% } %> 37 | 38 |
39 |
40 |
41 |

Messages

42 |
    43 | <% for(var i=0; i 44 |
  • 45 | <%= messages[i].name %>: 46 | <%= messages[i].msg %> 47 | <%= messages[i].thumbUp %> 48 | <%= messages[i].astro %> 49 | 50 | 51 | 52 |
  • 53 | <% } %> 54 |
55 | 56 |

Add a message

57 | <% if (user.local.email) { %> 58 |
59 | 60 | 61 | 62 | 63 |
64 |
65 | <% } %> 66 | 67 |
68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /config/passport.js: -------------------------------------------------------------------------------- 1 | // config/passport.js 2 | 3 | // load all the things we need 4 | var LocalStrategy = require('passport-local').Strategy; 5 | 6 | // load up the user model 7 | var User = require('../app/models/user'); 8 | 9 | // expose this function to our app using module.exports 10 | module.exports = function(passport) { 11 | 12 | // ========================================================================= 13 | // passport session setup ================================================== 14 | // ========================================================================= 15 | // required for persistent login sessions 16 | // passport needs ability to serialize and unserialize users out of session 17 | 18 | // used to serialize the user for the session 19 | passport.serializeUser(function(user, done) { 20 | done(null, user.id); 21 | }); 22 | 23 | // used to deserialize the user 24 | passport.deserializeUser(function(id, done) { 25 | User.findById(id, function(err, user) { 26 | done(err, user); 27 | }); 28 | }); 29 | 30 | // ========================================================================= 31 | // LOCAL SIGNUP ============================================================ 32 | // ========================================================================= 33 | // we are using named strategies since we have one for login and one for signup 34 | // by default, if there was no name, it would just be called 'local' 35 | 36 | passport.use('local-signup', new LocalStrategy({ 37 | // by default, local strategy uses username and password, we will override with email 38 | usernameField : 'email', 39 | passwordField : 'password', 40 | passReqToCallback : true // allows us to pass back the entire request to the callback 41 | }, 42 | function(req, email, password, done) { 43 | 44 | // find a user whose email is the same as the forms email 45 | // we are checking to see if the user trying to login already exists 46 | User.findOne({ 'local.email' : email }, function(err, user) { 47 | // if there are any errors, return the error 48 | if (err) 49 | return done(err); 50 | 51 | // check to see if theres already a user with that email 52 | if (user) { 53 | return done(null, false, req.flash('signupMessage', 'That email is already taken.')); 54 | } else { 55 | 56 | // if there is no user with that email 57 | // create the user 58 | var newUser = new User(); 59 | 60 | // set the user's local credentials 61 | newUser.local.email = email; 62 | newUser.local.password = newUser.generateHash(password); // use the generateHash function in our user model 63 | 64 | // save the user 65 | newUser.save(function(err) { 66 | if (err) 67 | throw err; 68 | return done(null, newUser); 69 | }); 70 | } 71 | 72 | }); 73 | 74 | })); 75 | 76 | // ========================================================================= 77 | // LOCAL LOGIN ============================================================= 78 | // ========================================================================= 79 | // we are using named strategies since we have one for login and one for signup 80 | // by default, if there was no name, it would just be called 'local' 81 | 82 | passport.use('local-login', new LocalStrategy({ 83 | // by default, local strategy uses username and password, we will override with email 84 | usernameField : 'email', 85 | passwordField : 'password', 86 | passReqToCallback : true // allows us to pass back the entire request to the callback 87 | }, 88 | function(req, email, password, done) { // callback with email and password from our form 89 | 90 | // find a user whose email is the same as the forms email 91 | // we are checking to see if the user trying to login already exists 92 | User.findOne({ 'local.email' : email }, function(err, user) { 93 | // if there are any errors, return the error before anything else 94 | if (err) 95 | return done(err); 96 | 97 | // if no user is found, return the message 98 | if (!user) 99 | return done(null, false, req.flash('loginMessage', 'No user found.')); // req.flash is the way to set flashdata using connect-flash 100 | 101 | // if the user is found but the password is wrong 102 | if (!user.validPassword(password)) 103 | return done(null, false, req.flash('loginMessage', 'Oops! Wrong password.')); // create the loginMessage and save it to session as flashdata 104 | 105 | // all is well, return successful user 106 | return done(null, user); 107 | }); 108 | 109 | })); 110 | 111 | }; 112 | -------------------------------------------------------------------------------- /app/routes.js: -------------------------------------------------------------------------------- 1 | module.exports = function(app, passport, db) { 2 | 3 | // normal routes =============================================================== 4 | 5 | // show the home page (will also have our login links) 6 | app.get('/', function(req, res) { 7 | res.render('index.ejs'); 8 | }); 9 | 10 | // PROFILE SECTION ========================= 11 | app.get('/profile', isLoggedIn, function(req, res) { 12 | db.collection('messages').find().toArray((err, result) => { 13 | if (err) return console.log(err) 14 | res.render('profile.ejs', { 15 | user : req.user, 16 | messages: result 17 | }) 18 | }) 19 | }); 20 | 21 | // LOGOUT ============================== 22 | app.get('/logout', function(req, res) { 23 | req.logout(); 24 | res.redirect('/'); 25 | }); 26 | 27 | // message board routes =============================================================== 28 | 29 | app.post('/messages', (req, res) => { 30 | db.collection('messages').save({name: req.body.name, msg: req.body.msg, astro: req.body.astro, thumbUp: 0, thumbDown:0}, (err, result) => { 31 | if (err) return console.log(err) 32 | console.log(sign) 33 | res.redirect('/profile') 34 | }) 35 | }) 36 | 37 | app.put('/messagesUp', (req, res) => { 38 | db.collection('messages') 39 | .findOneAndUpdate({name: req.body.name, msg: req.body.msg}, { 40 | $set: { 41 | thumbUp:req.body.thumbUp + 1 42 | } 43 | }, { 44 | sort: {_id: -1}, 45 | upsert: true 46 | }, (err, result) => { 47 | if (err) return res.send(err) 48 | res.send(result) 49 | }) 50 | }) 51 | app.put('/messagesDown', (req, res) => { 52 | db.collection('messages') 53 | .findOneAndUpdate({name: req.body.name, msg: req.body.msg}, { 54 | $set: { 55 | thumbUp:req.body.thumbUp - 1 56 | } 57 | }, { 58 | sort: {_id: -1}, 59 | upsert: true 60 | }, (err, result) => { 61 | if (err) return res.send(err) 62 | res.send(result) 63 | }) 64 | }) 65 | 66 | app.delete('/messages', (req, res) => { 67 | db.collection('messages').findOneAndDelete({name: req.body.name, msg: req.body.msg}, (err, result) => { 68 | if (err) return res.send(500, err) 69 | res.send('Message deleted!') 70 | }) 71 | }) 72 | 73 | // ============================================================================= 74 | // AUTHENTICATE (FIRST LOGIN) ================================================== 75 | // ============================================================================= 76 | 77 | // locally -------------------------------- 78 | // LOGIN =============================== 79 | // show the login form 80 | app.get('/login', function(req, res) { 81 | res.render('login.ejs', { message: req.flash('loginMessage') }); 82 | }); 83 | 84 | // process the login form 85 | app.post('/login', passport.authenticate('local-login', { 86 | successRedirect : '/profile', // redirect to the secure profile section 87 | failureRedirect : '/login', // redirect back to the signup page if there is an error 88 | failureFlash : true // allow flash messages 89 | })); 90 | 91 | // SIGNUP ================================= 92 | // show the signup form 93 | app.get('/signup', function(req, res) { 94 | res.render('signup.ejs', { message: req.flash('signupMessage') }); 95 | }); 96 | 97 | // process the signup form 98 | app.post('/signup', passport.authenticate('local-signup', { 99 | successRedirect : '/profile', // redirect to the secure profile section 100 | failureRedirect : '/signup', // redirect back to the signup page if there is an error 101 | failureFlash : true // allow flash messages 102 | })); 103 | 104 | // ============================================================================= 105 | // UNLINK ACCOUNTS ============================================================= 106 | // ============================================================================= 107 | // used to unlink accounts. for social accounts, just remove the token 108 | // for local account, remove email and password 109 | // user account will stay active in case they want to reconnect in the future 110 | 111 | // local ----------------------------------- 112 | app.get('/unlink/local', isLoggedIn, function(req, res) { 113 | var user = req.user; 114 | user.local.email = undefined; 115 | user.local.password = undefined; 116 | user.save(function(err) { 117 | res.redirect('/profile'); 118 | }); 119 | }); 120 | 121 | }; 122 | 123 | // route middleware to ensure user is logged in 124 | function isLoggedIn(req, res, next) { 125 | if (req.isAuthenticated()) 126 | return next(); 127 | 128 | res.redirect('/'); 129 | } 130 | --------------------------------------------------------------------------------