Skip to content
This repository was archived by the owner on Dec 10, 2022. It is now read-only.

Commit 33ab11b

Browse files
committed
* user login logout signup actions
* passport config local strategy
1 parent 15eab81 commit 33ab11b

File tree

12 files changed

+939
-26
lines changed

12 files changed

+939
-26
lines changed

app.js

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,43 @@
11
var createError = require('http-errors');
22
var express = require('express');
3+
var session = require('express-session')
34
var path = require('path');
45
var cookieParser = require('cookie-parser');
56
var logger = require('morgan');
7+
var MongoStore = require('connect-mongo')(session)
68
var mongoose = require('mongoose')
9+
var dotenv = require('dotenv')
10+
var passport = require('passport')
11+
var flash = require('express-flash')
712

8-
var indexRouter = require('./routes/index');
9-
var usersRouter = require('./routes/users');
13+
dotenv.config({ path: '.env' });
14+
15+
const passportConfig = require('./config/passport')
1016

1117
var app = express();
1218

1319

1420
// database connection
1521
mongoose.set('useNewUrlParser', true)
16-
mongoose.connect(preocess.env.MONGODB_URI)
22+
mongoose.connect(process.env.MONGODB_URI)
1723
mongoose.connection.on('error', (err) => {
1824
console.error(err)
1925
console.log('%s MongoDB connection error.', chalk.red('✗'))
2026
process.exit()
2127
})
28+
app.use(session({
29+
resave: true,
30+
saveUninitialized: true,
31+
secret: process.env.SESSION_SECRET,
32+
cookie: { maxAge: 1209600000 }, // two weeks in milliseconds
33+
store: new MongoStore({
34+
url: process.env.MONGODB_URI,
35+
autoReconnect: true,
36+
})
37+
}));
38+
app.use(passport.initialize())
39+
app.use(passport.session())
40+
app.use(flash())
2241

2342
// view engine setup
2443
app.set('views', path.join(__dirname, 'views'));
@@ -30,8 +49,16 @@ app.use(express.urlencoded({ extended: false }));
3049
app.use(cookieParser());
3150
app.use(express.static(path.join(__dirname, 'public')));
3251

33-
app.use('/', indexRouter);
34-
app.use('/users', usersRouter);
52+
53+
const userController = require('./controllers/user')
54+
const homeController = require('./controllers/home')
55+
56+
app.get('/', homeController.index);
57+
app.get('/login', userController.getLogin)
58+
app.post('/login', userController.postLogin)
59+
app.get('/logout', userController.logout);
60+
app.get('/signup', userController.getSignup);
61+
app.post('/signup', userController.postSignup);
3562

