diff --git a/api/auth/auth-router.js b/api/auth/auth-router.js index 47d8e51ae..0ad78bb9d 100644 --- a/api/auth/auth-router.js +++ b/api/auth/auth-router.js @@ -1,7 +1,26 @@ +const secrets = require('../config/secrets.js'); +const jwt = require('jsonwebtoken'); const router = require('express').Router(); +const Users = require("../users/user-model"); +const bcrypt = require('bcryptjs'); +const { checkPayload, isUsernameUnique, validateLogin,} = require('../middleware/auth-middleware'); -router.post('/register', (req, res) => { +router.post('/register', isUsernameUnique , checkPayload,(req, res, next) => { res.end('implement register, please!'); + +const { username, password } = req.body + + const hash = bcrypt.hashSync(password, 8); + Users.add({ username, password: hash }) + .then(newUser => { + res.status(200).json(newUser) +}) + .catch(next) +}); + + + + /* IMPLEMENT You are welcome to build additional middlewares to help with the endpoint's functionality. @@ -27,10 +46,27 @@ router.post('/register', (req, res) => { 4- On FAILED registration due to the `username` being taken, the response body should include a string exactly as follows: "username taken". */ -}); -router.post('/login', (req, res) => { + +router.post('/login' , validateLogin , checkPayload,(req, res, next) => { res.end('implement login, please!'); + const { username, password } = req.body + + Users.findByUsername(username) + .then(([user]) => { + if (user && bcrypt.compareSync(password, user.password)) { + const token = createToken(user) + res.status(200).json({ + message: `hello ${username}`, + token + }) + } else { + next({ status:401, message: 'invalid credentials' }) + } + }) + .catch(next) + + }); /* IMPLEMENT You are welcome to build additional middlewares to help with the endpoint's functionality. @@ -54,6 +90,17 @@ router.post('/login', (req, res) => { 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". */ -}); +function createToken(user) { +const payload = { + subject: user.id, + username: user.username, + +}; + +const options = { + expiresIn: '1d', +}; + return jwt.sign(payload, secrets.jwtSecret, options); +} module.exports = router; diff --git a/api/config/secrets.js b/api/config/secrets.js new file mode 100644 index 000000000..cb21fb29c --- /dev/null +++ b/api/config/secrets.js @@ -0,0 +1,3 @@ +module.exports = { + jwtSecret: process.env.JWT_SECRET || 'its a secret' + }; \ No newline at end of file diff --git a/api/jokes/jokes-router.js b/api/jokes/jokes-router.js index f663f983c..2a723075d 100644 --- a/api/jokes/jokes-router.js +++ b/api/jokes/jokes-router.js @@ -1,8 +1,8 @@ // do not make changes to this file const router = require('express').Router(); const jokes = require('./jokes-data'); - -router.get('/', (req, res) => { +const restricted = require('../middleware/restricted.js'); +router.get('/', restricted, (req, res) => { res.status(200).json(jokes); }); diff --git a/api/middleware/auth-middleware.js b/api/middleware/auth-middleware.js new file mode 100644 index 000000000..b0013b751 --- /dev/null +++ b/api/middleware/auth-middleware.js @@ -0,0 +1,47 @@ +const Users = require('../users/user-model') + +const checkPayload = (req, res, next) => { +try { + const { username, password } = req.body + if (!username || !password) { + res.status(404).json({message: 'A username and password is required'}) +} else { + req.username = username + req.password = password + next() +} +} catch (err) { + next(err) +}} + + +const isUsernameUnique = async (req, res, next) => { + try { + const existingUsername = await Users.findByUsername(req.body.username) + if (!existingUsername.length) { + next() +} else { + next({ status: 401, message: 'this username is already taken' }) +} +} catch (err) { + next(err) +}} + + +const validateLogin = async (req, res, next) => { +try { +const user = await Users.findByUsername(req.body.username) + const password = await Users.validatePassword(req.body.password) + if (!user || !password) { + next({ status: 400, message: 'invalid credentials' }) +} else { + next() +} +} catch (err) { + next(err) +}} + +module.exports = { checkPayload, + isUsernameUnique, + validateLogin, +}; \ No newline at end of file diff --git a/api/middleware/restricted.js b/api/middleware/restricted.js index a690e961e..bf99ccc01 100644 --- a/api/middleware/restricted.js +++ b/api/middleware/restricted.js @@ -1,5 +1,19 @@ +const jwt = require('jsonwebtoken'); +const { JWT_SECRET } = require('../config/secrets'); + + module.exports = (req, res, next) => { - next(); +const token = req.headers.authorization; +if (token) { jwt.verify(token, JWT_SECRET, (error, decoded) => { + if (error) { + next({ status: 401, message: 'token invalid' }); +} +else {req.decodedJwt = decoded; + next(); +} +}); +} else {next({ status: 401, message: 'token required' }); +}}; /* IMPLEMENT @@ -11,4 +25,4 @@ module.exports = (req, res, next) => { 3- On invalid or expired token in the Authorization header, the response body should include a string exactly as follows: "token invalid". */ -}; + diff --git a/api/server.js b/api/server.js index 33320b871..2f2c8ab47 100644 --- a/api/server.js +++ b/api/server.js @@ -1,3 +1,4 @@ +/* eslint-disable no-unused-vars */ const express = require('express'); const cors = require('cors'); const helmet = require('helmet'); @@ -12,8 +13,13 @@ const server = express(); server.use(helmet()); server.use(cors()); server.use(express.json()); - +server.use((err, req, res, next) => { + res.status(err.status || 500).json({ + message: err.message, + stack: err.stack, +}) +}) server.use('/api/auth', authRouter); server.use('/api/jokes', restrict, jokesRouter); // only logged-in users should have access! - +server.get('/', (req, res) => {res.send('Hello there traveler')}) module.exports = server; diff --git a/api/server.test.js b/api/server.test.js index 96965c559..b54ce91ca 100644 --- a/api/server.test.js +++ b/api/server.test.js @@ -1,4 +1,41 @@ -// Write your tests here -test('sanity', () => { - expect(true).toBe(false) +const request = require('supertest'); +const server = require('./server'); + +test('environment', () => { +expect(process.env.NODE_ENV).toBe('testing')}) + + +describe('[POST] /register', () => { test('responds with error when no username', async () => { + + const res = await request(server).post('/api/auth/register').send({ + username: '', + password: 'password', }) + expect(res.body).toMatchObject({message: 'username and password required'}) +}) +test('responds with error when no password', async () => { + + const res = await request(server).post('/api/auth/register').send({ + username: 'sakura', + password: '', +}) + expect(res.body).toMatchObject({message: 'username and password required'}) +}) +}) + +describe('[POST] /login', () => { test('responds with error when no username', async () => { + + const res = await request(server).post('/login').send({ + username: '', + password: 'username' +}) + expect(res.status).toBe(404) +}) + test('responds with error when no password', async () => { + + const res = await request(server).post('/api/auth/login').send({ + username: 'MightGuy', + password: '', +}) + expect(res.body).toMatchObject({message: 'username and password required'}) +})}) \ No newline at end of file diff --git a/api/users/user-model.js b/api/users/user-model.js new file mode 100644 index 000000000..ba31a2bd6 --- /dev/null +++ b/api/users/user-model.js @@ -0,0 +1,33 @@ +const db = require('../../data/dbConfig'); + +function find() { +return db('users') +} + +function findById(id) { +return db('users') + .where('id', id) + .first() +} + +function findByUsername(username) { +return db('users') + .where('username', username) +} + +function validatePassword(password) { +return db('users') + .where('password', password) +} + +async function add(user) { +const id = await db('users').insert(user) + return findById(id) +} + +module.exports = { find, + findById, + findByUsername, + validatePassword, + add, +}; \ No newline at end of file diff --git a/data/seeds/1-users.js b/data/seeds/1-users.js new file mode 100644 index 000000000..26d083638 --- /dev/null +++ b/data/seeds/1-users.js @@ -0,0 +1,11 @@ +exports.seed = function(knex,) { +return knex('users') + .truncate() + .then(function() { +return knex('users').insert([ + { username: 'naruto', password: 'password87'}, + { username: 'luffy', password: '00pirateking00'}, + { username: 'ichigo', password: 'number1bankai'}, +]); +}); +} \ No newline at end of file