Skip to content
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
9 changes: 9 additions & 0 deletions api/models/user.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const mongoose = require("mongoose");

const schema = mongoose.Schema({
username: String,
password: String,
admin: { type: Boolean, default: false }
});

module.exports = mongoose.model("User", schema);
136 changes: 136 additions & 0 deletions api/routes/auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
const router = require("express").Router();
const jsonwebtoken = require("jsonwebtoken");
const bcrypt = require("bcrypt");
const saltRounds = 10;
const User = require("../models/user");
const { SECRET_PW } = process.env;

router.post("/signup", async (req, res, next) => {
const status = 201;
try {
const { username, password, admin } = req.body;
const user = await User.findOne({ username });
if (user) throw new Error(`User ${username} already exists.`);

const hashedPassword = await bcrypt.hash(password, saltRounds);
const response = await User.create({
username,
password: hashedPassword,
admin
});
res.status(status).json({ status, response });
} catch (error) {
console.log(error);
const e = new Error(`User ${username} already exists.`);
e.status = 400;
next(e);
}
});

router.post("/login", async (req, res, next) => {
const status = 201;
try {
const { username, password } = req.body;
const checkForUser = await User.findOne({
username
});
if (!checkForUser) throw new Error(`User ${username} does not exist.`);

const user = await bcrypt.compare(password, checkForUser.password);
if (!user) throw new Error(`Password is invalid.`);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You'll want to keep these error messages the same, otherwise it's easier for someone to "guess" at a username. Having the same error message is more secure.


const payload = { id: user._id };
const options = { expiresIn: "1 day" };
const token = jsonwebtoken.sign(payload, "SECRET_PW", options);

// res.status(status).json({ status, response: `You have been logged in.` });
res.status(status).json({ status, token });
} catch (error) {
console.log(error);
const e = new Error("Login credentials incorrect.");
e.status = 401;
next(e);
}
});

// Admin should be able to change permissions for other users
// NOTE: Can't get this working...come back to it.
// router.patch("/users/:id/permissions", async (req, res, next) => {
// const { id } = req.params;

// try {
// const token = req.headers.authorization.split("Bearer ")[1];
// const payload = jsonwebtoken.verify(token, SECRET_PW);
// const user = await User.findOne({ _id: payload.id }).select(
// "-__v -password"
// );
// const isAdminUser = user.admin;

// if (!isAdminUser) {
// const error = new Error(
// "The JWT token is for a user who is not an admin."
// );
// error.message = 401;
// return next(error);
// }

// if (!user) {
// const error = new Error("User cannot be found.");
// error.message = 404;
// return next(error);
// }

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the order you have this in is a bit wrong. First you'll want to check for a user, then you'll want to check to see whether or not that user is an admin.


// const response = await User.findByIdAndUpdate(
// id,
// { admin: req.body.admin },
// { new: true }
// );
// const status = 204;
// res.json({ status, response });
// } catch (e) {
// console.error(e);
// }
// });

router.patch("/users/:id/permissions", async (req, res, next) => {
try {
const token = req.headers.authorization.split("Bearer ")[1];
const payload = jsonwebtoken.verify(token, SECRET_PW);
const user = await User.findOne({ _id: payload.id }).select(
"-__v -password"
);
const isAdminUser = user.admin;

if (!isAdminUser) {
const error = new Error(
"The JWT token is for a user who is not an admin."
);
error.message = 401;
return next(error);
}

if (!user) {
const error = new Error("User cannot be found.");
error.message = 404;
return next(error);
}

const AdminStatus = user.admin;
if (AdminStatus === true) {
user.admin = false;
} else {
user.admin = true;
}

// Update the user permissions for the current user
await user.save();

const response = await User.findById(user._id).select("-__v");
const status = 204;
res.json({ status, response });
} catch (e) {
console.error(e);
}
});

module.exports = router;
132 changes: 77 additions & 55 deletions api/routes/books.js
Original file line number Diff line number Diff line change
@@ -1,78 +1,100 @@
const router = require('express').Router()
const Book = require('../models/book')
const router = require("express").Router();
const Book = require("../models/book");
const User = require("../models/user");
const jsonwebtoken = require("jsonwebtoken");
const bcrypt = require("bcrypt");
const saltRounds = 10;
const { SECRET_PW } = process.env;

router.get('/', async (req, res, next) => {
const status = 200
const response = await Book.find().select('-__v')

res.json({ status, response })
})
router.get("/", async (req, res, next) => {
const status = 200;
const response = await Book.find().select("-__v");

router.get('/:id', async (req, res, next) => {
const { id } = req.params
const status = 200
res.json({ status, response });
});

router.get("/:id", async (req, res, next) => {
const { id } = req.params;
const status = 200;
try {
const response = await Book.findById(id).select('-__v')
if (!response) throw new Error(`Invalid Book _id: ${id}`)
res.json({ status, response })
const response = await Book.findById(id).select("-__v");
if (!response) throw new Error(`Invalid Book _id: ${id}`);

res.json({ status, response });
} catch (e) {
console.error(e)
const error = new Error(`Cannot find book with id ${id}.`)
error.status = 404
next(error)
console.error(e);
const error = new Error(`Cannot find book with id ${id}.`);
error.status = 404;
next(error);
}
})
});