3663
// catch 404 and forward to error handler
3764
app.use(function(req, res, next) {

config/passport.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
const passport = require('passport')
2+
const { Strategy: LocalStrategy} = require('passport-local')
3+
const axios = require('axios')
4+
const User = require('../models/Users')
5+
6+
passport.serializeUser((user, done) => {
7+
done(null, user.id);
8+
});
9+
10+
passport.deserializeUser((id, done) => {
11+
User.findById(id, (err, user) => {
12+
done(err, user);
13+
});
14+
});
15+
16+
/**
17+
* Sign in using Email and Password.
18+
*/
19+
passport.use(new LocalStrategy({ usernameField: 'email' }, (email, password, done) => {
20+
User.findOne({ email: email.toLowerCase() }, (err, user) => {
21+
if (err) { return done(err); }
22+
if (!user) {
23+
return done(null, false, { msg: `Email ${email} not found.` });
24+
}
25+
if (!user.password) {
26+
return done(null, false, { msg: 'Your account was registered using a sign-in provider. To enable password login, sign in using a provider, and then set a password under your user profile.' });
27+
}
28+
user.comparePassword(password, (err, isMatch) => {
29+
if (err) { return done(err); }
30+
if (isMatch) {
31+
return done(null, user);
32+
}
33+
return done(null, false, { msg: 'Invalid email or password.' });
34+
});
35+
});
36+
}));

controllers/home.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
* GET /
3+
* Home page.
4+
*/
5+
exports.index = (req, res) => {
6+
res.render('home', {
7+
title: 'Home'
8+
});
9+
};

controllers/user.js

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
const { promisify } = require('util');
2+
const crypto = require('crypto');
3+
const passport = require('passport');
4+
const validator = require('validator');
5+
const User = require('../models/Users');
6+
7+
const randomBytesAsync = promisify(crypto.randomBytes);
8+
9+
/**
10+
* GET /login
11+
* Login page.
12+
*/
13+
exports.getLogin = (req, res) => {
14+
if (req.user) {
15+
return res.redirect('/');
16+
}
17+
res.render('account/login', {
18+
title: 'Login'
19+
});
20+
};
21+
22+
/**
23+
* POST /login
24+
* Sign in using email and password.
25+
*/
26+
exports.postLogin = (req, res, next) => {
27+
const validationErrors = [];
28+
if (!validator.isEmail(req.body.email)) validationErrors.push({ msg: 'Please enter a valid email address.' });
29+
if (validator.isEmpty(req.body.password)) validationErrors.push({ msg: 'Password cannot be blank.' });
30+
31+
if (validationErrors.length) {
32+
req.flash('errors', validationErrors);
33+
return res.redirect('/login');
34+
}
35+
req.body.email = validator.normalizeEmail(req.body.email, { gmail_remove_dots: false });
36+
37+
passport.authenticate('local', (err, user, info) => {
38+
if (err) { return next(err); }
39+
if (!user) {
40+
req.flash('errors', info);
41+
return res.redirect('/login');
42+
}
43+
req.logIn(user, (err) => {
44+
if (err) { return next(err); }
45+
req.flash('success', { msg: 'Success! You are logged in.' });
46+
res.redirect(req.session.returnTo || '/');
47+
});
48+
})(req, res, next);
49+
};
50+
51+
/**
52+
* GET /logout
53+
* Log out.
54+
*/
55+
exports.logout = (req, res) => {
56+
req.logout();
57+
req.session.destroy((err) => {
58+
if (err) console.log('Error : Failed to destroy the session during logout.', err);
59+
req.user = null;
60+
res.redirect('/');
61+
});
62+
};
63+
64+
/**
65+
* GET /signup
66+
* Signup page.
67+
*/
68+
exports.getSignup = (req, res) => {
69+
if (req.user) {
70+
return res.redirect('/');
71+
}
72+
res.render('account/signup', {
73+
title: 'Create Account'
74+
});
75+
};
76+
77+
/**
78+
* POST /signup
79+
* Create a new local account.
80+
*/
81+
exports.postSignup = (req, res, next) => {
82+
const validationErrors = [];
83+
if (!validator.isEmail(req.body.email)) validationErrors.push({ msg: 'Please enter a valid email address.' });
84+
if (!validator.isLength(req.body.password, { min: 8 })) validationErrors.push({ msg: 'Password must be at least 8 characters long' });
85+
if (req.body.password !== req.body.confirmPassword) validationErrors.push({ msg: 'Passwords do not match' });
86+
87+
if (validationErrors.length) {
88+
req.flash('errors', validationErrors);
89+
return res.redirect('/signup');
90+
}
91+
req.body.email = validator.normalizeEmail(req.body.email, { gmail_remove_dots: false });
92+
93+
const user = new User({
94+
email: req.body.email,
95+
password: req.body.password
96+
});
97+
98+
User.findOne({ email: req.body.email }, (err, existingUser) => {
99+
if (err) { return next(err); }
100+
if (existingUser) {
101+
req.flash('errors', { msg: 'Account with that email address already exists.' });
102+
return res.redirect('/signup');
103+
}
104+
user.save((err) => {
105+
if (err) { return next(err); }
106+
req.logIn(user, (err) => {
107+
if (err) {
108+
return next(err);
109+
}
110+
res.redirect('/');
111+
});
112+
});
113+
});
114+
};
115+
116+
/**
117+
* GET /account
118+
* Profile page.
119+
*/
120+
exports.getAccount = (req, res) => {
121+
res.render('account/profile', {
122+
title: 'Account Management'
123+
});
124+
};
125+
126+
/**
127+
* POST /account/profile
128+
* Update profile information.
129+
*/
130+
exports.postUpdateProfile = (req, res, next) => {
131+
const validationErrors = [];
132+
if (!validator.isEmail(req.body.email)) validationErrors.push({ msg: 'Please enter a valid email address.' });
133+
134+
if (validationErrors.length) {
135+
req.flash('errors', validationErrors);
136+
return res.redirect('/account');
137+
}
138+
req.body.email = validator.normalizeEmail(req.body.email, { gmail_remove_dots: false });
139+
140+
User.findById(req.user.id, (err, user) => {
141+
if (err) { return next(err); }
142+
if (user.email !== req.body.email) user.emailVerified = false;
143+
user.email = req.body.email || '';
144+
user.name = req.body.name || '';
145+
user.gender = req.body.gender || '';
146+
user.save((err) => {
147+
if (err) {
148+
if (err.code === 11000) {
149+
req.flash('errors', { msg: 'The email address you have entered is already associated with an account.' });
150+
return res.redirect('/account');
151+
}
152+
return next(err);
153+
}
154+
req.flash('success', { msg: 'Profile information has been updated.' });
155+
res.redirect('/account');
156+
});
157+
});
158+
};
159+
160+
/**
161+
* POST /account/password
162+
* Update current password.
163+
*/
164+
exports.postUpdatePassword = (req, res, next) => {
165+
const validationErrors = [];
166+
if (!validator.isLength(req.body.password, { min: 8 })) validationErrors.push({ msg: 'Password must be at least 8 characters long' });
167+
if (req.body.password !== req.body.confirmPassword) validationErrors.push({ msg: 'Passwords do not match' });
168+
169+
if (validationErrors.length) {
170+
req.flash('errors', validationErrors);
171+
return res.redirect('/account');
172+
}
173+
174+
User.findById(req.user.id, (err, user) => {
175+
if (err) { return next(err); }
176+
user.password = req.body.password;
177+
user.save((err) => {
178+
if (err) { return next(err); }
179+
req.flash('success', { msg: 'Password has been changed.' });
180+
res.redirect('/account');
181+
});
182+
});
183+
};
184+
185+
/**
186+
* POST /account/delete
187+
* Delete user account.
188+
*/
189+
exports.postDeleteAccount = (req, res, next) => {
190+
User.deleteOne({ _id: req.user.id }, (err) => {
191+
if (err) { return next(err); }
192+
req.logout();
193+
req.flash('info', { msg: 'Your account has been deleted.' });
194+
res.redirect('/');
195+
});
196+
};

models/Users.js

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const mongoose = require('mongoose')
2+
const bcrypt = require('bcrypt')
23

34
const userSchema = new mongoose.Schema({
45
name: String,
@@ -11,13 +12,41 @@ const userSchema = new mongoose.Schema({
1112
trim: true,
1213
match: [/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/, 'Please fill a valid email address']
1314
},
14-
password: String
15+
password: String,
16+
passwordResetToken: String,
17+
passwordResetExpires: Date,
18+
emailVerificationToken: String,
19+
emailVerified: Boolean
1520

1621
}, { timestamps: true })
1722

23+
/**
24+
* Password hash middleware.
25+
*/
26+
userSchema.pre('save', function save(next) {
27+
const user = this;
28+
if (!user.isModified('password')) { return next(); }
29+
bcrypt.genSalt(10, (err, salt) => {
30+
if (err) { return next(err); }
31+
bcrypt.hash(user.password, salt, (err, hash) => {
32+
if (err) { return next(err); }
33+
user.password = hash;
34+
next();
35+
});
36+
});
37+
});
38+
39+
/**
40+
* Helper method for validating user's password.
41+
*/
42+
userSchema.methods.comparePassword = function comparePassword(candidatePassword, cb) {
43+
bcrypt.compare(candidatePassword, this.password, (err, isMatch) => {
44+
cb(err, isMatch);
45+
});
46+
};
47+
1848
const User = mongoose.model('User', userSchema)
1949

2050
module.exports = User
2151

22-
// TODO password
2352
// TODO Avatar picture from GRAVATAR?

0 commit comments

Comments
 (0)