From 02fa8c784c77ca8c64b39cf1f8e7388cf80f0bba Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 11 Sep 2020 20:17:08 -0500 Subject: [PATCH 1/3] update --- api/server.js | 14 ++++++ api/server.test..js | 86 +++++++++++++++++++++++++++++++++ auth/Token.js | 14 ++++++ auth/auth-router.js | 38 ++++++++++++++- auth/authenticate-middleware.js | 22 ++++++--- jokes/jokes-router.js | 12 +++++ package.json | 11 ++++- secrets/authSecret.js | 5 ++ users/model.js | 24 +++++++++ users/validation.js | 16 ++++++ 10 files changed, 232 insertions(+), 10 deletions(-) create mode 100644 api/server.test..js create mode 100644 auth/Token.js create mode 100644 secrets/authSecret.js create mode 100644 users/model.js create mode 100644 users/validation.js diff --git a/api/server.js b/api/server.js index c8acc0eb4..44205d215 100644 --- a/api/server.js +++ b/api/server.js @@ -16,3 +16,17 @@ server.use('/api/auth', authRouter); server.use('/api/jokes', authenticate, jokesRouter); module.exports = server; + +function checkRole(user) { + return (req, res, next) => { + if ( + req.decodedToken && + req.decodedToken.role && + req.decodedToken.role.toLowerCase() === user + ) { + next() + } else { + res.status(403).json({ message: 'Must be logged in' }) + } + } +} \ No newline at end of file diff --git a/api/server.test..js b/api/server.test..js new file mode 100644 index 000000000..14fa44fe8 --- /dev/null +++ b/api/server.test..js @@ -0,0 +1,86 @@ +const request = require('supertest') +const db = require('../database/dbConfig'); +const server = require('./server'); + +describe('end point tests', function() { + describe('POST /register and POST /login', function() { + beforeAll(async() => { + await db('users').truncate(); + }) + + //#1 should return status 201 + it('POST /auth/register', function() { + return request(server) + .post('/api/auth/register') + .send({ username: "test", password: "1234567" }) + .then(res => { + console.log(res.body) + expect(res.status).toBe(201) + expect(res.body.data.username).toBe(user.username) + }) + }) + + //#2 should give 400 error for invalid credentials' + it(' POST /auth/register', function() { + return request(server) + .post('/api/auth/register') + .send({ username: "nope" , password: "nope" }) + .then(res => { + console.log(res.status) + console.log(res.body) + expect(res.status).toBe(400); + expect(res.body).toEqual({ message: 'incorrect username/password' }) + }) + }) + + //#3 should return status 200 + it('POST /auth/login', function() { + return request(server) + .post('/api/auth/login') + .send({ username: 'test', password: '1234567' }) + .then(res => { + const token = res.data.token + return request(server) + .get('/api/jokes/') + .set({token}) + .then(res => { + expect(res.status).toBe(200); + }) + }) + }) + + //#4 res.type should match json + it(' POST /auth/login"', function() { + return request(server) + .post('/api/auth/login') + .send({ username: "test", password: "1234567" }) + .then(res => { + const token = res.data.token + return request(server) + .get('/api/jokes/') + .set({token}) + .then(res => { + expect(res.type).toMatch(/json/i); + }) + }) + }) + + //#5 res.type should match json + it(' GET /jokes/', function() { + return request(server) + .get('/api/jokes/') + .then(res => { + expect(res.type).toMatch(/json/i); + }) + }) + + //#6 should return a response + it(' GET /jokes/', function() { + return request(server) + .get('/api/jokes/') + .then(res => { + expect(res.body).toBeTruthy(); + }) + }) + }) +}) \ No newline at end of file diff --git a/auth/Token.js b/auth/Token.js new file mode 100644 index 000000000..a31e7fb8d --- /dev/null +++ b/auth/Token.js @@ -0,0 +1,14 @@ +const jwt = require('jsonwebtoken'); +const { jwtSecret } = require('../secrets/authSecret'); + +function Token(username) { + const payload = { + subject: username.id, + username: username.username, + role: username.role || 'user', + }; + + return jwt.sign(payload, jwtSecret); +} + +module.exports = Token; \ No newline at end of file diff --git a/auth/auth-router.js b/auth/auth-router.js index 2fa2c9766..5fbcc47b9 100644 --- a/auth/auth-router.js +++ b/auth/auth-router.js @@ -1,11 +1,45 @@ const router = require('express').Router(); +const bcrypt = require('bcryptjs'); + +const Users = require('../users/model'); +const newToken = require('./Token'); +const { validateUser } = require('../users/validation'); router.post('/register', (req, res) => { - // implement registration + let user = req.body + const validateResult = validateUser(user); + if (validateResult.isSuccessful === true) { + const hash = bcrypt.hashSync(user.password, 10); + user.password = hash; + Users.add(user) + .then(saved => { + const token = newToken(saved); + res.status(201).json(token); + }) + .catch(err => { + res.status(500).json({ message: 'Error', err }) + }) + } else { + res.status(400).json({ Message: 'invalid', errors: validateUser(user) }) + } }); router.post('/login', (req, res) => { - // implement login + let { username, password } = req.body; + Users.findBy({ username }) + .first() + .then(user => { + if (user && bcrypt.compareSync(password, user.password)) { + const token = newToken(user); + res.status(200).json({ Message: `Welcome ${user.username}`, token }); + } else { + res.status(401).json({ Message: 'Credentials invalid' }); + } + }) + .catch(err => { + res.status(500).json({ Message: 'Error' }) + }) }); + module.exports = router; diff --git a/auth/authenticate-middleware.js b/auth/authenticate-middleware.js index 6ca61d0cd..4068880a6 100644 --- a/auth/authenticate-middleware.js +++ b/auth/authenticate-middleware.js @@ -1,8 +1,18 @@ -/* - 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 { jwtSecret } = require('../secrets/authSecret'); module.exports = (req, res, next) => { - res.status(401).json({ you: 'shall not pass!' }); -}; + const { authorization } = req.headers; + if (authorization) { + jwt.verify(authorization, jwtSecret, (err, decodedToken) => { + if (err) { + res.status(401).json({ message: "Invalid credentials" }); + } else { + req.decodedToken = decodedToken; + next(); + } + }); + } else { + res.status(400).json({ message: "Please enter credentials" }); + } +}; \ No newline at end of file diff --git a/jokes/jokes-router.js b/jokes/jokes-router.js index aa93d0fa9..3b41333a7 100644 --- a/jokes/jokes-router.js +++ b/jokes/jokes-router.js @@ -1,4 +1,6 @@ const axios = require('axios'); +const Users = require('../users/model') +const restricted = require('../auth/middleware') const router = require('express').Router(); @@ -17,4 +19,14 @@ router.get('/', (req, res) => { }); }); +router.get('/user', restricted, (req, res) => { + Users.find() + .then(param => { + res.status(200).json(param) + }) + .catch(err => { + res.status(500).json(err) + }) +}) + module.exports = router; diff --git a/package.json b/package.json index 4feb96236..34dbede94 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "description": "Authentication Sprint Challenge", "main": "index.js", "scripts": { - "server": "nodemon index.js" + "server": "nodemon index.js", + "test": "jest --watchAll" }, "repository": { "type": "git", @@ -19,13 +20,19 @@ "homepage": "https://github.com/LambdaSchool/Sprint-Challenge-Authentication#readme", "dependencies": { "axios": "^0.19.2", + "bcryptjs": "^2.4.3", "cors": "^2.8.5", "express": "^4.17.1", "helmet": "^3.22.0", + "jsonwebtoken": "^8.5.1", "knex": "^0.21.0", "sqlite3": "^4.1.1" }, "devDependencies": { - "nodemon": "^2.0.3" + "cross-env": "^7.0.2", + "jest": "^26.4.2", + "knex-cleaner": "^1.3.1", + "nodemon": "^2.0.3", + "supertest": "^4.0.2" } } diff --git a/secrets/authSecret.js b/secrets/authSecret.js new file mode 100644 index 000000000..35c85386d --- /dev/null +++ b/secrets/authSecret.js @@ -0,0 +1,5 @@ +module.exports = { + + jwtSecret: process.env.JWT_SECRET || 'secret', + +} \ No newline at end of file diff --git a/users/model.js b/users/model.js new file mode 100644 index 000000000..97a3fd48f --- /dev/null +++ b/users/model.js @@ -0,0 +1,24 @@ +const db = require('../database/dbConfig'); +module.exports = { + add, + find, + findBy, + findById +} + +function find() { + return db('users').select('id', 'username', 'password'); +} + +function findBy(filter) { + return db('users').where(filter); +} + +function findById(id) { + return db('users').where({ id }).first(); +} + +async function add(user) { + const [id] = await db('users').insert(user); + return findById(id); +} \ No newline at end of file diff --git a/users/validation.js b/users/validation.js new file mode 100644 index 000000000..7f09c6532 --- /dev/null +++ b/users/validation.js @@ -0,0 +1,16 @@ +function validateUser(user) { + let errors = []; + + if (!user.username || user.username.length >= 7) { + errors.push('Username must have at least seven characters'); + } + if (!user.password || user.password.length >= 7) { + errors.push('Password must be at least seven characters') + } + return { + isSuccessful: errors.length > 0 ? false : true, + errors, + } +} + +module.exports = { validateUser } \ No newline at end of file From da6bf6b44d6e4f0e99e9fc220a315c903a54ec95 Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 14 Sep 2020 20:04:03 -0500 Subject: [PATCH 2/3] update --- README.md | 4 +++ api/{server.test..js => server..spec.js} | 0 api/server.js | 26 +++++++++--------- auth/auth.spec.js | 35 ++++++++++++++++++++++++ index.js | 2 +- jokes/jokes-router.js | 4 +-- 6 files changed, 55 insertions(+), 16 deletions(-) rename api/{server.test..js => server..spec.js} (100%) create mode 100644 auth/auth.spec.js diff --git a/README.md b/README.md index 71eb93186..3dd1d4c9e 100644 --- a/README.md +++ b/README.md @@ -25,12 +25,16 @@ Commit your code regularly and meaningfully. This helps both you (in case you ev Be prepared to demonstrate your understanding of this week's concepts by answering questions on the following topics. You might prepare by writing down your own answers before hand. 1. Differences between using _sessions_ or _JSON Web Tokens_ for authentication. + Sessions use cookies to create instances that store the user's data and remains in memory until the instance is deleted. With tokens the data is stored and passed back and forth. This is best suited for communication between trusted devices. 2. What does `bcrypt` do to help us store passwords in a secure manner. + bcrypt provides an efficent way for us to hash our passwords. 3. How are unit tests different from integration and end-to-end testing. + Unit test only focus on a relativley small piece of the code. Intergration tests focus on two "layers" of code and how well they communicate with each other. End-to-end testing focuses on the functionality of the entire program. 4. How _Test Driven Development_ changes the way we write applications and tests. + TDD makes you more mindful of how clean code should be written and how each piece works together before you begin writing the program. You are expected to be able to answer questions in these areas. Your responses contribute to your Sprint Challenge grade. diff --git a/api/server.test..js b/api/server..spec.js similarity index 100% rename from api/server.test..js rename to api/server..spec.js diff --git a/api/server.js b/api/server.js index 44205d215..bc2523674 100644 --- a/api/server.js +++ b/api/server.js @@ -17,16 +17,16 @@ server.use('/api/jokes', authenticate, jokesRouter); module.exports = server; -function checkRole(user) { - return (req, res, next) => { - if ( - req.decodedToken && - req.decodedToken.role && - req.decodedToken.role.toLowerCase() === user - ) { - next() - } else { - res.status(403).json({ message: 'Must be logged in' }) - } - } -} \ No newline at end of file +// function checkRole(user) { +// return (req, res, next) => { +// if ( +// req.decodedToken && +// req.decodedToken.role && +// req.decodedToken.role.toLowerCase() === user +// ) { +// next() +// } else { +// res.status(403).json({ message: 'Must be logged in' }) +// } +// } +// } \ No newline at end of file diff --git a/auth/auth.spec.js b/auth/auth.spec.js new file mode 100644 index 000000000..54574584b --- /dev/null +++ b/auth/auth.spec.js @@ -0,0 +1,35 @@ +let supertest = require('supertest'); +let server = require('../api/server'); + +let authRouter = require('./auth-router'); +let db = require('../database/dbConfig'); + + + describe("create user", () => { + it('should register new user', async () => { + const res = await supertest(server) + .post("/api/auth/register") + .send({ + username: "Reggie", + password: "321cba" + }) + expect(res.statusCode).toBe(201) + expect(res.type).toBe("application/json") + }) + + it("Should return 200 status", async() => { + let user = { + username: "Reggie", + password: "321cba" + } + + return await supertest(server) + .post('/api/auth/login') + .send(user) + .then(res => { + expect(res.status).toBe(200) + expect(res.body.message).toEqual("Welcome Reggie!" ) + + }) + }) + }) \ No newline at end of file diff --git a/index.js b/index.js index fd80bbe6d..f20e7d3c9 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,6 @@ const server = require('./api/server.js'); -const PORT = process.env.PORT || 3300; +const PORT = process.env.PORT || 3000; server.listen(PORT, () => { console.log(`\n=== Server listening on port ${PORT} ===\n`); }); diff --git a/jokes/jokes-router.js b/jokes/jokes-router.js index 3b41333a7..c0e93a52f 100644 --- a/jokes/jokes-router.js +++ b/jokes/jokes-router.js @@ -4,7 +4,7 @@ const restricted = require('../auth/middleware') const router = require('express').Router(); -router.get('/', (req, res) => { +router.get('/', restricted(), (req, res) => { const requestOptions = { headers: { accept: 'application/json' }, }; @@ -19,7 +19,7 @@ router.get('/', (req, res) => { }); }); -router.get('/user', restricted, (req, res) => { +router.get('/user', restricted(), (req, res) => { Users.find() .then(param => { res.status(200).json(param) From 15b4ea6621fff29459852c0fbea9a218b56fcd95 Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 14 Sep 2020 20:36:46 -0500 Subject: [PATCH 3/3] update --- auth/authenticate-middleware.js | 48 ++++++++++++++++++++++----------- jokes/jokes-router.js | 2 +- package.json | 2 ++ 3 files changed, 35 insertions(+), 17 deletions(-) diff --git a/auth/authenticate-middleware.js b/auth/authenticate-middleware.js index 4068880a6..cd870a187 100644 --- a/auth/authenticate-middleware.js +++ b/auth/authenticate-middleware.js @@ -1,18 +1,34 @@ const jwt = require('jsonwebtoken'); -const { jwtSecret } = require('../secrets/authSecret'); +// const { jwtSecret } = require('../secrets/authSecret'); -module.exports = (req, res, next) => { - const { authorization } = req.headers; - if (authorization) { - jwt.verify(authorization, jwtSecret, (err, decodedToken) => { - if (err) { - res.status(401).json({ message: "Invalid credentials" }); - } else { - req.decodedToken = decodedToken; - next(); - } - }); - } else { - res.status(400).json({ message: "Please enter credentials" }); - } -}; \ No newline at end of file + +function restrict() { + return async (req, res, next) => { + const authError = {err: 'invalid credentials'} + try{ + const token = req.headers.authorization + if(!token) {return res.status(401).json(authError)} + jwt.verify(token, 'safe', (err, decoded) => { + if(err) {return res.status(401).json(authError)} + next() + }) + } + catch(err) {next(err)} + } + } + + +module.exports = restrict + + // = req.headers; + // if (authorization) { + // jwt.verify(authorization, jwtSecret, (err, decodedToken) => { + // if (err) { + // res.status(401).json({ message: "Invalid credentials" }); + // } else { + // req.decodedToken = decodedToken; + // next(); + // } + // }); + // } else { + // res.status(400).json({ message: "Please enter credentials" }); diff --git a/jokes/jokes-router.js b/jokes/jokes-router.js index c0e93a52f..73dc8c91d 100644 --- a/jokes/jokes-router.js +++ b/jokes/jokes-router.js @@ -1,6 +1,6 @@ const axios = require('axios'); const Users = require('../users/model') -const restricted = require('../auth/middleware') +const restricted = require('../auth/authenticate-middleware') const router = require('express').Router(); diff --git a/package.json b/package.json index 34dbede94..a59d1cf95 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,9 @@ "dependencies": { "axios": "^0.19.2", "bcryptjs": "^2.4.3", + "cookie-parser": "^1.4.5", "cors": "^2.8.5", + "dotenv": "^8.2.0", "express": "^4.17.1", "helmet": "^3.22.0", "jsonwebtoken": "^8.5.1",