diff --git a/api/auth/auth-middleware.js b/api/auth/auth-middleware.js index ddd8bc4dd7..e8d104fa38 100644 --- a/api/auth/auth-middleware.js +++ b/api/auth/auth-middleware.js @@ -1,3 +1,4 @@ +const Users = require('../users/users-model') /* If the user does not have a session saved in the server @@ -6,7 +7,12 @@ "message": "You shall not pass!" } */ -function restricted() { +function restricted(req,res,next) { + if(req.session && req.session.user) { + next(); + }else{ + next({ status: 401, message: "You shall not pass!"}) + } } @@ -18,8 +24,17 @@ function restricted() { "message": "Username taken" } */ -function checkUsernameFree() { - +async function checkUsernameFree(req,res,next) { + try { + const user = await Users.findBy(req.body.username) + if(user){ + next({ status: 422, message: "Username taken"}) + }else{ + next(); + } + } catch (err) { + next(err) + } } /* @@ -30,8 +45,17 @@ function checkUsernameFree() { "message": "Invalid credentials" } */ -function checkUsernameExists() { - +async function checkUsernameExists(req,res,next) { + try { + const user = await Users.findBy(req.body.username) + if(!user){ + next({ status: 401, message: "Invalid credentials"}) + }else{ + next(); + } + } catch (err) { + next(err) + } } /* @@ -42,8 +66,19 @@ function checkUsernameExists() { "message": "Password must be longer than 3 chars" } */ -function checkPasswordLength() { - +function checkPasswordLength(req,res,next) { + let { password } = req.body + if(password === undefined || password === null || password.length <= 3){ + next({ status: 422, message: 'Password must be longer than 3 chars'}) + }else{ + next(); + } } // Don't forget to add these to the `exports` object so they can be required in other modules +module.exports = { + checkUsernameFree, + checkPasswordLength, + checkUsernameExists, + restricted +} diff --git a/api/auth/auth-router.js b/api/auth/auth-router.js index ffd7a2e003..966481efee 100644 --- a/api/auth/auth-router.js +++ b/api/auth/auth-router.js @@ -1,7 +1,29 @@ +const express = require('express') +const Users = require('../users/users-model') +const bcrypt = require('bcryptjs') +const { checkUsernameFree, checkPasswordLength, checkUsernameExists } = require('./auth-middleware') +const router = express.Router() // Require `checkUsernameFree`, `checkUsernameExists` and `checkPasswordLength` // middleware functions from `auth-middleware.js`. You will need them here! + +router.post('/register', checkUsernameFree, checkPasswordLength, async (req, res, next) => { + const { username, password } = req.body + try { + const hashedPassword = await bcrypt.hash(password, 12) + const credentials = { + username: username, + password: hashedPassword + } + const result = await Users.add(credentials) + res.status(201).json(result) + } catch (error) { + next(error) + } + + +}) /** 1 [POST] /api/auth/register { "username": "sue", "password": "1234" } @@ -25,7 +47,20 @@ } */ - +router.post('/login', checkUsernameExists, async (req, res, next) => { + try { + const { password } = await Users.findBy(req.body.username) + const validPassword = await bcrypt.compare(req.body.password, password) + if (validPassword) { + req.session.user = req.body.username + res.status(200).json({ message: `Welcome ${req.body.username}!` }) + } else { + next({ status: 401, message: "Invalid credentials" }) + } + } catch (err) { + next({ message: 'Login error, please try again' }) + } +}) /** 2 [POST] /api/auth/login { "username": "sue", "password": "1234" } @@ -42,7 +77,23 @@ } */ - +router.get('/logout', (req, res, next) => { + try { + if (req.session.user) { + req.session.destroy(err => { + if (err) { + res.send('error logging out') + } else { + res.status(200).json({ message: 'logged out' }) + } + }) + } else { + res.status(200).json({ message: 'no session' }) + } + } catch (err) { + next(err) + } +}) /** 3 [GET] /api/auth/logout @@ -59,5 +110,6 @@ } */ - + // Don't forget to add the router to the `exports` object so it can be required in other modules +module.exports = router \ No newline at end of file diff --git a/api/server.js b/api/server.js index bdc628cef2..12146a9433 100644 --- a/api/server.js +++ b/api/server.js @@ -1,6 +1,10 @@ const express = require("express"); const helmet = require("helmet"); const cors = require("cors"); +const session = require('express-session') +const KnexSessionStore = require("connect-session-knex") (session) +const usersRouter = require('./users/users-router') +const authRouter = require('./auth/auth-router') /** Do what needs to be done to support sessions with the `express-session` package! @@ -20,6 +24,26 @@ const server = express(); server.use(helmet()); server.use(express.json()); server.use(cors()); +server.use(session({ + name: 'chocolatechip', + secret: 'Bloomtech', + cookie: { + maxAge: 1000 * 60 * 60, + secure: false + }, + httpOnly: true, + resave: false, + saveUninitialized: false, + store: new KnexSessionStore({ + knex: require('../data/db-config'), + tablename: "sessions", + sidfieldname: "session_id", + createtable: true, + clearInterval: 1000 * 1000 * 60 + }) +})) +server.use('/api/users', usersRouter) +server.use('/api/auth', authRouter) server.get("/", (req, res) => { res.json({ api: "up" }); diff --git a/api/users/users-model.js b/api/users/users-model.js index 0eb347ce52..021eff55b0 100644 --- a/api/users/users-model.js +++ b/api/users/users-model.js @@ -1,29 +1,52 @@ +const db = require('../../data/db-config') /** resolves to an ARRAY with all users, each user having { user_id, username } */ -function find() { +async function find() { + const users = await db('users') + .select('user_id', 'username') + return users } /** resolves to an ARRAY with all users that match the filter condition */ -function findBy(filter) { - +async function findBy(filter) { + const [users] = await db('users') + // .select('user_id', 'username') + .where('user_id', filter).orWhere('username', filter).orWhere('password',filter) + return users } /** resolves to the user { user_id, username } with the given user_id */ -function findById(user_id) { - +async function findById(user_id) { + const users = await db('users') + .select('user_id', 'username') + .where('user_id', user_id) + return users } /** resolves to the newly inserted user { user_id, username } */ -function add(user) { +async function add(user) { + const newID = await db('users') + .insert(user) + const newUser = await db('users') + .select('user_id', 'username') + .where('user_id', newID) + .first() + return(newUser) } // Don't forget to add these to the `exports` object so they can be required in other modules +module.exports = { + find, + findBy, + findById, + add, +} \ No newline at end of file diff --git a/api/users/users-router.js b/api/users/users-router.js index 84aaf5b4be..9c495558da 100644 --- a/api/users/users-router.js +++ b/api/users/users-router.js @@ -1,3 +1,18 @@ +const express = require('express') +const Users = require('./users-model') +const { restricted } = require('../auth/auth-middleware') +const router = express.Router(); + + +router.get('/', restricted, async (req, res, next) => { + try { + const users = await Users.find() + res.status(200).json(users) + } catch (err) { + next(err) + } +}) + // Require the `restricted` middleware from `auth-middleware.js`. You will need it here! @@ -26,3 +41,5 @@ // Don't forget to add the router to the `exports` object so it can be required in other modules + +module.exports = router diff --git a/data/auth.db3 b/data/auth.db3 index c675b8976d..bd15438234 100644 Binary files a/data/auth.db3 and b/data/auth.db3 differ diff --git a/package-lock.json b/package-lock.json index c3a920a3df..49775cce4e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,8 +9,10 @@ "version": "1.0.0", "dependencies": { "bcryptjs": "^2.4.3", + "connect-session-knex": "^3.0.1", "cors": "^2.8.5", "express": "^4.18.1", + "express-session": "^1.17.3", "helmet": "^5.0.2", "knex": "^2.0.0", "sqlite3": "^5.0.8" @@ -2050,9 +2052,9 @@ } }, "node_modules/colorette": { - "version": "2.0.16", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.16.tgz", - "integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==" + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", + "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==" }, "node_modules/combined-stream": { "version": "1.0.8", @@ -2114,6 +2116,23 @@ "typedarray-to-buffer": "^3.1.5" } }, + "node_modules/connect-session-knex": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/connect-session-knex/-/connect-session-knex-3.0.1.tgz", + "integrity": "sha512-w+sNn/mdEmAGCfw90hyXlC0QixGvkfzTs9hZpDWLnzdyI3BQqnWUxZshWJlYNeZza45OrTxT+yqe3epNMPbs2Q==", + "dependencies": { + "bluebird": "^3.7.2", + "knex": "^2.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/connect-session-knex/node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + }, "node_modules/console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", @@ -2857,6 +2876,45 @@ "node": ">= 0.10.0" } }, + "node_modules/express-session": { + "version": "1.17.3", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.3.tgz", + "integrity": "sha512-4+otWXlShYlG1Ma+2Jnn+xgKUZTMJ5QD3YvfilX3AcocOAbIkVylSWEklzALe/+Pu4qV6TYBj5GwOBFfdKqLBw==", + "dependencies": { + "cookie": "0.4.2", + "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.2.1", + "uid-safe": "~2.1.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/express-session/node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express-session/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express-session/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "node_modules/express/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -4758,11 +4816,11 @@ } }, "node_modules/knex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/knex/-/knex-2.0.0.tgz", - "integrity": "sha512-LchC8/GLfreMz8d4kCwh/ymXttsoJG8zO1O0AJBjnxdyr2oT/k2ik77hP1PpZkZH9mDQrq6WsQcIu18Pnqppzg==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/knex/-/knex-2.4.1.tgz", + "integrity": "sha512-5wylehvnTOE8EdypPFakccA1zgo6Lp+TNultncvBUCUD0PasY+PLVa9qPrTFCioxPSPVha1u9ye2niAVVbLM0Q==", "dependencies": { - "colorette": "2.0.16", + "colorette": "2.0.19", "commander": "^9.1.0", "debug": "4.3.4", "escalade": "^3.1.1", @@ -5469,6 +5527,14 @@ "node": ">= 0.8" } }, + "node_modules/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==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -5841,6 +5907,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -6724,6 +6798,17 @@ "is-typedarray": "^1.0.0" } }, + "node_modules/uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "dependencies": { + "random-bytes": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/undefsafe": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", @@ -8614,9 +8699,9 @@ "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==" }, "colorette": { - "version": "2.0.16", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.16.tgz", - "integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==" + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", + "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==" }, "combined-stream": { "version": "1.0.8", @@ -8671,6 +8756,22 @@ } } }, + "connect-session-knex": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/connect-session-knex/-/connect-session-knex-3.0.1.tgz", + "integrity": "sha512-w+sNn/mdEmAGCfw90hyXlC0QixGvkfzTs9hZpDWLnzdyI3BQqnWUxZshWJlYNeZza45OrTxT+yqe3epNMPbs2Q==", + "requires": { + "bluebird": "^3.7.2", + "knex": "^2.3.0" + }, + "dependencies": { + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + } + } + }, "console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", @@ -9253,6 +9354,41 @@ } } }, + "express-session": { + "version": "1.17.3", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.3.tgz", + "integrity": "sha512-4+otWXlShYlG1Ma+2Jnn+xgKUZTMJ5QD3YvfilX3AcocOAbIkVylSWEklzALe/+Pu4qV6TYBj5GwOBFfdKqLBw==", + "requires": { + "cookie": "0.4.2", + "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.2.1", + "uid-safe": "~2.1.5" + }, + "dependencies": { + "cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==" + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + } + } + }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -10699,11 +10835,11 @@ "dev": true }, "knex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/knex/-/knex-2.0.0.tgz", - "integrity": "sha512-LchC8/GLfreMz8d4kCwh/ymXttsoJG8zO1O0AJBjnxdyr2oT/k2ik77hP1PpZkZH9mDQrq6WsQcIu18Pnqppzg==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/knex/-/knex-2.4.1.tgz", + "integrity": "sha512-5wylehvnTOE8EdypPFakccA1zgo6Lp+TNultncvBUCUD0PasY+PLVa9qPrTFCioxPSPVha1u9ye2niAVVbLM0Q==", "requires": { - "colorette": "2.0.16", + "colorette": "2.0.19", "commander": "^9.1.0", "debug": "4.3.4", "escalade": "^3.1.1", @@ -11213,6 +11349,11 @@ "ee-first": "1.1.1" } }, + "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==" + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -11485,6 +11626,11 @@ "side-channel": "^1.0.4" } }, + "random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==" + }, "range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -12143,6 +12289,14 @@ "is-typedarray": "^1.0.0" } }, + "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.5", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", diff --git a/package.json b/package.json index 92dfcc99ba..a066e8d2e9 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "test": "cross-env NODE_ENV=testing jest --verbose --runInBand", "migrate": "knex migrate:latest", "rollback": "knex migrate:rollback", - "seed": "knex seed:run" + "seed": "knex seed:run", + "resetdb": "npm run rollback && npm run migrate && npm run seed" }, "devDependencies": { "@types/jest": "^27.5.0", @@ -21,8 +22,10 @@ }, "dependencies": { "bcryptjs": "^2.4.3", + "connect-session-knex": "^3.0.1", "cors": "^2.8.5", "express": "^4.18.1", + "express-session": "^1.17.3", "helmet": "^5.0.2", "knex": "^2.0.0", "sqlite3": "^5.0.8"