From b82cb7f8eb332d89c65ed4180989e2ecdab96f04 Mon Sep 17 00:00:00 2001 From: "mike.rode" Date: Wed, 27 Feb 2019 13:42:18 -0600 Subject: [PATCH] WIP: User auth --- client/views/signin.hbs | 19 ++ client/views/signup.hbs | 23 +++ package-lock.json | 173 +++++++++++++++++- package.json | 5 + server/controllers/auth.controller.js | 10 + .../migrations/20190224043920-create-user.js | 14 ++ server/db/models/user.js | 14 ++ server/db/seeders/index.js | 1 + server/index.js | 23 +++ server/passport/passport.js | 60 ++++++ server/routes/auth.js | 6 + test/server/services/plex/importData.test.js | 89 ++++++--- 12 files changed, 399 insertions(+), 38 deletions(-) create mode 100644 client/views/signin.hbs create mode 100644 client/views/signup.hbs create mode 100644 server/controllers/auth.controller.js create mode 100644 server/passport/passport.js create mode 100644 server/routes/auth.js diff --git a/client/views/signin.hbs b/client/views/signin.hbs new file mode 100644 index 0000000..76b3ae5 --- /dev/null +++ b/client/views/signin.hbs @@ -0,0 +1,19 @@ + + + + + + + + +
+ + + + + +
+ + + + \ No newline at end of file diff --git a/client/views/signup.hbs b/client/views/signup.hbs new file mode 100644 index 0000000..33865f3 --- /dev/null +++ b/client/views/signup.hbs @@ -0,0 +1,23 @@ + + + + + + + + +
+ + + + + + + + + +
+ + + + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 5dd698e..81808eb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -484,6 +484,11 @@ "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=" }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" + }, "asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", @@ -514,6 +519,14 @@ "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", "dev": true }, + "async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", + "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", + "requires": { + "lodash": "^4.17.11" + } + }, "async-each": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", @@ -1242,6 +1255,11 @@ "safe-buffer": "5.1.2" } }, + "bcrypt-nodejs": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/bcrypt-nodejs/-/bcrypt-nodejs-0.0.3.tgz", + "integrity": "sha1-xgkX8m3CNWYVZsaBBhwwPCsohCs=" + }, "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -1895,6 +1913,11 @@ } } }, + "crc": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/crc/-/crc-3.4.4.tgz", + "integrity": "sha1-naHpgOO9RPxck79as9ozeNheRms=" + }, "create-error-class": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", @@ -2016,7 +2039,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, "requires": { "object-keys": "^1.0.12" } @@ -2672,6 +2694,34 @@ } } }, + "express-handlebars": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/express-handlebars/-/express-handlebars-3.0.1.tgz", + "integrity": "sha512-ZrfVjnGHS8liArTuVpWFckWII6Wwf1Z9BLqgDIYNjzH/yYxkRPeYvA4mv/o+7f7mJTJAX9oXys0ZZRGqkUyTog==", + "requires": { + "glob": "^7.1.3", + "graceful-fs": "^4.1.2", + "handlebars": "^4.0.13", + "object.assign": "^4.1.0", + "promise": "^8.0.2" + } + }, + "express-session": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.15.6.tgz", + "integrity": "sha512-r0nrHTCYtAMrFwZ0kBzZEXa1vtPVrw0dKvGSrKP4dahwBQ1BJpF2/y1Pp4sCD/0kvxV4zZeclyvfmw0B4RMJQA==", + "requires": { + "cookie": "0.3.1", + "cookie-signature": "1.0.6", + "crc": "3.4.4", + "debug": "2.6.9", + "depd": "~1.1.1", + "on-headers": "~1.0.1", + "parseurl": "~1.3.2", + "uid-safe": "~2.1.5", + "utils-merge": "1.0.1" + } + }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -3474,8 +3524,7 @@ "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, "functional-red-black-tree": { "version": "1.0.1", @@ -3687,6 +3736,24 @@ "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", "dev": true }, + "handlebars": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.0.tgz", + "integrity": "sha512-l2jRuU1NAWK6AW5qqcTATWQJvNPEwkM7NEKSiv/gqOsoSQbVoWyqVEY5GS+XPQ88zLNmqASRpzfdm8d79hJS+w==", + "requires": { + "async": "^2.5.0", + "optimist": "^0.6.1", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -3735,8 +3802,7 @@ "has-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", - "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", - "dev": true + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=" }, "has-value": { "version": "1.0.0", @@ -9962,8 +10028,7 @@ "object-keys": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz", - "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==", - "dev": true + "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==" }, "object-visit": { "version": "1.0.1", @@ -9977,7 +10042,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", - "dev": true, "requires": { "define-properties": "^1.1.2", "function-bind": "^1.1.1", @@ -10074,6 +10138,27 @@ "mimic-fn": "^1.0.0" } }, + "optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "requires": { + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" + }, + "dependencies": { + "minimist": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=" + }, + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" + } + } + }, "optionator": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", @@ -10249,6 +10334,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", @@ -10308,6 +10415,11 @@ "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", "dev": true }, + "pause": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", + "integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10=" + }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -10534,6 +10646,14 @@ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true }, + "promise": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/promise/-/promise-8.0.2.tgz", + "integrity": "sha512-EIyzM39FpVOMbqgzEHhxdrEhtOSDOtjMZQ0M6iVfCE+kWNgCkAyOdnuCWqfmflylftfadU6FkiMgHZA2kUzwRw==", + "requires": { + "asap": "~2.0.6" + } + }, "promise-polyfill": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-6.1.0.tgz", @@ -10610,6 +10730,11 @@ "strict-uri-encode": "^1.0.0" } }, + "random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=" + }, "randomatic": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", @@ -11987,6 +12112,38 @@ "mime-types": "~2.1.18" } }, + "uglify-js": { + "version": "3.4.9", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz", + "integrity": "sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==", + "optional": true, + "requires": { + "commander": "~2.17.1", + "source-map": "~0.6.1" + }, + "dependencies": { + "commander": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", + "optional": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "optional": true + } + } + }, + "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" + } + }, "umzug": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/umzug/-/umzug-2.2.0.tgz", diff --git a/package.json b/package.json index 82964de..fc242d4 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "license": "ISC", "dependencies": { "axios": "^0.18.0", + "bcrypt-nodejs": "0.0.3", "body-parser": "^1.18.3", "btoa": "^1.2.1", "build-url": "^1.3.2", @@ -36,9 +37,13 @@ "custom-env": "^1.0.0", "dotenv": "^6.2.0", "express": "^4.16.4", + "express-handlebars": "^3.0.1", + "express-session": "^1.15.6", "lodash": "^4.17.11", "morgan": "^1.9.1", "onchange": "^5.2.0", + "passport": "^0.4.0", + "passport-local": "^1.0.0", "pg": "^7.8.0", "plex-api": "^5.2.1", "request-promise": "^4.2.4", diff --git a/server/controllers/auth.controller.js b/server/controllers/auth.controller.js new file mode 100644 index 0000000..a70b828 --- /dev/null +++ b/server/controllers/auth.controller.js @@ -0,0 +1,10 @@ +// eslint-disable-next-line import/prefer-default-export +const signup = (req, res) => { + res.render('signup'); +}; + +const signin = (req, res) => { + res.render('signin'); +}; + +export default {signup, signin}; diff --git a/server/db/migrations/20190224043920-create-user.js b/server/db/migrations/20190224043920-create-user.js index a8d2edd..fbfa2c3 100644 --- a/server/db/migrations/20190224043920-create-user.js +++ b/server/db/migrations/20190224043920-create-user.js @@ -17,6 +17,20 @@ module.exports = { type: Sequelize.STRING, unique: true, }, + username: Sequelize.STRING, + password: { + type: Sequelize.STRING, + allowNull: false, + }, + + last_login: { + type: Sequelize.DATE, + }, + + status: { + type: Sequelize.ENUM('active', 'inactive'), + defaultValue: 'active', + }, createdAt: { allowNull: false, type: Sequelize.DATE, diff --git a/server/db/models/user.js b/server/db/models/user.js index df16d3c..e8adbf9 100644 --- a/server/db/models/user.js +++ b/server/db/models/user.js @@ -5,6 +5,20 @@ module.exports = (sequelize, DataTypes) => { firstName: DataTypes.STRING, lastName: DataTypes.STRING, email: {type: DataTypes.STRING, unique: true}, + username: DataTypes.STRING, + password: { + type: DataTypes.STRING, + allowNull: false, + }, + + last_login: { + type: DataTypes.DATE, + }, + + status: { + type: DataTypes.ENUM('active', 'inactive'), + defaultValue: 'active', + }, }, {}, ); diff --git a/server/db/seeders/index.js b/server/db/seeders/index.js index d0ed85d..9f71e85 100644 --- a/server/db/seeders/index.js +++ b/server/db/seeders/index.js @@ -4,6 +4,7 @@ export default { firstName: 'Mike', lastName: 'Rode', email: 'mike@email.com', + password: 'password123', }, ], }; diff --git a/server/index.js b/server/index.js index 0f39a99..355777a 100644 --- a/server/index.js +++ b/server/index.js @@ -1,7 +1,12 @@ +/* eslint-disable global-require */ import express from 'express'; import {json, urlencoded} from 'body-parser'; +import passport from 'passport'; +import session from 'express-session'; +import exphbs from 'express-handlebars'; import {sequelize} from './db/models'; import plex from './routes/plex.route'; +import auth from './routes/auth'; export default () => { const server = express(); @@ -16,8 +21,26 @@ export default () => { server.use(json()); server.use(urlencoded({extended: true})); + // Passport + server.use( + session({secret: 'keyboard cat', resave: true, saveUninitialized: true}), + ); + server.use(passport.initialize()); + server.use(passport.session()); // persistent login sessions + // Set up routes server.use('/plex', plex); + auth(server); + + // Set up views + server.set('views', './client/views'); + server.engine( + 'hbs', + exphbs({ + extname: '.hbs', + }), + ); + server.set('view engine', '.hbs'); return server; }; diff --git a/server/passport/passport.js b/server/passport/passport.js new file mode 100644 index 0000000..d95f594 --- /dev/null +++ b/server/passport/passport.js @@ -0,0 +1,60 @@ +import Strategy from 'passport-local'; +import bCrypt from 'bcrypt-nodejs'; + +module.exports = function(passport, user) { + const User = user; + + const LocalStrategy = Strategy; + + passport.use( + 'local-signup', + new LocalStrategy( + { + usernameField: 'email', + + passwordField: 'password', + + passReqToCallback: true, // allows us to pass back the entire request to the callback + }, + + function(req, email, password, done) { + const generateHash = function(password) { + return bCrypt.hashSync(password, bCrypt.genSaltSync(8), null); + }; + + User.findOne({ + where: { + email, + }, + }).then(function(user) { + if (user) { + return done(null, false, { + message: 'That email is already taken', + }); + } + const userPassword = generateHash(password); + + const data = { + email, + + password: userPassword, + + firstname: req.body.firstname, + + lastname: req.body.lastname, + }; + + User.create(data).then(function(newUser, created) { + if (!newUser) { + return done(null, false); + } + + if (newUser) { + return done(null, newUser); + } + }); + }); + }, + ), + ); +}; diff --git a/server/routes/auth.js b/server/routes/auth.js new file mode 100644 index 0000000..9bb3e5d --- /dev/null +++ b/server/routes/auth.js @@ -0,0 +1,6 @@ +import authController from '../controllers/auth.controller'; + +module.exports = function(app) { + app.get('/signup', authController.signup); + app.get('/signin', authController.signin); +}; diff --git a/test/server/services/plex/importData.test.js b/test/server/services/plex/importData.test.js index 02ab931..d06e444 100644 --- a/test/server/services/plex/importData.test.js +++ b/test/server/services/plex/importData.test.js @@ -1,37 +1,66 @@ -// import chai from 'chai'; -// import nock from 'nock'; -// import app from '../../../../index'; -// import importData from '../../../../server/services/plex/importData'; -// import { PlexSection, User, PlexLibrary } from '../../../../server/db/models'; -// import { seed, truncate } from '../../../../server/db/scripts'; -// import * as nocks from '../../../nocks'; +import chai from 'chai'; +import nock from 'nock'; +import app from '../../../../index'; +import importData from '../../../../server/services/plex/importData'; +import models from '../../../../server/db/models'; +import { seed, truncate } from '../../../../server/db/scripts'; +import * as nocks from '../../../nocks'; // // before(() => truncate('PlexSection')); -// describe('ImportData', () => { -// before(() => seed('User')); -// after(() => { -// truncate('User'); -// truncate('PlexLibrary'); -// truncate('PlexSection'); +describe('ImportData', () => { + before(() => { + nocks.plexSections(); + seed('User'); + }); + after(() => { + truncate('User'); + truncate('PlexLibrary'); + truncate('PlexSection'); + }); + + describe('GET /plex/import/sections', () => { + it('should find and store sections in the database', async () => { + const response = await importData.importSections(); + console.log('import sec response', response); + const sections = await models.PlexSection.findAll(); + console.log('Sections response', sections); + sections.should.be.length(2); + }); + }); +}); + +// describe('createRecord', () => { +// const name = 'testCreateIM'; +// const im = { name, description: 'new interface method' }; +// let jwt; + +// before((done) => { +// createToken('test') +// .then((token) => { +// jwt = token; +// done(); +// }); // }); -// describe('GET /plex/import/sections', async () => { -// it('should find and store sections in the database first', async () => { -// nocks.plexSections(); -// const response = await importData.importSections().then -// console.log('import sec response', response); -// const sections = await PlexSection.findAll(); -// sections.should.be.length(2); -// }); -// }); +// after(() => Interfacemethod.destroy({ where: { name } })); -// describe('Get /plex/import/libraries', async () => { -// it('should import sections', async () => { -// nocks.plexLibrary(); -// nocks.plexSections(); -// await chai.request(app).get('/plex/import/libraries'); -// const media = await PlexLibrary.findAll(); -// media.should.be.length(56); +// it('creates record', (done) => { +// let responseRecord; +// chai.request(app) +// .post('/v1/interfacemethods') +// .set('Authorization', `Bearer ${jwt}`) +// .send(im) +// .then((res) => { +// expect(res).to.have.status(200); +// expect(res.body).to.have.property('record'); +// responseRecord = res.body.record; +// return Interfacemethod.findByPk(responseRecord.id); +// }) +// .then((dbRecord) => { +// expect(responseRecord).to.deep.equal(JSON.parse(JSON.stringify(dbRecord))); +// expect(im.name).to.equal(responseRecord.name); +// expect(im.description).to.equal(responseRecord.description); +// done(); +// }); // }); // }); -// });