From ee187c4f4459783682a5f2dd5da1708fc96c9ef4 Mon Sep 17 00:00:00 2001 From: Ofrepose Date: Sun, 13 Sep 2020 21:32:37 -0400 Subject: [PATCH] Completed Integrated testing --- .config/secrets.js | 3 + .idea/.gitignore | 5 + .idea/misc.xml | 6 ++ .idea/modules.xml | 8 ++ .idea/vcs.xml | 6 ++ ...t-challenge-authentication-and-testing.iml | 12 +++ README.md | 8 ++ __tests__/jokesRouterTests.js | 31 ++++++ __tests__/serverStatusTest.js | 9 ++ __tests__/userLoginTests.js | 88 ++++++++++++++++++ api/server.js | 18 +++- auth/auth-router.js | 61 +++++++++++- auth/authenticate-middleware.js | 34 ++++++- auth/user_validation.js | 29 ++++++ auth/users-model.js | 37 ++++++++ database/auth.db3 | Bin 24576 -> 24576 bytes database/dbConfig.js | 4 +- database/seeds/001.js | 13 +++ database/test.db3 | Bin 0 -> 24576 bytes index.js | 4 +- knexfile.js | 13 +++ package.json | 18 +++- 22 files changed, 390 insertions(+), 17 deletions(-) create mode 100644 .config/secrets.js create mode 100644 .idea/.gitignore create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 .idea/web-sprint-challenge-authentication-and-testing.iml create mode 100644 __tests__/jokesRouterTests.js create mode 100644 __tests__/serverStatusTest.js create mode 100644 __tests__/userLoginTests.js create mode 100644 auth/user_validation.js create mode 100644 auth/users-model.js create mode 100644 database/seeds/001.js create mode 100644 database/test.db3 diff --git a/.config/secrets.js b/.config/secrets.js new file mode 100644 index 000000000..9f430e371 --- /dev/null +++ b/.config/secrets.js @@ -0,0 +1,3 @@ +module.exports = { + jwtSecret: process.env.JWT_SECRET || 'add a third table for many to many', +}; \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 000000000..ea7ed093e --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,5 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 000000000..ef004d16c --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 000000000..dc93163ca --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 000000000..9661ac713 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/web-sprint-challenge-authentication-and-testing.iml b/.idea/web-sprint-challenge-authentication-and-testing.iml new file mode 100644 index 000000000..0b872d82d --- /dev/null +++ b/.idea/web-sprint-challenge-authentication-and-testing.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 71eb93186..0bc0841be 100644 --- a/README.md +++ b/README.md @@ -26,12 +26,20 @@ Be prepared to demonstrate your understanding of this week's concepts by answeri 1. Differences between using _sessions_ or _JSON Web Tokens_ for authentication. +sessions are stored in the servers memory, json web tokens are stateless. + 2. What does `bcrypt` do to help us store passwords in a secure manner. +bcrypt encrypts your data to help guard against brute force attacks. + 3. How are unit tests different from integration and end-to-end testing. +unit test test one specific aspect or function of your code, integration testing tests modules as a group, end-to-end testing tests the entire application from start to end + 4. How _Test Driven Development_ changes the way we write applications and tests. +test driven development forces you to write tests first that fail and then write the code to make them pass + You are expected to be able to answer questions in these areas. Your responses contribute to your Sprint Challenge grade. ## Instructions diff --git a/__tests__/jokesRouterTests.js b/__tests__/jokesRouterTests.js new file mode 100644 index 000000000..c7fde6fcd --- /dev/null +++ b/__tests__/jokesRouterTests.js @@ -0,0 +1,31 @@ +const superTest = require('supertest'); +const server = require('../api/server'); +const db = require("../database/dbConfig") + +beforeAll(async () => { + // run the seeds programatically before each test to start fresh + await db.seed.run() +}) + +afterAll(async () => { + // close the database connection so the test process doesn't hang or give a warning + await db.destroy() +}) + + + +describe("Jokes Router Integration Tests", ()=> { + + + + it("POST / Initial test to ensure unAuth user cannot access endpoints", async ()=>{ + let res = await superTest(server) + .get("/api/jokes") + expect(res.statusCode).toBe(401) + expect(res.type).toBe("application/json") + expect(res.body.you).toBe("shall not pass!") + + }) + + +}); \ No newline at end of file diff --git a/__tests__/serverStatusTest.js b/__tests__/serverStatusTest.js new file mode 100644 index 000000000..55f552de5 --- /dev/null +++ b/__tests__/serverStatusTest.js @@ -0,0 +1,9 @@ +const superTest = require('supertest'); +const server = require('../api/server'); + +test("GET / and test its up and returning the welcome data", async ()=>{ + const res = await superTest(server).get("/") + expect(res.statusCode).toBe(200) + expect(res.type).toBe("application/json") + expect(res.body.data).toBe("welcome to the api") +}) \ No newline at end of file diff --git a/__tests__/userLoginTests.js b/__tests__/userLoginTests.js new file mode 100644 index 000000000..c2c4c69eb --- /dev/null +++ b/__tests__/userLoginTests.js @@ -0,0 +1,88 @@ +const superTest = require('supertest'); +const server = require('../api/server'); +const db = require("../database/dbConfig") +const user_valid = require('../auth/user_validation') +const bcrypt = require("bcryptjs"); + +beforeAll(async () => { + // run the seeds programatically before each test to start fresh + await db.seed.run() +}) + +afterAll(async () => { + // close the database connection so the test process doesn't hang or give a warning + await db.destroy() +}) + +async function CreateTest(person){ + const [id] = await db('users').insert(person, 'id'); + + return db('users') + .where({ id }) + .first(); +} + + + + +describe("User Register Tests", ()=>{ + it("POST / tests registering of new user and Loggin in", async()=>{ + let res = await superTest(server) + .post("/api/auth/register") + .send({username:"testerBean", password:"BeanPassword"}) + expect(res.statusCode).toBe(201) + expect(res.type).toBe("application/json") + expect(res.body.username).toBe("testerBean") + + }) + + it("POST / test registering user without the proper information {username}. Should FAIL", async ()=>{ + const res = await superTest(server) + .post("/api/auth/register") + .send({ + username: '', + password:'asfdasdfas', + }) + expect(res.statusCode).toBe(409) + expect(res.type).toBe("application/json") + expect(res.body.message).toBe("Please enter a username") + }) + + it("POST / test registering user without the proper information {password}. Should FAIL", async ()=>{ + const res = await superTest(server) + .post("/api/auth/register") + .send({ + username: 'asfdasdfas', + password:'', + }) + expect(res.statusCode).toBe(409) + expect(res.type).toBe("application/json") + expect(res.body.message).toBe("Please enter a password") + }) + + it("POST / test registering user without the proper information {password}. Should FAIL", async ()=> { + + }); + +}) + +describe("Test Logging in a user", ()=>{ + it("POST/ Login user should return a 200", async ()=>{ + + const res = await superTest(server) + .post("/api/auth/login") + .send({username:"testerBean", password:"BeanPassword"}) + expect(res.statusCode).toBe(200) + expect(res.type).toBe("application/json") + expect(res.body.message).toBe("Welcome testerBean!") + },99999) + it("POST / Login user with bad credentials should FAIL", async () =>{ + const res = await superTest(server) + .post("/api/auth/login") + .send({username:"testerBean2", password:"BeanPassword"}) + expect(res.statusCode).toBe(401) + expect(res.type).toBe("application/json") + expect(res.body.message).toBe("Invalid Credentials") + }) +}) + diff --git a/api/server.js b/api/server.js index c8acc0eb4..f5ffccf71 100644 --- a/api/server.js +++ b/api/server.js @@ -1,8 +1,9 @@ const express = require('express'); const cors = require('cors'); const helmet = require('helmet'); +const cookieParser = require("cookie-parser"); -const authenticate = require('../auth/authenticate-middleware.js'); +const restrict = require('../auth/authenticate-middleware.js'); const authRouter = require('../auth/auth-router.js'); const jokesRouter = require('../jokes/jokes-router.js'); @@ -11,8 +12,21 @@ const server = express(); server.use(helmet()); server.use(cors()); server.use(express.json()); +server.use(express.json()); +server.use(cookieParser()); server.use('/api/auth', authRouter); -server.use('/api/jokes', authenticate, jokesRouter); +server.use('/api/jokes', restrict, jokesRouter); + +server.get('/',(req,res,)=>{ + res.status(200).json({data:"welcome to the api"}) +}) + +server.use( (err, req, res, next) => { + console.log(err) + res.status(500).json({ + message:"Something went wrong", + }) +}) module.exports = server; diff --git a/auth/auth-router.js b/auth/auth-router.js index 2fa2c9766..af024fcb8 100644 --- a/auth/auth-router.js +++ b/auth/auth-router.js @@ -1,11 +1,62 @@ const router = require('express').Router(); +const bcrypt = require("bcryptjs"); +const Users = require('./users-model'); +const jwt = require('jsonwebtoken'); +const secrets = require("../.config/secrets") -router.post('/register', (req, res) => { +const user_valid = require('./user_validation') + + +router.post('/register', async (req, res, next) => { // implement registration -}); + try { + const { username, password } = req.body + const user = await Users.findBy({ username }).first() + + if (user) { + return res.status(409).json({ + message: "Username is already taken", + }) + } + console.log(username) + if(username === "" || !username){ + return res.status(409).json({message:"Please enter a username"}) + } + + if(password === "" || !password){ + return res.status(409).json({message:"Please enter a password"}) + } + + const newUser = await Users.add({ + username, + // hash the password with a time complexity of 14 + password: await bcrypt.hash(password, 14), + }) -router.post('/login', (req, res) => { - // implement login + await res.status(201).json({ + 'username': username , 'password': newUser.password}) + } catch(err) { + next(err) + } }); -module.exports = router; +router.post('/login', async (req, res, next) => { + try { + const { username, password } = req.body + const user = await Users.findByUsername({ username }).first() + + if (!user) { + return res.status(401).json({ + message: "Invalid Credentials", + }) + } + + await user_valid(user,req, res,) + + + } catch(err) { + next(err) + } +}) + +module.exports = router; \ No newline at end of file diff --git a/auth/authenticate-middleware.js b/auth/authenticate-middleware.js index 6ca61d0cd..3121e4553 100644 --- a/auth/authenticate-middleware.js +++ b/auth/authenticate-middleware.js @@ -1,8 +1,32 @@ -/* - complete the middleware code to check if the user is logged in - before granting access to the next middleware/route handler -*/ +const jwt = require("jsonwebtoken") +const secrets = require("../.config/secrets") module.exports = (req, res, next) => { - res.status(401).json({ you: 'shall not pass!' }); + const authError = { you: 'shall not pass!' } + try { + // token is coming from the client's cookie jar, in the "Cookie" header + const token = req.cookies.token + console.log(` token is ${token}`) + if (!token) { + console.log("!token") + return res.status(401).json(authError) + } + + // decode the token, re-sign the payload, and check if signature is valid + jwt.verify(token, secrets.jwtSecret, (err, decoded) => { + console.log(`inside verify secret is ${secrets}`) + if (err) { + return res.status(401).json(authError) + } + + // we know the user is authorized at this point, + // make the token's payload available to other middleware functions + req.token = decoded + next() + }) + } catch(err) { + + next(err) + + } }; diff --git a/auth/user_validation.js b/auth/user_validation.js new file mode 100644 index 000000000..bfae5bd16 --- /dev/null +++ b/auth/user_validation.js @@ -0,0 +1,29 @@ +const jwt = require('jsonwebtoken'); +const secrets = require("../.config/secrets") +const bcrypt = require("bcryptjs"); + + +module.exports = async (user, req, res, next) =>{ + const passwordValid = await bcrypt.compare(req.body.password, user.password) + + if (!passwordValid) { + return res.status(401).json({ + message: "Invalid Credentials", + }) + } + + const token = jwt.sign({ + userID: user.id, + }, secrets.jwtSecret) + + + res.cookie("token", token) + res.cookie("username", user.username) + + return res.json({ + message: `Welcome ${user.username}!`, + token:token + }) + + +} \ No newline at end of file diff --git a/auth/users-model.js b/auth/users-model.js new file mode 100644 index 000000000..3d413220a --- /dev/null +++ b/auth/users-model.js @@ -0,0 +1,37 @@ +const db = require("../database/dbConfig"); + +async function add(user){ + const [id] = await db("users").insert(user) + return findById(id); +} + +function find(){ + return db("users").select("id", "username") +} + +function findById(id) { + return db("users") + .select("id", "username") + .where({ id }) + .first() +} + +function findBy(filter) { + return db("users") + .select("id", "username") + .where(filter) +} + +function findByUsername(username){ + return db('users') + .select('id','username','password') + .where(username) +} + +module.exports = { + add, + find, + findBy, + findById, + findByUsername +} diff --git a/database/auth.db3 b/database/auth.db3 index cc6ee6d93c913bdd08a9c0d42269f9e4d30d7d0d..dbfc74ec16f467e039e4466ebea460a84f66fb7a 100644 GIT binary patch delta 533 zcmY+>F>l&X5C?EO=Ba`xytGRPB6VRwm9Vjm4eC$@BQS<%hr|$@4PzS!IEi_745$*x z7l_(#(63M-b!N$$xlCQUw0rl`)~#CUhSOiCpYC+G8}fETzRi%ycSjOP68YEL5BB>P zDGELk@EhF1$Mw_04ct#s)N?Y#&J2U~mVSAd2I+eOeuPt)OMgz^?}px_R~i6B?hXuH z8@oenySp0VEZff(_^f1^H`8W5Y^%~tkxq+~f~IoXRo?3y%j)HI3(M^V9~G=6!|Fn} zXf?Ia*5m{rGJnBXq7}D_s=*C|qETCE3a35e+IQS?XGKRRxbC;PGlSDv8k^xDoO4Dr zL38K)m;%JX9f*x(Nz_J-y3Ac+b6KqvyP_`)9VZZliyA5hlZ(-KS~pAciYi&G8lmoj zsfP02uWbSc2hNO(CDkz-JdQY}YOEBgG~lB^)0uE#3ZfrrUQJ+YW7;YsMyGp7nxaPE z?}TSE`47d$RLkpl<%(6~dxO5v^Oonbh8B*8#FIui?9@js#aC>(tyy(6o*U{Q6m&FB z5d;C?0|CFlC!gR0{0_hFp0S3no>4jS|4)E@D)T2wB&Y+D`WeLM?4_to@^{bv00p?8 A+yDRo delta 68 zcmZoTz}Rqrae_1>%S0JxRu%?5oq&xg3*FmN1aNSDHDMbH~?Oo3^^X$xTe3|tYpKh8i zXAR448!k(cX+kM^$QU6sM+g~{@1TU`iT4f)4Nm_3D03`6_<2$`P$$18T7K$}srAY4 zIUiuWmuHm`Jr+39k>a%^R{ zkS`TjdF%1ha)CYGS}h!~>d5w0W{JyCmCgHYylmCzAQB$eb)7zsxW-ZIBCnr8y-Z%p zKiVu@>uT1k%oHwfa+?XuWun_^u`}Cj8+MSkMUISUZrg|ZNoJ!;Z#~y?Q~RK_~r~o zHSsKbbdr4lth^i{bru%Y0*XLVopj1In~ z<6g?=u~E|=noj6O&gxLqpXb}bUg7`e!u@m6twFTkwV$*fw1%d?YdZ9U00bZa0SG_<0uX=z1Rwwb2;35ZlB$x_KrM0S$+AlB z4OEvqF1H_XLuAr(NnlNO*J`Zgx6QTVQhg^A+wHr%XEDCf zJKQesoNRW@UMI8fb~aX361kr4GFd%QPt}se{N{4JeO}BQXZwY%!)~J4UtT|G6pjS% ztQ7Wo^?tD&+b=&`JUB0|XF8uLVKVcjZJqBKtu9Y_V>CrG5s5tfJR~Du1(9SBNj40} z_>{`n7eOo$#6Ffw6ZW3}|5N)_d#%0He!L}Xg<(Pf0uX=z1Rwwb2tWV=5P$##K2!l+ sNzsyjOJZ8NN4+h4aC2gLN{L*nSe;a6Xt38$Dc8RxLwA&!i6EBv8-;q7i~s-t literal 0 HcmV?d00001 diff --git a/index.js b/index.js index fd80bbe6d..e3eec5ec2 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,8 @@ const server = require('./api/server.js'); -const PORT = process.env.PORT || 3300; +const PORT = process.env.PORT || 3500; + server.listen(PORT, () => { console.log(`\n=== Server listening on port ${PORT} ===\n`); + }); diff --git a/knexfile.js b/knexfile.js index c83e47c2c..f9a2be7d3 100644 --- a/knexfile.js +++ b/knexfile.js @@ -9,4 +9,17 @@ module.exports = { }, seeds: { directory: './database/seeds' }, }, + testing: { + client: 'sqlite3', + connection: { + filename: './database/test.db3', + }, + useNullAsDefault: true, + migrations: { + directory: './database/migrations', + }, + seeds: { + directory: './database/seeds', + }, + }, }; diff --git a/package.json b/package.json index 4feb96236..94bf4c57a 100644 --- a/package.json +++ b/package.json @@ -4,12 +4,17 @@ "description": "Authentication Sprint Challenge", "main": "index.js", "scripts": { - "server": "nodemon index.js" + "server": "cross-env NODE_ENV=development nodemon index.js", + "start": "node index.js", + "test": "cross-env NODE_ENV=testing jest" }, "repository": { "type": "git", "url": "git+https://github.com/LambdaSchool/Sprint-Challenge-Authentication.git" }, + "jest": { + "testEnvironment": "node" + }, "keywords": [], "author": "Lambda School", "license": "ISC", @@ -19,13 +24,22 @@ "homepage": "https://github.com/LambdaSchool/Sprint-Challenge-Authentication#readme", "dependencies": { "axios": "^0.19.2", + "bcryptjs": "^2.4.3", + "cookie-parser": "^1.4.5", + "cookieparser": "^0.1.0", "cors": "^2.8.5", + "dotenv": "^8.2.0", "express": "^4.17.1", "helmet": "^3.22.0", "knex": "^0.21.0", "sqlite3": "^4.1.1" }, "devDependencies": { - "nodemon": "^2.0.3" + "cookie-parser": "^1.4.5", + "cross-env": "^7.0.2", + "jest": "^26.4.2", + "jsonwebtoken": "^8.5.1", + "nodemon": "^2.0.3", + "supertest": "^4.0.2" } }