Skip to content
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

Third #88

Open
wants to merge 15 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
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@

Dad jokes are all the rage these days! In this challenge, you will build a real wise-guy application.







Users must be able to call the `[POST] /api/auth/register` endpoint to create a new account, and the `[POST] /api/auth/login` endpoint to get a token.

We also need to make sure nobody without the token can call `[GET] /api/jokes` and gain access to our dad jokes.
Expand All @@ -35,6 +41,15 @@ Your finished project must include all of the following requirements (further in
- Codegrade is running some tests you cannot see in this repo. Make sure to comply with project instructions to the letter!
- Do not exceed 2^8 rounds of hashing with `bcryptjs`.
- If you use environment variables make sure to provide fallbacks in the code (e.g. `process.env.SECRET || "shh"`).









- You are welcome to create additional files but **do not move or rename existing files** or folders.
- Do not alter your `package.json` file except to install extra libraries. Do not update existing packages.
- The database already has the `users` table, but if you run into issues, the migration is available.
Expand Down
42 changes: 36 additions & 6 deletions api/auth/auth-router.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
const router = require('express').Router();
const bcrypt = require('bcryptjs')
const {checkUsP, checkUsername, insert, checkLogin} = require('../middleware/users-middleware')
const jwt = require('jsonwebtoken')
const {JWT_SECRET} = require('../../api/secrets/index')

router.post('/register', (req, res) => {
res.end('implement register, please!');
router.post('/register', checkUsP, checkUsername, async (req, res, next) => {
await insert(req, res, next)
});
/*
IMPLEMENT
You are welcome to build additional middlewares to help with the endpoint's functionality.
Expand All @@ -27,10 +32,35 @@ 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".
*/
});
function buildToken (user) {
const payload = {
subject: user.id,
role: 'all access',
username: user.username
}
const options = {
expiresIn: '1d'
}
return jwt.sign(payload, JWT_SECRET, options)
}

router.post('/login', (req, res) => {
res.end('implement login, please!');
router.post('/login', checkUsP, checkLogin, async (req, res, next) => {
try {
if (bcrypt.compareSync(req.body.password, req.user.password)) {
const token = buildToken(req.body)
res.status(200).json({
message: `welcome, ${req.user.username}`,
token
})
}
else {
next({status: 401, message: 'invalid credentials'})
}
}
catch (error) {
next(error)
}
});
/*
IMPLEMENT
You are welcome to build additional middlewares to help with the endpoint's functionality.
Expand All @@ -54,6 +84,6 @@ 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".
*/
});


module.exports = router;
3 changes: 2 additions & 1 deletion api/jokes/jokes-router.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// do not make changes to this file
const router = require('express').Router();
const jokes = require('./jokes-data');
const restricted = require('../middleware/restricted')

router.get('/', (req, res) => {
router.get('/', restricted, (req, res) => {
res.status(200).json(jokes);
});

Expand Down
25 changes: 23 additions & 2 deletions api/middleware/restricted.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
module.exports = (req, res, next) => {
next();
const {secret} = require('../secrets/index')
const jwt = require('jsonwebtoken')


module.exports = async (req, res, next) => {
const token = req.headers.authorization
return res.status(406).json({message: token})
// if (token) {
// await jwt.verify(token, secret, (error, decoded) => {
// if (error) {
// next({status: 401, message: 'token invalid', er: error})
// }
// else {
// req.decodedJwt = decoded
// next()
// }
// })
// }
// else {
// next({status:401, message: 'token required'})
// }


/*
IMPLEMENT

Expand Down
56 changes: 56 additions & 0 deletions api/middleware/users-middleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
const db = require('../../data/dbConfig')
const bcrypt = require('bcryptjs')


async function checkUsername (req, res, next) {
const username = req.body.username
const exists = await db('users').select('*').where('username', username).first()
if (exists) {
next({status: 400, message: 'username taken'})
}
else next()
}

async function checkLogin (req, res, next) {
try {
const user = await db('users').select('*').where('username', req.body.username).first()
if (user) {
req.user = user
next()
}
else {
next({status: 401, message: 'invalid credentials'})
}
}
catch (error) {
next(error)
}
}

async function checkUsP (req, res, next) {
const {username, password} = req.body

if (!username || !password) {
next({status: 402, message: 'username and password required'})
}
else next()
}

async function insert (req, res, next) {
try {
let {username, password} = req.body
password = await bcrypt.hashSync(password, 8)

await db('users').insert({username, password})

const user = await db('users').select('*').where('username', username).first()
res.status(201).json(user)
}
catch (error) {
next(error)
}
}

module.exports = {
checkUsername, insert, checkUsP, checkLogin
}
5 changes: 5 additions & 0 deletions api/secrets/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const JWT_SECRET = process.env.JWT_SECRET || 'shh'

module.exports = {
JWT_SECRET
}
14 changes: 13 additions & 1 deletion api/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,16 @@ server.use(express.json());
server.use('/api/auth', authRouter);
server.use('/api/jokes', restrict, jokesRouter); // only logged-in users should have access!

module.exports = server;

server.use('*', (req, res, next) => {
next({status: 404, message: 'not found'})
})

server.use( (error, req, res, next) => { //eslint-disable-line
res.status(error.status || 500).json({
message: error.message || 'error',
stack: error.stack
})
})

module.exports = server;
90 changes: 88 additions & 2 deletions api/server.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,90 @@
// Write your tests here

const db = require('../data/dbConfig')
const request = require('supertest')
const server = require('../api/server')

beforeAll( async () => {
await db.migrate.rollback()
await db.migrate.latest()
})

beforeEach( async () => {
await db.seed.run()
})

// afterAll( async () => {
// await db.destroy()
// })


test('sanity', () => {
expect(true).toBe(false)
expect(true).toBe(true)
})






describe('[POST] /api/auth/register', () => {
test('gets success status', async () => {
const creds = {username: 'hello', password: '1234'}
const res = await request(server).post('/api/auth/register').send(creds)
expect(res.status).toBe(201)
})
test('adds credentials to database', async () => {
const creds = {username: 'hello', password: '1234'}
await request(server).post('/api/auth/register').send(creds)
const res = await db('users').select('username').where('username', creds.username).first()
expect(res.username).toBe(creds.username)
})
})






describe('[POST] /api/auth/login', () => {
test('gets a success status', async () => {
const creds = {username: 'hello', password: '1234'}
const res1 = await request(server).post('/api/auth/register').send(creds)
expect(res1.status).toBe(201)
const res = await request(server).post('/api/auth/login').send(creds)
expect(res.status).toBe(200)
})
test('valid login gets a token', async () => {
const creds = {username: 'hello', password: '1234'}
await request(server).post('/api/auth/register').send(creds)
const res = await request(server).post('/api/auth/login').send(creds)
expect(res.body.token).toBeDefined()
})
})






describe('[GET] /api/jokes', () => {
test.only('valid token gets the jokes', async () => {

const creds = {username: 'hello', password: '1234'}
const res1 = await request(server).post('/api/auth/register').send(creds)
expect(res1.status).toBe(201)

const res2 = await request(server).post('/api/auth/login').send(creds)
expect(res2.body.token).toBeDefined()

const res3 = await request(server).get('/api/jokes').set('Authorization', res2.body.token)
expect(res3.body.message).toHaveLength(3)
})



test('no token gets error', async () => {
const res = await request(server).get('/api/jokes')
expect(res.body.jokes).not.toBeDefined()
expect(res.status).toBe(401)
})
})
24 changes: 16 additions & 8 deletions data/migrations/20201123181212_users.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
exports.up = function (knex) {
return knex.schema.createTable('users', users => {
users.increments();
users.string('username', 255).notNullable().unique();
users.string('password', 255).notNullable();
});
exports.up = async function (knex) {

await knex.schema.createTable('users', users => {
users.increments();
users.string('username', 255).notNullable().unique();
users.string('password', 255).notNullable();
});

await knex.schema.createTable('jokes', table => {
table.increments('db_id')
table.string('id').notNullable()
table.string('joke', 400).notNullable().unique()
})
};

exports.down = function (knex) {
return knex.schema.dropTableIfExists('users');
exports.down = async function (knex) {
await knex.schema.dropTableIfExists('users');
await knex.schema.dropTableIfExists('jokes');
};
15 changes: 15 additions & 0 deletions data/migrations/20231121002935_jokes-migrate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* @param { import("knex").Knex } knex
* @returns { Promise<void> }
*/
exports.up = function(knex) {

};

/**
* @param { import("knex").Knex } knex
* @returns { Promise<void> }
*/
exports.down = function(knex) {

};
8 changes: 8 additions & 0 deletions data/seeds/initial-seed.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const jokes = require('../../api/jokes/jokes-data')

exports.seed = async function(knex) {
// Deletes ALL existing entries
await knex('users').truncate()
await knex('jokes').truncate()
await knex('jokes').insert(jokes);
};
13 changes: 13 additions & 0 deletions data/seeds/jokes-seed.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* @param { import("knex").Knex } knex
* @returns { Promise<void> }
*/
exports.seed = async function(knex) {
// Deletes ALL existing entries
await knex('table_name').del()
await knex('table_name').insert([
{id: 1, colName: 'rowValue1'},
{id: 2, colName: 'rowValue2'},
{id: 3, colName: 'rowValue3'}
]);
};
3 changes: 2 additions & 1 deletion knexfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ const sharedConfig = {
client: 'sqlite3',
useNullAsDefault: true,
migrations: { directory: './data/migrations' },
seeds: { directory: './data/seeds' },
pool: { afterCreate: (conn, done) => conn.run('PRAGMA foreign_keys = ON', done) },
}

module.exports = {
development: {
...sharedConfig,
connection: { filename: './data/auth.db3' },
seeds: { directory: './data/seeds' },

},
testing: {
...sharedConfig,
Expand Down
Loading