diff --git a/app.js b/app.js index 94b5d27..0f37d55 100644 --- a/app.js +++ b/app.js @@ -5,7 +5,9 @@ const mongoose = require("mongoose"); const cookieParser = require("cookie-parser"); require("dotenv").config(); -const router = require("./routes.js"); +const indexRouter = require("./routes/index.js"); +const postRouter = require("./routes/post.js"); +const userRouter = require("./routes/user.js"); const app = express(); @@ -38,6 +40,8 @@ app.use(express.static(path.join(__dirname, "public"))); app.use(cors()); // routing -app.use("/", router); +app.use("/", indexRouter); +app.use("/post", postRouter); +app.use("/user", userRouter); module.exports = app; diff --git a/controllers/post.js b/controllers/post.js index de9aa1c..7165862 100644 --- a/controllers/post.js +++ b/controllers/post.js @@ -1,13 +1,39 @@ const { default: mongoose } = require("mongoose"); const asyncHandler = require("express-async-handler"); const { body, validationResult } = require("express-validator"); +const jwt = require("jsonwebtoken"); const Post = require("../models/post.js"); const Comment = require("../models/comment.js"); +const hasToken = require("../middleware/hasToken.js"); +const sameAuthor = require("../middleware/sameAuthor.js"); -// returns json object with ALL posts and comments exports.index = asyncHandler(async (req, res, next) => { - const dbPosts = await Post.find().lean().exec(); + // get all posts + const dbPosts = await Post.find({ published: true }).lean().exec(); + // check for authorization + const token = req.cookies.JWT_TOKEN; + blocc: if (token) { + try { + jwt.verify(token, process.env.SECRET_KEY); + } catch { + break blocc; + } + // run through unpublished posts + const unpublished = await Post.find({ published: false }).lean().exec(); + for (let z = 0; z < unpublished.length; z++) { + const post = unpublished[i]; + const author = post.author; + let opts = { + expiresIn: "1d", + }; + const authorToken = jwt.sign({ author }, process.env.SECRET_KEY, opts); + // if any are by the current user, append to dbPosts + if (token == authorToken) { + dbPosts.push(post); + } + } + } const posts = []; for (let i = 0; i < dbPosts.length; i++) { const comments = await Comment.find({ post: dbPosts[i]._id }); @@ -16,7 +42,6 @@ exports.index = asyncHandler(async (req, res, next) => { date: dbPosts[i].date, text: dbPosts[i].text, author: dbPosts[i].author, - published: dbPosts[i].published, _id: dbPosts[i]._id, comments: comments, }; @@ -40,14 +65,6 @@ exports.post = [ .escape(), asyncHandler(async (req, res, next) => { - // begin by authorizing token - const token = req.cookies.JWT_TOKEN; - if (!token) { - return res.status(403).json({ - message: "Not authorized!", - }); - } - // then return any validation errors const errors = validationResult(req); if (!errors.isEmpty()) { @@ -77,7 +94,14 @@ exports.post = [ // returns post in json format - R exports.get = asyncHandler(async (req, res, next) => { - const post = await Post.findOne({ _id: req.params.postID }).lean().exec(); + const post = await Post.findById(req.params.postID).lean().exec(); + // if post is not published, not publicly visible + if (!post.published) { + hasToken(); + sameAuthor(); + return res.status(200).json({ post }); + } + // otherwise return post if published return res.status(200).json({ post }); }); @@ -97,15 +121,7 @@ exports.put = [ // Process request after sanitization and validation asyncHandler(async (req, res, next) => { - // begin by authorizing token - const token = req.cookies.JWT_TOKEN; - if (!token) { - return res.status(403).json({ - message: "Not authorized!", - }); - } - - // then return any validation errors + // return any validation errors const errors = validationResult(req); if (!errors.isEmpty()) { return res.status(400).json({ @@ -115,13 +131,13 @@ exports.put = [ } // else data is valid, update post object - const dbPost = await Post.findOne({ _id: req.params.postID }).lean().exec(); + const dbPost = await Post.findById(req.params.postID).lean().exec(); const post = new Post({ title: req.body.title, date: new Date(), text: req.body.text, author: dbPost.author, - published: dbPost.published, + published: req.body.published, _id: dbPost._id, }); @@ -136,12 +152,6 @@ exports.put = [ // deletes a post - D exports.delete = asyncHandler(async (req, res, next) => { - const token = req.cookies.JWT_TOKEN; - if (!token) { - return res.status(403).json({ - message: "Not authorized!", - }); - } - await Post.findByIdAndDelete({ _id: req.params.postID }).exec(); + await Post.findByIdAndDelete(req.params.postID).exec(); return res.status(200).json({ message: "Post deleted!" }); }); diff --git a/controllers/user.js b/controllers/user.js index 2781981..72b7139 100644 --- a/controllers/user.js +++ b/controllers/user.js @@ -1,7 +1,6 @@ const asyncHandler = require("express-async-handler"); const bcrypt = require("bcryptjs"); const User = require("../models/user.js"); -const jwt = require("jsonwebtoken"); const { body, validationResult } = require("express-validator"); const { default: mongoose } = require("mongoose"); @@ -60,7 +59,7 @@ exports.get = asyncHandler(async (req, res, next) => { .lean() .exec(); // gets user based on username return res.status(200).json({ - user: user, + user, }); }); @@ -81,11 +80,6 @@ exports.post = [ asyncHandler(async (req, res, next) => { // begin by authorizing token const token = req.cookies.JWT_TOKEN; - if (!token) { - return res.status(403).json({ - message: "Not authorized!", - }); - } // if token is not for this user - compares by creating another token let opts = { expiresIn: "1d", @@ -137,29 +131,6 @@ exports.post = [ // D exports.delete = asyncHandler(async (req, res, next) => { - // begin by authorizing token - const token = req.cookies.JWT_TOKEN; - if (!token) { - return res.status(403).json({ - message: "Not authorized!", - }); - } - // if token is not for this user - compares by creating another token - let opts = { - expiresIn: "1d", - }; - const originalUsername = req.params.username; - const userToken = jwt.sign( - { originalUsername }, - process.env.SECRET_KEY, - opts - ); - if (token != userToken) { - return res.status(403).json({ - message: "Not authorized!", - }); - } - // if everything is correct, delete user await User.findOneAndDelete({ username: originalUsername }).exec(); return res.status(200).json({ message: "Post deleted!" }); diff --git a/middleware/hasToken.js b/middleware/hasToken.js new file mode 100644 index 0000000..bbdbd3b --- /dev/null +++ b/middleware/hasToken.js @@ -0,0 +1,22 @@ +const hasToken = (req, res, next) => { + // get token + const token = req.cookies.JWT_TOKEN; + if (!token) { + // if none, error + return res.status(403).json({ + message: "Not authorized!", + }); + } + try { + jwt.verify(token, process.env.SECRET_KEY); + // move forward + return next(); + } catch { + // if incorrect, error + return res.status(403).json({ + message: "Not authorized!", + }); + } +}; + +module.exports = hasToken; diff --git a/middleware/sameAuthor.js b/middleware/sameAuthor.js new file mode 100644 index 0000000..3a9c422 --- /dev/null +++ b/middleware/sameAuthor.js @@ -0,0 +1,26 @@ +const jwt = require("jsonwebtoken"); +const asyncHandler = require("express-async-handler"); +const { default: mongoose } = require("mongoose"); +const Post = require("../models/post.js"); + +const sameAuthor = asyncHandler(async (req, res, next) => { + // get token + const token = req.cookies.JWT_TOKEN; + // make token with identical user information + let opts = { + expiresIn: "1d", + }; + const post = await Post.findById(req.params.postID).lean().exec(); + const author = post.author; + const userToken = jwt.sign({ author }, process.env.SECRET_KEY, opts); + // compare the two, if the token does not match then the action is unauthorized + if (token != userToken) { + return res.status(403).json({ + message: "Not authorized!", + }); + } + // otherwise all good + return next(); +}); + +module.exports = sameAuthor; diff --git a/middleware/sameUser.js b/middleware/sameUser.js new file mode 100644 index 0000000..07c1105 --- /dev/null +++ b/middleware/sameUser.js @@ -0,0 +1,22 @@ +const jwt = require("jsonwebtoken"); + +const sameUser = (req, res, next) => { + // get token + const token = req.cookies.JWT_TOKEN; + // make token with identical user information + let opts = { + expiresIn: "1d", + }; + const username = req.params.username; + const userToken = jwt.sign({ username }, process.env.SECRET_KEY, opts); + // compare the two, if the token does not match then the action is unauthorized + if (token != userToken) { + return res.status(403).json({ + message: "Not authorized!", + }); + } + // otherwise all good + return next(); +}; + +module.exports = sameUser; diff --git a/routes.js b/routes.js deleted file mode 100644 index 215900d..0000000 --- a/routes.js +++ /dev/null @@ -1,44 +0,0 @@ -const express = require("express"); -const router = express.Router(); -const login_controller = require("./controllers/login.js"); -const post_controller = require("./controllers/post.js"); -const comment_controller = require("./controllers/comment.js"); -const user_controller = require("./controllers/user.js"); - -// list all posts and append comments to each post based on id, return as json -router.get("/", post_controller.index); - -// login page -router.post("/login", login_controller.post); - -// user get -router.get("/:username", user_controller.get); - -// user post -router.post("/new_user", user_controller.post); - -// user put -router.put("/:username", user_controller.put); - -// user delete -router.delete("/:username", user_controller.delete); - -// post post -router.post("/new_post", post_controller.post); - -// post get -router.get("/:postID", post_controller.get); - -// post put -router.put("/:postID", post_controller.put); - -// post delete -router.delete("/:postID", post_controller.delete); - -// comment post -router.post("/:postID/new_comment", comment_controller.post); - -// comment get -router.get("/:postID/:commentID", comment_controller.get); - -module.exports = router; diff --git a/routes/index.js b/routes/index.js new file mode 100644 index 0000000..fa1365a --- /dev/null +++ b/routes/index.js @@ -0,0 +1,18 @@ +const express = require("express"); +const router = express.Router(); +const login_controller = require("../controllers/login.js"); +const post_controller = require("../controllers/post.js"); +const hasToken = require("../middleware/hasToken.js"); + +// list all posts and append comments to each post based on id, return as json +router.get("/", post_controller.index); + +// login page +router.post("/login", login_controller.post); + +// authentication checking page - used by frontend +router.get("/ping", hasToken, (req, res) => { + return res.status(200).json({ message: "Authenticated!" }); +}); + +module.exports = router; diff --git a/routes/post.js b/routes/post.js new file mode 100644 index 0000000..bc28c62 --- /dev/null +++ b/routes/post.js @@ -0,0 +1,26 @@ +const express = require("express"); +const router = express.Router(); +const post_controller = require("../controllers/post.js"); +const comment_controller = require("../controllers/comment.js"); +const hasToken = require("../middleware/hasToken.js"); +const sameAuthor = require("../middleware/sameAuthor.js"); + +// post post +router.post("/new_post", hasToken, post_controller.post); + +// post get +router.get("/:postID", post_controller.get); + +// post put +router.put("/:postID", hasToken, sameAuthor, post_controller.put); + +// post delete +router.delete("/:postID", hasToken, sameAuthor, post_controller.delete); + +// comment post +router.post("/:postID/new_comment", comment_controller.post); + +// comment get +router.get("/:postID/:commentID", comment_controller.get); + +module.exports = router; diff --git a/routes/user.js b/routes/user.js new file mode 100644 index 0000000..ae28e20 --- /dev/null +++ b/routes/user.js @@ -0,0 +1,21 @@ +const express = require("express"); +const router = express.Router(); +const user_controller = require("../controllers/user.js"); +const hasToken = require("../middleware/hasToken.js"); +const sameUser = require("../middleware/sameUser.js"); + +// hardcoded URLs always go before matchers + +// user post +router.post("/new_user", user_controller.post); + +// user get +router.get("/:username", hasToken, user_controller.get); + +// user put +router.put("/:username", hasToken, sameUser, user_controller.put); + +// user delete +router.delete("/:username", hasToken, sameUser, user_controller.delete); + +module.exports = router;