revised to use httpOnly cookie to store jwt
comments can no longer be updated or deleted - out of scope users can now be created, updated and deleted
This commit is contained in:
parent
7ea24df56b
commit
e69fe69d90
12 changed files with 333 additions and 308 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
# express-blog-api
|
# express-blog-api
|
||||||
|
|
||||||
RESTful blog API implemented in Express. Has POST/GET/PUT/DELETE methods for all posts and comments.
|
RESTful blog API implemented in Express. Has POST/GET/PUT/DELETE methods for posts and users. Comments cannot be updated or deleted.
|
||||||
Post Creation, Updates and Deletion require jwt token returned when post completed at /api/login.
|
Post Creation, Updates and Deletion require jwt token returned when login completed at /login. User Updates and Deletion require a matching jwt token.
|
||||||
Hosted on Deta Space at https://expressblogapi-1-v2871156.deta.app/api/
|
Hosted on Deta Space at https://expressblogapi-1-v2871156.deta.app/
|
||||||
|
|
|
||||||
6
app.js
6
app.js
|
|
@ -2,9 +2,10 @@ const express = require("express");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const cors = require("cors");
|
const cors = require("cors");
|
||||||
const mongoose = require("mongoose");
|
const mongoose = require("mongoose");
|
||||||
|
const cookieParser = require("cookie-parser");
|
||||||
require("dotenv").config();
|
require("dotenv").config();
|
||||||
|
|
||||||
const apiRouter = require("./routes/api.js");
|
const router = require("./routes.js");
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
|
|
@ -28,6 +29,7 @@ app.set("views", path.join(__dirname, "views"));
|
||||||
app.set("view engine", "ejs");
|
app.set("view engine", "ejs");
|
||||||
|
|
||||||
// parsing
|
// parsing
|
||||||
|
app.use(cookieParser());
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
app.use(express.urlencoded({ extended: false }));
|
app.use(express.urlencoded({ extended: false }));
|
||||||
app.use(express.static(path.join(__dirname, "public")));
|
app.use(express.static(path.join(__dirname, "public")));
|
||||||
|
|
@ -36,6 +38,6 @@ app.use(express.static(path.join(__dirname, "public")));
|
||||||
app.use(cors());
|
app.use(cors());
|
||||||
|
|
||||||
// routing
|
// routing
|
||||||
app.use("/api", apiRouter);
|
app.use("/", router);
|
||||||
|
|
||||||
module.exports = app;
|
module.exports = app;
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,6 @@ exports.post = [
|
||||||
.trim()
|
.trim()
|
||||||
.escape(),
|
.escape(),
|
||||||
|
|
||||||
// Validate and sanitize password
|
|
||||||
body("password", "Please enter password for future comment modification!")
|
|
||||||
.isLength({ min: 1 })
|
|
||||||
.trim()
|
|
||||||
.escape(),
|
|
||||||
|
|
||||||
// Process request after authentication, validation and sanitization
|
// Process request after authentication, validation and sanitization
|
||||||
asyncHandler(async (req, res, next) => {
|
asyncHandler(async (req, res, next) => {
|
||||||
const errors = validationResult(req);
|
const errors = validationResult(req);
|
||||||
|
|
@ -40,7 +34,6 @@ exports.post = [
|
||||||
author: req.body.author,
|
author: req.body.author,
|
||||||
post: req.params.postID,
|
post: req.params.postID,
|
||||||
_id: new mongoose.Types.ObjectId(),
|
_id: new mongoose.Types.ObjectId(),
|
||||||
password: req.body.password,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// save to DB
|
// save to DB
|
||||||
|
|
@ -60,96 +53,9 @@ exports.get = asyncHandler(async (req, res, next) => {
|
||||||
return res.status(200).json({ comment });
|
return res.status(200).json({ comment });
|
||||||
});
|
});
|
||||||
|
|
||||||
// updates comment - U
|
// no U(pdate)
|
||||||
exports.put = [
|
// comments cannot be edited as they can be made by anyone
|
||||||
// Validate and sanitize text
|
// best to prevent impersonation
|
||||||
body("text", "Please enter comment!").isLength({ min: 1 }).trim().escape(),
|
|
||||||
|
|
||||||
// Validate and sanitize author name
|
// no D(elete)
|
||||||
body("author", "Please enter comment author!")
|
// again, would require authorization that is out of scope for this project
|
||||||
.isLength({ min: 1 })
|
|
||||||
.trim()
|
|
||||||
.escape(),
|
|
||||||
|
|
||||||
// Validate and sanitize password
|
|
||||||
body("password", "Please enter password to modify comment!")
|
|
||||||
.isLength({ min: 1 })
|
|
||||||
.trim()
|
|
||||||
.escape(),
|
|
||||||
|
|
||||||
// Process request after sanitization and validation
|
|
||||||
asyncHandler(async (req, res, next) => {
|
|
||||||
const errors = validationResult(req);
|
|
||||||
|
|
||||||
// if there are validation errors, render with errors
|
|
||||||
if (!errors.isEmpty()) {
|
|
||||||
return res.status(400).json({
|
|
||||||
message: "Comment produced validation errors!",
|
|
||||||
errors: errors.array(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const dbComment = await Comment.findOne({ _id: req.params.commentID })
|
|
||||||
.lean()
|
|
||||||
.exec();
|
|
||||||
|
|
||||||
if (req.body.author === dbComment.author) {
|
|
||||||
if (req.body.password === dbComment.password) {
|
|
||||||
const comment = {
|
|
||||||
date: new Date(),
|
|
||||||
text: req.body.text,
|
|
||||||
author: req.body.author,
|
|
||||||
post: dbComment.post,
|
|
||||||
_id: dbComment._id,
|
|
||||||
password: dbComment.password,
|
|
||||||
};
|
|
||||||
|
|
||||||
await comment.save();
|
|
||||||
return res.status(200).json({
|
|
||||||
message: "Post updated!",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return res.status(401).json({
|
|
||||||
message: "Comments can only be updated with their original password!",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return res.status(401).json({
|
|
||||||
message: "Comments can only be updated by their original author!",
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
|
|
||||||
// deletes a comment - D
|
|
||||||
exports.delete = [
|
|
||||||
// Validate and sanitize password
|
|
||||||
body("password", "Please enter comment deletion password!")
|
|
||||||
.isLength({ min: 1 })
|
|
||||||
.trim()
|
|
||||||
.escape(),
|
|
||||||
|
|
||||||
// Process request after authentication, validation and sanitization
|
|
||||||
asyncHandler(async (req, res, next) => {
|
|
||||||
const errors = validationResult(req);
|
|
||||||
|
|
||||||
// if there are validation errors, render with errors
|
|
||||||
if (!errors.isEmpty()) {
|
|
||||||
return res.status(400).json({
|
|
||||||
message: "Comment password produced validation errors!",
|
|
||||||
errors: errors.array(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const dbComment = await Comment.findOne({ _id: req.params.commentID })
|
|
||||||
.lean()
|
|
||||||
.exec();
|
|
||||||
|
|
||||||
if (req.body.password === dbComment.password) {
|
|
||||||
await Comment.findByIdAndDelete({ _id: req.params.commentID }).exec();
|
|
||||||
return res.status(200).json({ message: "Comment deleted!" });
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(401).json({
|
|
||||||
message: "Incorrect password!",
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
|
|
|
||||||
|
|
@ -6,19 +6,22 @@ const User = require("../models/user.js");
|
||||||
let opts = {};
|
let opts = {};
|
||||||
|
|
||||||
exports.post = asyncHandler(async (req, res, next) => {
|
exports.post = asyncHandler(async (req, res, next) => {
|
||||||
const admin = await User.findOne({}).lean().exec(); // only one user in DB - admin. pull it up, jamie!
|
|
||||||
const { username, password } = req.body; // get fields from body
|
const { username, password } = req.body; // get fields from body
|
||||||
if (username === admin.username) {
|
const user = await User.findOne({ username: username }).lean().exec(); // gets user based on username
|
||||||
console.log();
|
if (user) {
|
||||||
const match = await bcrypt.compare(password, admin.password); // compare bcrypt hashed passwords
|
const match = await bcrypt.compare(password, user.password); // compare bcrypt hashed passwords
|
||||||
if (match) {
|
if (match) {
|
||||||
opts.expiresIn = 120;
|
opts.expiresIn = "1d";
|
||||||
const token = jwt.sign({ username }, process.env.SECRET_KEY, opts); // create token and return below
|
const token = jwt.sign({ username }, process.env.SECRET_KEY, opts); // create token and return below
|
||||||
return res.status(200).json({
|
return res
|
||||||
message: "Authentication complete",
|
.cookie("JWT_TOKEN", token, {
|
||||||
token,
|
httpOnly: true,
|
||||||
});
|
})
|
||||||
|
.status(200)
|
||||||
|
.json({
|
||||||
|
message: "Authentication complete", // a winrar is you
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return res.status(401).json({ message: "Authentication failed" }); // get rekt nerd
|
return res.status(401).json({ message: "Authentication failed" }); // epic fail
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,6 @@ const { body, validationResult } = require("express-validator");
|
||||||
const Post = require("../models/post.js");
|
const Post = require("../models/post.js");
|
||||||
const Comment = require("../models/comment.js");
|
const Comment = require("../models/comment.js");
|
||||||
|
|
||||||
const passport = require("passport");
|
|
||||||
const jwtStrategy = require("../strategy/jwt.js");
|
|
||||||
passport.use(jwtStrategy);
|
|
||||||
|
|
||||||
// returns json object with ALL posts and comments
|
// returns json object with ALL posts and comments
|
||||||
exports.index = asyncHandler(async (req, res, next) => {
|
exports.index = asyncHandler(async (req, res, next) => {
|
||||||
const dbPosts = await Post.find().lean().exec();
|
const dbPosts = await Post.find().lean().exec();
|
||||||
|
|
@ -30,50 +26,54 @@ exports.index = asyncHandler(async (req, res, next) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// makes new post - C
|
// makes new post - C
|
||||||
(exports.post = passport.authenticate("jwt", { session: false })),
|
exports.post = [
|
||||||
[
|
// Validate and sanitize title
|
||||||
// Validate and sanitize title
|
body("title", "Please enter blog post title!")
|
||||||
body("title", "Please enter blog post title!")
|
.isLength({ min: 1 })
|
||||||
.isLength({ min: 1 })
|
.trim()
|
||||||
.trim()
|
.escape(),
|
||||||
.escape(),
|
|
||||||
|
|
||||||
// Validate and sanitize text
|
// Validate and sanitize text
|
||||||
body("text", "Please enter blog post text!")
|
body("text", "Please enter blog post text!")
|
||||||
.isLength({ min: 1 })
|
.isLength({ min: 1 })
|
||||||
.trim()
|
.trim()
|
||||||
.escape(),
|
.escape(),
|
||||||
|
|
||||||
// Process request after authentication, validation and sanitization
|
asyncHandler(async (req, res, next) => {
|
||||||
asyncHandler(async (req, res, next) => {
|
// begin by authorizing token
|
||||||
const errors = validationResult(req);
|
const token = req.cookies.JWT_TOKEN;
|
||||||
|
if (!token) {
|
||||||
// if there are validation errors, render with errors
|
return res.status(403).json({
|
||||||
if (!errors.isEmpty()) {
|
message: "Not authorized!",
|
||||||
res.render("/admin/create"),
|
|
||||||
{
|
|
||||||
errors: errors.array(),
|
|
||||||
};
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// else data is valid, make post object
|
|
||||||
const post = new Post({
|
|
||||||
title: req.body.title,
|
|
||||||
date: new Date(),
|
|
||||||
text: req.body.text,
|
|
||||||
author: req.user.username,
|
|
||||||
_id: new mongoose.Types.ObjectId(),
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// save to DB
|
// then return any validation errors
|
||||||
await post.save();
|
const errors = validationResult(req);
|
||||||
|
if (!errors.isEmpty()) {
|
||||||
return res.status(200).json({
|
return res.status(400).json({
|
||||||
message: "Post created!",
|
message: "Comment produced validation errors!",
|
||||||
|
errors: errors.array(),
|
||||||
});
|
});
|
||||||
}),
|
}
|
||||||
];
|
|
||||||
|
// else data is valid, make post object
|
||||||
|
const post = new Post({
|
||||||
|
title: req.body.title,
|
||||||
|
date: new Date(),
|
||||||
|
text: req.body.text,
|
||||||
|
author: req.user.username,
|
||||||
|
_id: new mongoose.Types.ObjectId(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// save to DB
|
||||||
|
await post.save();
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
message: "Post created!",
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
// returns post in json format - R
|
// returns post in json format - R
|
||||||
exports.get = asyncHandler(async (req, res, next) => {
|
exports.get = asyncHandler(async (req, res, next) => {
|
||||||
|
|
@ -82,43 +82,66 @@ exports.get = asyncHandler(async (req, res, next) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// updates post - U
|
// updates post - U
|
||||||
(exports.put = passport.authenticate("jwt", { session: false })),
|
exports.put = [
|
||||||
[
|
// Validate and sanitize title
|
||||||
// Validate and sanitize title
|
body("title", "Please enter blog post title!")
|
||||||
body("title", "Please enter blog post title!")
|
.isLength({ min: 1 })
|
||||||
.isLength({ min: 1 })
|
.trim()
|
||||||
.trim()
|
.escape(),
|
||||||
.escape(),
|
|
||||||
|
|
||||||
// Validate and sanitize text
|
// Validate and sanitize text
|
||||||
body("text", "Please enter blog post text!")
|
body("text", "Please enter blog post text!")
|
||||||
.isLength({ min: 1 })
|
.isLength({ min: 1 })
|
||||||
.trim()
|
.trim()
|
||||||
.escape(),
|
.escape(),
|
||||||
|
|
||||||
// Process request after sanitization and validation
|
// Process request after sanitization and validation
|
||||||
asyncHandler(async (req, res, next) => {
|
asyncHandler(async (req, res, next) => {
|
||||||
const dbPost = await Post.findOne({ _id: req.params.postID })
|
// begin by authorizing token
|
||||||
.lean()
|
const token = req.cookies.JWT_TOKEN;
|
||||||
.exec();
|
if (!token) {
|
||||||
const post = {
|
return res.status(403).json({
|
||||||
title: req.body.title,
|
message: "Not authorized!",
|
||||||
date: new Date(),
|
|
||||||
text: req.body.text,
|
|
||||||
author: dbPost.author,
|
|
||||||
published: dbPost.published,
|
|
||||||
_id: dbPost._id,
|
|
||||||
};
|
|
||||||
await post.save();
|
|
||||||
return res.status(200).json({
|
|
||||||
message: "Post updated!",
|
|
||||||
});
|
});
|
||||||
}),
|
}
|
||||||
];
|
|
||||||
|
// then return any validation errors
|
||||||
|
const errors = validationResult(req);
|
||||||
|
if (!errors.isEmpty()) {
|
||||||
|
return res.status(400).json({
|
||||||
|
message: "Comment produced validation errors!",
|
||||||
|
errors: errors.array(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// else data is valid, update post object
|
||||||
|
const dbPost = await Post.findOne({ _id: 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,
|
||||||
|
_id: dbPost._id,
|
||||||
|
});
|
||||||
|
|
||||||
|
// save to DB
|
||||||
|
await Post.findByIdAndUpdate(dbPost._id, post, {});
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
message: "Post updated!",
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
// deletes a post - D
|
// deletes a post - D
|
||||||
(exports.delete = passport.authenticate("jwt", { session: false })),
|
exports.delete = asyncHandler(async (req, res, next) => {
|
||||||
asyncHandler(async (req, res, next) => {
|
const token = req.cookies.JWT_TOKEN;
|
||||||
await Post.findByIdAndDelete({ _id: req.params.postID }).exec();
|
if (!token) {
|
||||||
return res.status(200).json({ message: "Post deleted!" });
|
return res.status(403).json({
|
||||||
});
|
message: "Not authorized!",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await Post.findByIdAndDelete({ _id: req.params.postID }).exec();
|
||||||
|
return res.status(200).json({ message: "Post deleted!" });
|
||||||
|
});
|
||||||
|
|
|
||||||
164
controllers/user.js
Normal file
164
controllers/user.js
Normal file
|
|
@ -0,0 +1,164 @@
|
||||||
|
const asyncHandler = require("express-async-handler");
|
||||||
|
const bcrypt = require("bcryptjs");
|
||||||
|
const User = require("../models/user.js");
|
||||||
|
const jwt = require("jsonwebtoken");
|
||||||
|
|
||||||
|
// C
|
||||||
|
exports.put = [
|
||||||
|
// Validate and sanitize username
|
||||||
|
body("username", "Please enter username!")
|
||||||
|
.isLength({ min: 1 })
|
||||||
|
.trim()
|
||||||
|
.escape(),
|
||||||
|
|
||||||
|
// Validate and sanitize password
|
||||||
|
body("password", "Please enter password!")
|
||||||
|
.isLength({ min: 1 })
|
||||||
|
.trim()
|
||||||
|
.escape(),
|
||||||
|
|
||||||
|
asyncHandler(async (req, res, next) => {
|
||||||
|
const errors = validationResult(req);
|
||||||
|
// if there are validation errors, return them
|
||||||
|
if (!errors.isEmpty()) {
|
||||||
|
return res.status(400).json({
|
||||||
|
message: "Comment produced validation errors!",
|
||||||
|
errors: errors.array(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const { username, password } = req.body; // get fields from body
|
||||||
|
const dbUser = await User.findOne({ username: req.params.username })
|
||||||
|
.lean()
|
||||||
|
.exec();
|
||||||
|
const exists = await User.findOne({ username: username }).lean().exec();
|
||||||
|
|
||||||
|
if (exists) {
|
||||||
|
return res.status(409).json({
|
||||||
|
message: "Username is taken!",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// else
|
||||||
|
const user = new User({
|
||||||
|
username: username,
|
||||||
|
password: await bcrypt.hash(password, 10),
|
||||||
|
_id: dbUser._id,
|
||||||
|
});
|
||||||
|
|
||||||
|
await User.findByIdAndUpdate(dbUser._id, user, {});
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
message: "User updated!",
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
// R
|
||||||
|
exports.get = asyncHandler(async (req, res, next) => {
|
||||||
|
const user = await User.findOne({ username: req.params.username })
|
||||||
|
.lean()
|
||||||
|
.exec(); // gets user based on username
|
||||||
|
return res.status(200).json({
|
||||||
|
user: user,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// U
|
||||||
|
exports.post = [
|
||||||
|
// Validate and sanitize username
|
||||||
|
body("username", "Please enter username!")
|
||||||
|
.isLength({ min: 1 })
|
||||||
|
.trim()
|
||||||
|
.escape(),
|
||||||
|
|
||||||
|
// Validate and sanitize password
|
||||||
|
body("password", "Please enter password!")
|
||||||
|
.isLength({ min: 1 })
|
||||||
|
.trim()
|
||||||
|
.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!",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 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!",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// then return any validation errors
|
||||||
|
const errors = validationResult(req);
|
||||||
|
// if there are validation errors, return them
|
||||||
|
if (!errors.isEmpty()) {
|
||||||
|
return res.status(400).json({
|
||||||
|
message: "Comment produced validation errors!",
|
||||||
|
errors: errors.array(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for duplicates
|
||||||
|
const { username, password } = req.body; // get fields from body
|
||||||
|
const exists = await User.findOne({ username: originalUsername })
|
||||||
|
.lean()
|
||||||
|
.exec();
|
||||||
|
if (exists) {
|
||||||
|
return res.status(409).json({
|
||||||
|
message: "Username is taken!",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise update user
|
||||||
|
const user = new User({
|
||||||
|
username: username,
|
||||||
|
password: await bcrypt.hash(password, 10),
|
||||||
|
});
|
||||||
|
await user.save(); // make and save user
|
||||||
|
return res.status(200).json({
|
||||||
|
message: "User created!",
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
// 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!" });
|
||||||
|
});
|
||||||
|
|
@ -7,13 +7,12 @@ const CommentSchema = new Schema({
|
||||||
author: { type: String, required: true },
|
author: { type: String, required: true },
|
||||||
post: { type: mongoose.ObjectId, required: true },
|
post: { type: mongoose.ObjectId, required: true },
|
||||||
_id: { type: mongoose.ObjectId, required: true },
|
_id: { type: mongoose.ObjectId, required: true },
|
||||||
password: { type: String, required: true },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Virtual for comment RESTful functions
|
// Virtual for comment RESTful functions
|
||||||
CommentSchema.virtual("url").get(function () {
|
CommentSchema.virtual("url").get(function () {
|
||||||
// We don't use an arrow function as we'll need the this object
|
// We don't use an arrow function as we'll need the this object
|
||||||
return `/api/${post}/${this._id}`;
|
return `/${post}/${this._id}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Export model
|
// Export model
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ const PostSchema = new Schema({
|
||||||
// Virtual for message URL
|
// Virtual for message URL
|
||||||
PostSchema.virtual("url").get(function () {
|
PostSchema.virtual("url").get(function () {
|
||||||
// We don't use an arrow function as we'll need the this object
|
// We don't use an arrow function as we'll need the this object
|
||||||
return `/api/${this._id}`;
|
return `/${this._id}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Export model
|
// Export model
|
||||||
|
|
|
||||||
100
package-lock.json
generated
100
package-lock.json
generated
|
|
@ -10,6 +10,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"bootstrap": "^5.3.1",
|
"bootstrap": "^5.3.1",
|
||||||
|
"cookie-parser": "^1.4.6",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"debug": "~2.6.9",
|
"debug": "~2.6.9",
|
||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.3.1",
|
||||||
|
|
@ -20,11 +21,7 @@
|
||||||
"express-session": "^1.17.3",
|
"express-session": "^1.17.3",
|
||||||
"express-validator": "^7.0.1",
|
"express-validator": "^7.0.1",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"mongoose": "^7.5.1",
|
"mongoose": "^7.5.1"
|
||||||
"passport": "^0.6.0",
|
|
||||||
"passport-jwt": "^4.0.1",
|
|
||||||
"passport-local": "^1.0.0",
|
|
||||||
"passport-local-mongoose": "^8.0.0"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@mongodb-js/saslprep": {
|
"node_modules/@mongodb-js/saslprep": {
|
||||||
|
|
@ -394,6 +391,26 @@
|
||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/cookie": {
|
||||||
|
"version": "0.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
|
||||||
|
"integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cookie-parser": {
|
||||||
|
"version": "1.4.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz",
|
||||||
|
"integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==",
|
||||||
|
"dependencies": {
|
||||||
|
"cookie": "0.4.1",
|
||||||
|
"cookie-signature": "1.0.6"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cookie-signature": {
|
"node_modules/cookie-signature": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||||
|
|
@ -829,11 +846,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||||
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
|
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
|
||||||
},
|
},
|
||||||
"node_modules/generaterr": {
|
|
||||||
"version": "1.5.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/generaterr/-/generaterr-1.5.0.tgz",
|
|
||||||
"integrity": "sha512-JgcGRv2yUKeboLvvNrq9Bm90P4iJBu7/vd5wSLYqMG5GJ6SxZT46LAAkMfNhQ+EK3jzC+cRBm7P8aUWYyphgcQ=="
|
|
||||||
},
|
|
||||||
"node_modules/get-caller-file": {
|
"node_modules/get-caller-file": {
|
||||||
"version": "2.0.5",
|
"version": "2.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||||
|
|
@ -1373,64 +1385,6 @@
|
||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/passport": {
|
|
||||||
"version": "0.6.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/passport/-/passport-0.6.0.tgz",
|
|
||||||
"integrity": "sha512-0fe+p3ZnrWRW74fe8+SvCyf4a3Pb2/h7gFkQ8yTJpAO50gDzlfjZUZTO1k5Eg9kUct22OxHLqDZoKUWRHOh9ug==",
|
|
||||||
"dependencies": {
|
|
||||||
"passport-strategy": "1.x.x",
|
|
||||||
"pause": "0.0.1",
|
|
||||||
"utils-merge": "^1.0.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.4.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/jaredhanson"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/passport-jwt": {
|
|
||||||
"version": "4.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-4.0.1.tgz",
|
|
||||||
"integrity": "sha512-UCKMDYhNuGOBE9/9Ycuoyh7vP6jpeTp/+sfMJl7nLff/t6dps+iaeE0hhNkKN8/HZHcJ7lCdOyDxHdDoxoSvdQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"jsonwebtoken": "^9.0.0",
|
|
||||||
"passport-strategy": "^1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/passport-local": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-9wCE6qKznvf9mQYYbgJ3sVOHmCWoUNMVFoZzNoznmISbhnNNPhN9xfY3sLmScHMetEJeoY7CXwfhCe7argfQow==",
|
|
||||||
"dependencies": {
|
|
||||||
"passport-strategy": "1.x.x"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/passport-local-mongoose": {
|
|
||||||
"version": "8.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/passport-local-mongoose/-/passport-local-mongoose-8.0.0.tgz",
|
|
||||||
"integrity": "sha512-jgfN/B0j11WT5f96QlL5EBvxbIwmzd+tbwPzG1Vk8hzDOF68jrch5M+NFvrHjWjb3lfAU0DkxKmNRT9BjFZysQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"generaterr": "^1.5.0",
|
|
||||||
"passport-local": "^1.0.0",
|
|
||||||
"scmp": "^2.1.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 8.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/passport-strategy": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/path-to-regexp": {
|
"node_modules/path-to-regexp": {
|
||||||
"version": "0.1.7",
|
"version": "0.1.7",
|
||||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
||||||
|
|
@ -1444,11 +1398,6 @@
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/pause": {
|
|
||||||
"version": "0.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz",
|
|
||||||
"integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg=="
|
|
||||||
},
|
|
||||||
"node_modules/picomatch": {
|
"node_modules/picomatch": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||||
|
|
@ -1652,11 +1601,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||||
},
|
},
|
||||||
"node_modules/scmp": {
|
|
||||||
"version": "2.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/scmp/-/scmp-2.1.0.tgz",
|
|
||||||
"integrity": "sha512-o/mRQGk9Rcer/jEEw/yw4mwo3EU/NvYvp577/Btqrym9Qy5/MdWGBqipbALgd2lrdWTJ5/gqDusxfnQBxOxT2Q=="
|
|
||||||
},
|
|
||||||
"node_modules/semver": {
|
"node_modules/semver": {
|
||||||
"version": "7.5.4",
|
"version": "7.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"bootstrap": "^5.3.1",
|
"bootstrap": "^5.3.1",
|
||||||
|
"cookie-parser": "^1.4.6",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"debug": "~2.6.9",
|
"debug": "~2.6.9",
|
||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.3.1",
|
||||||
|
|
@ -18,10 +19,6 @@
|
||||||
"express-session": "^1.17.3",
|
"express-session": "^1.17.3",
|
||||||
"express-validator": "^7.0.1",
|
"express-validator": "^7.0.1",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"mongoose": "^7.5.1",
|
"mongoose": "^7.5.1"
|
||||||
"passport": "^0.6.0",
|
|
||||||
"passport-jwt": "^4.0.1",
|
|
||||||
"passport-local": "^1.0.0",
|
|
||||||
"passport-local-mongoose": "^8.0.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,28 @@
|
||||||
const express = require("express");
|
const express = require("express");
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const login_controller = require("../controllers/login.js");
|
const login_controller = require("./controllers/login.js");
|
||||||
const post_controller = require("../controllers/post.js");
|
const post_controller = require("./controllers/post.js");
|
||||||
const comment_controller = require("../controllers/comment.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
|
// list all posts and append comments to each post based on id, return as json
|
||||||
router.get("/", post_controller.index);
|
router.get("/", post_controller.index);
|
||||||
|
|
||||||
// login page - should work with json
|
// login page
|
||||||
router.post("/login", login_controller.post);
|
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
|
// post post
|
||||||
router.post("/new_post", post_controller.post);
|
router.post("/new_post", post_controller.post);
|
||||||
|
|
||||||
|
|
@ -28,10 +41,4 @@ router.post("/:postID/new_comment", comment_controller.post);
|
||||||
// comment get
|
// comment get
|
||||||
router.get("/:postID/:commentID", comment_controller.get);
|
router.get("/:postID/:commentID", comment_controller.get);
|
||||||
|
|
||||||
// comment put
|
|
||||||
router.put("/:postID/:commentID", comment_controller.put);
|
|
||||||
|
|
||||||
// comment delete
|
|
||||||
router.delete("/:postID/:commentID", comment_controller.delete);
|
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
const User = require("../models/user.js");
|
|
||||||
const JwtStrategy = require("passport-jwt").Strategy;
|
|
||||||
const ExtractJwt = require("passport-jwt").ExtractJwt;
|
|
||||||
|
|
||||||
const opts = {};
|
|
||||||
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
|
|
||||||
opts.secretOrKey = process.env.SECRET_KEY;
|
|
||||||
|
|
||||||
const getAdmin = async () => {
|
|
||||||
const admin = await User.findOne({}).lean().exec(); // only one user in DB - admin. pull it up, jamie!
|
|
||||||
return admin;
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = new JwtStrategy(opts, (jwt_payload, done) => {
|
|
||||||
const admin = getAdmin();
|
|
||||||
if (jwt_payload.username === admin.username) {
|
|
||||||
return done(null, true);
|
|
||||||
}
|
|
||||||
return done(null, false);
|
|
||||||
});
|
|
||||||
Loading…
Add table
Reference in a new issue