Added security policies to all endpoints and implemented file upload feature on posts

This commit is contained in:
Sven laptop
2026-07-24 23:25:31 +02:00
parent 9519a01ca0
commit 9e56f00c1e
19 changed files with 718 additions and 134 deletions
+51
View File
@@ -0,0 +1,51 @@
const express = require("express");
const router = express.Router();
const pool = require("../../db");
const { strictInput } = require("../../middleware/strict_input");
const { authenticateToken } = require("../../middleware/authenticate_token");
const {
handlePostUpload,
imageLinkForFile,
removeUploadedFile,
} = require("../../middleware/post_upload");
async function create_post(req, res) {
try {
const { title, text } = req.body;
const author_id = req.user.id;
if (!title || !text) {
await removeUploadedFile(req.file);
return res.status(400).send("Title or text was not provided");
} else {
const imageLink = imageLinkForFile(req.file);
try {
const { rows } = await pool.query(
"INSERT INTO posts (title, text, author_id, image_link) VALUES ($1, $2, $3, $4) RETURNING id, title, text, author_id, image_link",
[title, text, author_id, imageLink],
);
res.status(201).json(rows[0]);
} catch (err) {
await removeUploadedFile(req.file);
throw err;
}
}
} catch (err) {
console.error("database query failed", {
message: err.message,
code: err.code,
detail: err.detail,
hint: err.hint,
stack: err.stack,
});
res.status(500).send("Data was not pushed, request failed");
}
}
router.post(
"/",
authenticateToken,
handlePostUpload,
strictInput({ body: ["title", "text"], cleanupUploadedFile: true }),
create_post,
);
module.exports = router;
+40
View File
@@ -0,0 +1,40 @@
const express = require("express");
const router = express.Router();
const pool = require("../../db");
const { strictInput } = require("../../middleware/strict_input");
const { authenticateToken } = require("../../middleware/authenticate_token");
const { removeImageLink } = require("../../middleware/post_upload");
async function delete_post(req, res) {
try {
const id = req.query.id;
const result = await pool.query(
"DELETE FROM posts WHERE id = $1 AND author_id = $2 RETURNING id, image_link",
[id, req.user.id],
);
if (result.rowCount === 0) {
return res.status(404).send("Post not found or does not belong to you");
}
await removeImageLink(result.rows[0].image_link);
res.status(200).json({
message: "Post Deleted",
});
} catch (err) {
console.error("database query failed", {
message: err.message,
code: err.code,
detail: err.detail,
hint: err.hint,
stack: err.stack,
});
res.status(500).send("Data has not been deleted, request failed");
}
}
router.delete(
"/",
authenticateToken,
strictInput({ query: ["id"] }),
delete_post,
);
module.exports = router;
+100
View File
@@ -0,0 +1,100 @@
const express = require("express");
const router = express.Router();
const pool = require("../../db");
const { strictInput } = require("../../middleware/strict_input");
const { authenticateToken } = require("../../middleware/authenticate_token");
const {
handlePostUpload,
imageLinkForFile,
removeImageLink,
removeUploadedFile,
} = require("../../middleware/post_upload");
async function editPost(req, res) {
const id = req.query.id;
const { title, text, remove_image: removeImageValue } = req.body;
const removeImage = removeImageValue === "true";
if (removeImageValue !== undefined && !["true", "false"].includes(removeImageValue)) {
await removeUploadedFile(req.file);
return res.status(400).send("remove_image must be true or false");
}
if (req.file && removeImage) {
await removeUploadedFile(req.file);
return res.status(400).send("Choose either an image or remove_image=true");
}
if (title === "" || text === "") {
await removeUploadedFile(req.file);
return res.status(400).send("title and text cannot be empty");
}
if (title === undefined && text === undefined && !req.file && !removeImage) {
return res.status(400).send("At least one post field must be changed");
}
try {
const existing = await pool.query(
"SELECT id, image_link FROM posts WHERE id = $1 AND author_id = $2",
[id, req.user.id],
);
if (existing.rowCount === 0) {
await removeUploadedFile(req.file);
return res.status(404).send("Post not found or does not belong to you");
}
const oldImageLink = existing.rows[0].image_link;
const fields = [];
const values = [];
const addField = (field, value) => {
fields.push(`${field} = $${values.length + 1}`);
values.push(value);
};
if (title !== undefined) addField("title", title);
if (text !== undefined) addField("text", text);
if (req.file) addField("image_link", imageLinkForFile(req.file));
if (removeImage) addField("image_link", null);
values.push(id, req.user.id);
let updated;
try {
updated = await pool.query(
`UPDATE posts SET ${fields.join(", ")} WHERE id = $${values.length - 1} AND author_id = $${values.length} RETURNING id, title, text, author_id, image_link`,
values,
);
} catch (err) {
await removeUploadedFile(req.file);
throw err;
}
if (req.file || removeImage) {
await removeImageLink(oldImageLink).catch((err) => {
console.error("old post image could not be removed", { message: err.message });
});
}
return res.status(200).json(updated.rows[0]);
} catch (err) {
await removeUploadedFile(req.file);
console.error("database query failed", {
message: err.message,
code: err.code,
detail: err.detail,
hint: err.hint,
stack: err.stack,
});
return res.status(500).send("Post was not updated, request failed");
}
}
router.put(
"/",
authenticateToken,
handlePostUpload,
strictInput({
query: ["id"],
body: ["title", "text", "remove_image"],
cleanupUploadedFile: true,
}),
editPost,
);
module.exports = router;
+56
View File
@@ -0,0 +1,56 @@
const express = require("express");
const router = express.Router();
const pool = require("../../db");
const { strictInput } = require("../../middleware/strict_input");
const { authenticateToken } = require("../../middleware/authenticate_token");
const { uploadDirectory } = require("../../middleware/post_upload");
const path = require("path");
async function getPosts(req, res, ownOnly) {
try {
const query = ownOnly
? "SELECT id, title, text, author_id, image_link FROM posts WHERE author_id = $1"
: "SELECT id, title, text, author_id, image_link FROM posts";
const values = ownOnly ? [req.user.id] : [];
const { rows } = await pool.query(query, values);
res.status(200).json(rows);
} catch (err) {
console.error("database query failed", {
message: err.message,
code: err.code,
detail: err.detail,
hint: err.hint,
stack: err.stack,
});
res.status(500).send("Posts could not be retrieved, request failed");
}
}
router.get(
"/image/:filename",
authenticateToken,
strictInput(),
(req, res) => {
const filename = path.basename(req.params.filename);
if (filename !== req.params.filename) return res.status(404).end();
res.sendFile(filename, { root: uploadDirectory }, (err) => {
if (err && !res.headersSent) res.status(err.statusCode === 404 ? 404 : 500).end();
});
},
);
router.get(
"/me",
authenticateToken,
strictInput(),
(req, res) => getPosts(req, res, true),
);
router.get(
"/",
authenticateToken,
strictInput(),
(req, res) => getPosts(req, res, false),
);
module.exports = router;