updated authentication scheme

added middleware
This commit is contained in:
ak 2023-09-27 22:26:37 -07:00
parent 7b7bd3f4f0
commit 23314af0d0
10 changed files with 182 additions and 106 deletions

8
app.js
View file

@ -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;

View file

@ -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!" });
});

View file

@ -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!" });

22
middleware/hasToken.js Normal file
View file

@ -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;

26
middleware/sameAuthor.js Normal file
View file

@ -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;

22
middleware/sameUser.js Normal file
View file

@ -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;

View file

@ -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;

18
routes/index.js Normal file
View file

@ -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;

26
routes/post.js Normal file
View file

@ -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;

21
routes/user.js Normal file
View file

@ -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;