// You should only be able to create a book if the user is an admin
router.post('/', async (req, res, next) => {
const status = 200
router.post("/", async (req, res, next) => {
const status = 200;
try {
const book = await Book.create(req.body)
if (!book) throw new Error(`Request body failed: ${JSON.stringify(req.body)}`)

const response = await Book.findById(book._id).select('-__v')
res.json({ status, response })
const token = req.headers.authorization.split("Bearer ")[1];
const payload = jsonwebtoken.verify(token, SECRET_PW);
const user = await User.findOne({ _id: payload.id }).select(
"-__v -password"
);
const isAdminUser = user.admin;
// Check for admin user
if (!isAdminUser) {
const error = new Error(
"Sorry, please contact your admin to create a new book."
);
error.message = 401;
return next(error);
}
// If user is an admin, then create a book.
const book = await Book.create(req.body);
if (!book)
throw new Error(`Request body failed: ${JSON.stringify(req.body)}`);

const response = await Book.findById(book._id).select("-__v");
res.json({ status, response });
} catch (e) {
console.error(e)
const message = 'Failure to create. Please check request body and try again.'
const error = new Error(message)
error.status = 400
next(error)
console.error(e);
const message =
"Failure to create. Please check request body and try again.";

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having the right-hand assignment be on a different line works fine but looks a bit dicey.

const error = new Error(message);
error.status = 400;
next(error);
}
})
});

// You should only be able to reserve a book if a user is logged in
router.patch('/:id/reserve', async (req, res, next) => {
const { id } = req.params
router.patch("/:id/reserve", async (req, res, next) => {
const { id } = req.params;
try {
const book = await Book.findById(id)
const book = await Book.findById(id);
if (!book) {
const error = new Error(`Invalid Book _id: ${id}`)
error.message = 404
return next(error)
const error = new Error(`Invalid Book _id: ${id}`);
error.message = 404;
return next(error);
}

book.reserved.status = true
book.reserved.status = true;
// Set the reserved memberId to the current user
await book.save()
const response = await Book.findById(book._id).select('-__v')
const status = 200
res.json({ status, response })
await book.save();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You'll want to include a user id as well when reserving a book.


const response = await Book.findById(book._id).select("-__v");
const status = 200;
res.json({ status, response });
} catch (e) {
console.error(e)
console.error(e);
}
})
});

// You should only be able to return a book if the user is logged in
// and that user is the one who reserved the book
router.patch('/:id/return', async (req, res, next) => {
const status = 200
const message = 'You must implement this route!'
console.log(message)
res.status(status).json({ status, message })
})
router.patch("/:id/return", async (req, res, next) => {
const status = 200;
const message = "You must implement this route!";

console.log(message);
res.status(status).json({ status, message });
});

module.exports = router
module.exports = router;
41 changes: 21 additions & 20 deletions app.js
Original file line number Diff line number Diff line change
@@ -1,37 +1,38 @@
const { MONGO_DB_CONNECTION, NODE_ENV, PORT } = process.env
const express = require('express')
const mongoose = require('mongoose')
const app = express()
const { MONGO_DB_CONNECTION, NODE_ENV, PORT } = process.env;
const express = require("express");
const mongoose = require("mongoose");
const app = express();

// Database Connection
if (MONGO_DB_CONNECTION) {
mongoose.connect(MONGO_DB_CONNECTION, { useNewUrlParser: true })
console.log('Connected to database...')
mongoose.connect(MONGO_DB_CONNECTION, { useNewUrlParser: true });
console.log("Connected to database...");
} else {
console.log('Could not connect to database!')
console.log("Could not connect to database!");
}

// Application-level Middleware
if (NODE_ENV === 'development') app.use(require('morgan')('dev'))
app.use(require('body-parser').json())
if (NODE_ENV === "development") app.use(require("morgan")("dev"));
app.use(require("body-parser").json());

// Routes
app.use('/api/books', require('./api/routes/books'))
app.use("/api/", require("./api/routes/auth"));
app.use("/api/books", require("./api/routes/books"));

// Not Found Handler
app.use((req, res, next) => {
const error = new Error(`Could not ${req.method} ${req.path}`)
error.status = 404
next(error)
})
const error = new Error(`Could not ${req.method} ${req.path}`);
error.status = 404;
next(error);
});

// Error Handler
app.use((err, req, res, next) => {
if (NODE_ENV === 'development') console.error(err)
const { message, status } = err
res.status(status).json({ status, message })
})
if (NODE_ENV === "development") console.error(err);
const { message, status } = err;
res.status(status).json({ status, message });
});

// Open Connection
const listener = () => console.log(`Listening on Port ${PORT}!`)
app.listen(PORT, listener)
const listener = () => console.log(`Listening on Port ${PORT}!`);
app.listen(PORT, listener);
Loading