Skip to content

auth and testing sprint #58

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
136 changes: 83 additions & 53 deletions api/auth/auth-router.js
Original file line number Diff line number Diff line change
@@ -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;
33 changes: 33 additions & 0 deletions api/middleware/restricted.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
56 changes: 56 additions & 0 deletions api/server.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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")
})