diff --git a/README.md b/README.md index 315af9d95..a507f3d86 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,13 @@ Your finished project must include all of the following requirements (further in Be prepared to demonstrate your understanding of this week's concepts by answering questions on the following topics. 1. Differences between using _sessions_ or _JSON Web Tokens_ for authentication. +The differences between using _sessions_ or _JSON Web Tokens_ for authentication is that the session cookies are stored in the server's memory. JSON web tokens are stateless. Session based authentication, the server will create a session for a user after the user logs in. With token based authentication the server creates JWT with a secret and sends the JWT to the client. + 2. What does `bcryptjs` do to help us store passwords in a secure manner? +Bcrypt allows us to build a password security platform that scales with computation power. + 3. How are unit tests different from integration and end-to-end testing? +Units tests ensures that modules work well in isolation and play well together. End to end is functional testing and has a helper robot that behaves like a user to click around the app and verify that it functions correctly. + 4. How does _Test Driven Development_ change the way we write applications and tests? +TTD relies on the repetition and starts with desiging and developing tests for every small functioanlity of an application. \ No newline at end of file diff --git a/api/auth/auth-router.js b/api/auth/auth-router.js index 47d8e51ae..f6f2daf89 100644 --- a/api/auth/auth-router.js +++ b/api/auth/auth-router.js @@ -1,59 +1,89 @@ const router = require('express').Router(); +const express = require("express"); +const bcrypt = require("bcryptjs") +const jwt = require("jsonwebtoken") +const Users = require("./users-model") +const { restrict } = require("./users-middleware") +const router = express.Router(); -router.post('/register', (req, res) => { - res.end('implement register, please!'); - /* - IMPLEMENT - You are welcome to build additional middlewares to help with the endpoint's functionality. - DO NOT EXCEED 2^8 ROUNDS OF HASHING! - - 1- In order to register a new account the client must provide `username` and `password`: - { - "username": "Captain Marvel", // must not exist already in the `users` table - "password": "foobar" // needs to be hashed before it's saved - } - - 2- On SUCCESSFUL registration, - the response body should have `id`, `username` and `password`: - { - "id": 1, - "username": "Captain Marvel", - "password": "2a$08$jG.wIGR2S4hxuyWNcBf9MuoC4y0dNy7qC/LbmtuFBSdIhWks2LhpG" - } - - 3- On FAILED registration due to `username` or `password` missing from the request body, - the response body should include a string exactly as follows: "username and password required". - - 4- On FAILED registration due to the `username` being taken, - the response body should include a string exactly as follows: "username taken". - */ + +router.get("/users", restrict(), async (req, res, next) => { + try { + res.json(await Users.find()) + } catch(err) { + next(err) + } +}) + +router.post("/login", async (req, res, next)=>{ + try { + const { username, password } = req.body + const user = await Users.findByUsername(username) + + if (!user) { + return res.status(401).json({ + message: "Invalid Credentials", + }) + } + const passwordValid = await bcrypt.compare(password, user.password) + + if (!passwordValid) { + return res.status(401).json({ + message: "Invalid Credentials", + }) + } + const token = jwt.sign({ + userID: user.id, + userDepartment: user.department, + + }, process.env.JWT_SECRET) + + res.cookie("token", token) + + res.json({ + message: `Welcome ${user.username}!`, + }) + + } catch(err) { + next(err) + } }); -router.post('/login', (req, res) => { - res.end('implement login, please!'); - /* - IMPLEMENT - You are welcome to build additional middlewares to help with the endpoint's functionality. - - 1- In order to log into an existing account the client must provide `username` and `password`: - { - "username": "Captain Marvel", - "password": "foobar" - } - - 2- On SUCCESSFUL login, - the response body should have `message` and `token`: - { - "message": "welcome, Captain Marvel", - "token": "eyJhbGciOiJIUzI ... ETC ... vUPjZYDSa46Nwz8" - } - - 3- On FAILED login due to `username` or `password` missing from the request body, - the response body should include a string exactly as follows: "username and password required". - - 4- On FAILED login due to `username` not existing in the db, or `password` being incorrect, - the response body should include a string exactly as follows: "invalid credentials". - */ +router.post("/register", async (req, res, next)=>{ + try { + const { username, password, department } = req.body + const user = await Users.findByUsername(username) + + if (user) { + return res.status(409).json({ + message: "Username is already taken", + }) + } + + const newUser = await Users.add({ + username, + department, + password: await bcrypt.hash(password, 14) + }) + + res.status(201).json(newUser) + } catch(err) { + next(err) + } }); -module.exports = router; +router.get("/logout", async (req, res, next) => { + try { + req.session.destroy((err) => { + if (err) { + next(err) + } else { + res.status(204).end() + } + }) + } catch (err) { + next(err) + } +}) + +module.exports = router; \ No newline at end of file diff --git a/api/middleware/restricted.js b/api/middleware/restricted.js index a690e961e..8b10a675c 100644 --- a/api/middleware/restricted.js +++ b/api/middleware/restricted.js @@ -12,3 +12,36 @@ module.exports = (req, res, next) => { the response body should include a string exactly as follows: "token invalid". */ }; + + +const db = require("../data/config") + +async function add(user) { + const [id] = await db("users").insert(user) + return findById(id) +} + +function find() { + return db("users as u") + .select("u.id", "u.username", "u.department") +} + +function findByUsername(username) { + return db("users as u") + .where("u.username", username) + .first("u.id", "u.username", "u.password", "u.department") +} + +function findById(id) { + return db("users as u") + .select("u.id", "u.username") + .where("u.id", id) + .first() +} + +module.exports = { + add, + find, + findByUsername, + findById +} \ No newline at end of file diff --git a/api/server.test.js b/api/server.test.js index 96965c559..5a1489b38 100644 --- a/api/server.test.js +++ b/api/server.test.js @@ -2,3 +2,59 @@ test('sanity', () => { expect(true).toBe(false) }) + +const supertest = require("supertest") +const server = require("../server") + +test("GET /", async () => { + const res = await supertest(server).get("/") + expect(res.statusCode).toBe(200) + expect(res.type).toBe("application/json") + expect(res.body.message).toBe("Welcome to our API") +}) + +const supertest = require("supertest") +const server = require("../server") +const db = require("../data/config") + +beforeEach(async () => { + await db.seed.run() +}) + +afterAll(async () => { + await db.destroy() +}) + +describe("jokes integration tests", () => { + it("gets a list of jokes", async () => { + const res = await supertest(server).get("/jokes") + expect(res.statusCode).toBe(200) + expect(res.type).toBe("application/json") + expect(res.body.length).toBe(4) + }) +}) + + + it("get the joke by ID", async () => { + const res = await supertest(server).get("/jokes/2") + expect(res.statusCode).toBe(404) + expect(res.statusCode).toBe(200) + expect(res.type).toBe("application?json") + expect(res.body.id).toBe(2) + }) + + +it("get the joke by ID", async () => { + const res = await supertest(server).get("/jokes/50") + expect(res.statusCode).toBe(404) + expect(res.type).toBe("application?json") + expect(res.body.message).toBe("joke not found") +}) + +it("get the joke by ID", async () => { + const res = await supertest(server) + .post("/jokes") + .send({ name: "bilbo"}) + expect(res.statusCode).toBe(201) + expect(res.type).toBe("application?json") +})