From 235f3b611a3c976d1823d65072db50547d1ad4e1 Mon Sep 17 00:00:00 2001 From: notnull Date: Mon, 8 Jul 2019 14:40:40 -0400 Subject: [PATCH] implemented Passport local authentication --- .gitignore | 1 + api/index.js | 1 + db/models/user.js | 54 ++++++++++++++++++ db/seed.js | 4 +- index.js | 77 +++++++++++++++++++++++-- package-lock.json | 141 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 6 +- 7 files changed, 277 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 10000fa..2ca678f 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ /build npm-debug.log* +sessions diff --git a/api/index.js b/api/index.js index b3c9127..71778cb 100755 --- a/api/index.js +++ b/api/index.js @@ -3,6 +3,7 @@ module.exports = router const ascii = require('../ascii') router.use('/tasks', require('./tasks')) +router.use('/users', require('./users')) router.use('/projects', require('./projects')) router.get('/', async (req, res, next) => { diff --git a/db/models/user.js b/db/models/user.js index 0555b69..fd30b7f 100644 --- a/db/models/user.js +++ b/db/models/user.js @@ -1,4 +1,5 @@ const Sequelize = require('sequelize') +const crypto = require('crypto') const db = require('../db') const User = db.define('users', { @@ -11,6 +12,23 @@ const User = db.define('users', { type: Sequelize.STRING, }, + password: { + type: Sequelize.STRING, + // Making `.password` act like a func hides it when serializing to JSON. + // This is a hack to get around Sequelize's lack of a "private" option. + get() { + return () => this.getDataValue('password') + }, + }, + salt: { + type: Sequelize.STRING, + // Making `.salt` act like a function hides it when serializing to JSON. + // This is a hack to get around Sequelize's lack of a "private" option. + get() { + return () => this.getDataValue('salt') + }, + }, + avatar: { type: Sequelize.STRING, defaultValue: 'default-user-img.png', @@ -18,3 +36,39 @@ const User = db.define('users', { }) module.exports = User + +User.prototype.correctPassword = function(candidatePwd) { + return User.encryptPassword(candidatePwd, this.salt()) === this.password() +} + +/** + * classMethods + */ +User.generateSalt = function() { + return crypto.randomBytes(16).toString('base64') +} + +User.encryptPassword = function(plainText, salt) { + console.log('inside of encryptPassword', plainText, salt) + return crypto + .createHash('RSA-SHA256') + .update(plainText) + .update(salt) + .digest('hex') +} + +/** + * hooks + */ +const setSaltAndPassword = user => { + if (user.changed('password')) { + user.salt = User.generateSalt() + user.password = User.encryptPassword(user.password(), user.salt()) + } +} + +User.beforeCreate(setSaltAndPassword) +User.beforeUpdate(setSaltAndPassword) +User.beforeBulkCreate(users => { + users.forEach(setSaltAndPassword) +}) diff --git a/db/seed.js b/db/seed.js index b7d967a..388b354 100755 --- a/db/seed.js +++ b/db/seed.js @@ -22,8 +22,8 @@ const testTasks = [ const testProjects = [{ name: 'Anarchy Planet' }, { name: 'Tilde' }] const testUsers = [ - { name: 'nn', email: 'nn@ap.org' }, - { name: 'dn', email: 'dn@ap.org' }, + { name: 'nn', email: 'nn@ap.org', password: '123' }, + { name: 'dn', email: 'dn@ap.org', password: '123' }, ] async function runSeed() { diff --git a/index.js b/index.js index 7f6c5fe..729ec6b 100755 --- a/index.js +++ b/index.js @@ -4,20 +4,89 @@ const app = express() const morgan = require('morgan') const ascii = require('./ascii') const cors = require('cors') + +var passport = require('passport') +var Strategy = require('passport-local').Strategy +const session = require('express-session') +const FileStore = require('session-file-store')(session) + +const { User } = require('./db/models') + const port = process.env.PORT || 1337 +passport.use( + new Strategy({ usernameField: 'email' }, async (email, password, cb) => { + const user = await User.findOne({ where: { email: email } }) + if (!user) return cb(null, false) + if (!user.correctPassword(password)) return cb(null, false) + return cb(null, user) + }) +) + +passport.serializeUser((user, cb) => { + cb(null, user.id) +}) + +passport.deserializeUser(async (id, cb) => { + try { + const user = await User.findByPk(id) + cb(null, user) + } catch (err) { + return cb(err) + } +}) + app.use(morgan('tiny')) app.use(cors()) // body parsing middleware app.use(express.json()) app.use(express.urlencoded({ extended: true })) app.use(require('body-parser').text()) + +app.use( + session({ + store: new FileStore(), + secret: 'keyboard cat', + resave: false, + saveUninitialized: true, + }) +) +app.use(passport.initialize()) +app.use(passport.session()) + app.use('/api', require('./api')) -if (process.env.NODE_ENV === 'production') { - // Express will serve up production assets - app.use(express.static(path.join(__dirname, 'dist'))) -} +// if (process.env.NODE_ENV === 'production') { +// // Express will serve up production assets +// app.use(express.static(path.join(__dirname, 'dist'))) +// } + +app.get('/login', (req, res) => { + res.send('Not logged in.\n') +}) + +app.post('/login', async (req, res, next) => { + try { + const user = await User.findOne({ where: { email: req.body.email } }) + if (!user) { + console.log('User does not exist:', req.body.email) + res.status(401).send('Wrong username and/or password') + } else if (!user.correctPassword(req.body.password)) { + console.log('Incorrect password for user:', req.body.email) + res.status(401).send('Wrong username and/or password') + } else { + req.login(user, err => (err ? next(err) : res.json(user))) + } + } catch (err) { + next(err) + } +}) + +app.get('/logout', function(req, res) { + req.logout() + res.redirect('/') +}) + app.get('*', (req, res) => { res.sendFile(path.resolve(__dirname, '..', 'public', 'index.html')) }) diff --git a/package-lock.json b/package-lock.json index e624660..12dd038 100644 --- a/package-lock.json +++ b/package-lock.json @@ -145,6 +145,11 @@ } } }, + "bagpipe": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/bagpipe/-/bagpipe-0.3.5.tgz", + "integrity": "sha1-40HRZPyyTN8E6n4Ft2XsEMiupqE=" + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -795,6 +800,38 @@ "vary": "~1.1.2" } }, + "express-session": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.16.2.tgz", + "integrity": "sha512-oy0sRsdw6n93E9wpCNWKRnSsxYnSDX9Dnr9mhZgqUEEorzcq5nshGYSZ4ZReHFhKQ80WI5iVUUSPW7u3GaKauw==", + "requires": { + "cookie": "0.3.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-headers": "~1.0.2", + "parseurl": "~1.3.3", + "safe-buffer": "5.1.2", + "uid-safe": "~2.1.5" + }, + "dependencies": { + "cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" + }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" + } + } + }, "extend-shallow": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", @@ -962,6 +999,23 @@ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" }, + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "dependencies": { + "graceful-fs": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.0.tgz", + "integrity": "sha512-jpSvDPV4Cq/bgtpndIWbI5hmYxhQGHPC4d4cqBPb4DLniCfhJokdXhwhaDuLBGLQdvvRum/UiX6ECVIPvDXqdg==" + } + } + }, "fsevents": { "version": "1.2.9", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz", @@ -1846,6 +1900,14 @@ "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "requires": { + "graceful-fs": "^4.1.6" + } + }, "kind-of": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", @@ -2323,6 +2385,28 @@ "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=" }, + "passport": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.4.0.tgz", + "integrity": "sha1-xQlWkTR71a07XhgCOMORTRbwWBE=", + "requires": { + "passport-strategy": "1.x.x", + "pause": "0.0.1" + } + }, + "passport-local": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz", + "integrity": "sha1-H+YyaMkudWBmJkN+O5BmYsFbpu4=", + "requires": { + "passport-strategy": "1.x.x" + } + }, + "passport-strategy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", + "integrity": "sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ=" + }, "path-dirname": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", @@ -2353,6 +2437,11 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" }, + "pause": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", + "integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10=" + }, "pg": { "version": "7.11.0", "resolved": "https://registry.npmjs.org/pg/-/pg-7.11.0.tgz", @@ -2485,6 +2574,11 @@ "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" }, + "random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=" + }, "range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -2612,6 +2706,11 @@ "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==" }, + "retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=" + }, "retry-as-promised": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-3.2.0.tgz", @@ -2739,6 +2838,30 @@ "send": "0.17.1" } }, + "session-file-store": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/session-file-store/-/session-file-store-1.3.0.tgz", + "integrity": "sha512-NKGM/77UDGbV4iMGhrbauMrmrBn0JrLVUpwAFYGZSgI+1SCQYZ+K/neFbkX6x7Z8Wd7gFuEV+35dfjrNUnzLrw==", + "requires": { + "bagpipe": "^0.3.5", + "fs-extra": "^8.0.1", + "object-assign": "^4.1.1", + "retry": "^0.12.0", + "write-file-atomic": "1.3.1" + }, + "dependencies": { + "write-file-atomic": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-1.3.1.tgz", + "integrity": "sha1-fUW6MjFjKN0ex9kPYOvA2EW7dZo=", + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "slide": "^1.1.5" + } + } + } + }, "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", @@ -2793,6 +2916,11 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" }, + "slide": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", + "integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=" + }, "snapdragon": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", @@ -3165,6 +3293,14 @@ "mime-types": "~2.1.24" } }, + "uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "requires": { + "random-bytes": "~1.0.0" + } + }, "undefsafe": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.2.tgz", @@ -3213,6 +3349,11 @@ "crypto-random-string": "^1.0.0" } }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", diff --git a/package.json b/package.json index 4433bf8..1f67b27 100644 --- a/package.json +++ b/package.json @@ -8,11 +8,15 @@ "concurrently": "^4.0.1", "cors": "^2.8.5", "express": "^4.17.1", + "express-session": "^1.16.2", "http-proxy-middleware": "^0.19.0", "morgan": "^1.9.1", "nodemon": "^1.19.1", + "passport": "^0.4.0", + "passport-local": "^1.0.0", "pg": "^7.11.0", - "sequelize": "^5.8.11" + "sequelize": "^5.8.11", + "session-file-store": "^1.3.0" }, "scripts": { "seed": "node db/seed.js",