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 cc6ee6d93..dbfc74ec1 100644
Binary files a/database/auth.db3 and b/database/auth.db3 differ
diff --git a/database/dbConfig.js b/database/dbConfig.js
index f40399bcb..303ff01d1 100644
--- a/database/dbConfig.js
+++ b/database/dbConfig.js
@@ -1,5 +1,5 @@
const knex = require('knex');
const knexConfig = require('../knexfile.js');
-
-module.exports = knex(knexConfig.development);
+const env = process.env.NODE_ENV || "development";
+module.exports = knex(knexConfig[env]);
diff --git a/database/seeds/001.js b/database/seeds/001.js
new file mode 100644
index 000000000..26b59a4ef
--- /dev/null
+++ b/database/seeds/001.js
@@ -0,0 +1,13 @@
+
+exports.seed = function(knex) {
+ // Deletes ALL existing entries
+ return knex('users').del()
+ .then(function () {
+ // Inserts seed entries
+ return knex('users').insert([
+ {id: 1, username: 'rowValue1', password:'testing1'},
+ {id: 2, username: 'rowValue2', password:'testing12fasa'},
+ {id: 3, username: 'rowValue3', password:'testing1!!@'},
+ ]);
+ });
+};
diff --git a/database/test.db3 b/database/test.db3
new file mode 100644
index 000000000..e88e319e8
Binary files /dev/null and b/database/test.db3 differ
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"
}
}