Fix attachments

This commit is contained in:
Rory& committed 2026-04-18 18:26:34 +02:00
1 parent 5b501fa8da
commit 103922e841
16 files changed
+141 -117

No files matched your search

+1 -22
View File
@@ -17,12 +17,11 @@
*/
import { Server, ServerOptions } from "lambert-server";
import { Attachment, Config, initDatabase, registerRoutes } from "@spacebar/util";
import { Config, initDatabase, registerRoutes } from "@spacebar/util";
import { CORS, BodyParser } from "@spacebar/api";
import path from "path";
import guildProfilesRoute from "./routes/guild-profiles";
import morgan from "morgan";
import { Like } from "typeorm";
export type CDNServerOptions = ServerOptions;
@@ -36,7 +35,6 @@ export class CDNServer extends Server {
async start() {
await initDatabase();
await Config.init();
await this.cleanupSignaturesInDb();
const logRequests = process.env["LOG_REQUESTS"] != undefined;
if (logRequests) {
@@ -73,23 +71,4 @@ export class CDNServer extends Server {
async stop() {
return super.stop();
}
async cleanupSignaturesInDb() {
console.log("[CDN] Cleaning up signatures in database");
const attachmentsToFix = await Attachment.find({
where: { url: Like("%?ex=%") },
});
if (attachmentsToFix.length === 0) {
console.log("[CDN] No attachments to fix");
return;
}
console.log("[CDN] Found", attachmentsToFix.length, " attachments to fix");
for (const attachment of attachmentsToFix) {
attachment.url = attachment.url.split("?ex=")[0];
attachment.proxy_url = attachment.proxy_url?.split("?ex=")[0];
await attachment.save();
console.log(`[CDN] Fixed attachment ${attachment.id}`);
}
}
}
+30 -18
View File
@@ -16,7 +16,7 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Config, hasValidSignature, NewUrlUserSignatureData, Snowflake, UrlSignResult } from "@spacebar/util";
import { Attachment, Config, hasValidSignature, NewUrlUserSignatureData, Snowflake, UrlSignResult } from "@spacebar/util";
import { Request, Response, Router } from "express";
import imageSize from "image-size";
import { HTTPError } from "lambert-server";
@@ -30,16 +30,16 @@ const router = Router({ mergeParams: true });
const SANITIZED_CONTENT_TYPE = ["text/html", "text/mhtml", "multipart/related", "application/xhtml+xml"];
router.post("/:channel_id", multer.single("file"), async (req: Request, res: Response) => {
if (req.headers.signature !== Config.get().security.requestSignature) throw new HTTPError("Invalid request signature");
router.post("/:channel_id/:message_id", multer.single("file"), async (req: Request, res: Response) => {
if (req.headers.signature !== Config.get().security.requestSignature)
throw new HTTPError(`Invalid request signature, expected '${Config.get().security.requestSignature}', got ${req.headers.signature}`);
if (!req.file) throw new HTTPError("file missing");
const { buffer, mimetype, size, originalname } = req.file;
const { channel_id } = req.params as { [key: string]: string };
const { channel_id, message_id } = req.params as { [key: string]: string };
const filename = originalname.replaceAll(" ", "_").replace(/[^a-zA-Z0-9._]+/g, "");
const id = Snowflake.generate();
const path = `attachments/${channel_id}/${id}/${filename}`;
const path = `attachments/${channel_id}/${message_id}/${filename}`;
const endpoint = Config.get()?.cdn.endpointPublic;
@@ -57,7 +57,9 @@ router.post("/:channel_id", multer.single("file"), async (req: Request, res: Res
const finalUrl = `${endpoint}/${path}`;
const file = {
id,
id: Snowflake.generate(),
channel_id,
message_id,
content_type: mimetype,
filename: filename,
size,
@@ -70,11 +72,11 @@ router.post("/:channel_id", multer.single("file"), async (req: Request, res: Res
return res.json(file);
});
router.get("/:channel_id/:id/:filename", cache, async (req: Request, res: Response) => {
const { channel_id, id, filename } = req.params as { [key: string]: string };
router.get("/:channel_id/:message_id/:filename", cache, async (req: Request, res: Response) => {
const { channel_id, message_id, filename } = req.params as { [key: string]: string };
// const { format } = req.query;
const path = `attachments/${channel_id}/${id}/${filename}`;
const path = `attachments/${channel_id}/${message_id}/${filename}`;
const fullUrl = (req.headers["x-forwarded-proto"] ?? req.protocol) + "://" + (req.headers["x-forwarded-host"] ?? req.hostname) + req.originalUrl;
@@ -91,14 +93,24 @@ router.get("/:channel_id/:id/:filename", cache, async (req: Request, res: Respon
}),
UrlSignResult.fromUrl(fullUrl),
);
console.warn("[CDN/Attachments] Client sent invalid attachment URL signature");
if (!hasValidAuth) console.warn("[CDN/Attachments] Client sent invalid attachment URL signature");
}
if (!hasValidAuth) {
return res.status(404).send("This content is no longer available.");
}
if (!hasValidAuth) return res.status(404).send("This content is no longer available.");
const file = await storage.get(path);
let file = await storage.get(path);
// handle re-keying paths to be correct
if (!file) {
const att = await Attachment.findOne({ where: { id: message_id, channel_id: channel_id } });
if (att) {
const oldPath = `attachments/${channel_id}/${att.id}/${filename}`;
if (await storage.exists(oldPath)) {
console.log(`[CDN/Attachments] Moving ${oldPath} -> ${path}!`);
await storage.move(oldPath, path);
file = await storage.get(path);
}
}
}
if (!file) throw new HTTPError("File not found");
const type = await fileTypeFromBuffer(file);
let content_type = type?.mime || "application/octet-stream";
@@ -112,11 +124,11 @@ router.get("/:channel_id/:id/:filename", cache, async (req: Request, res: Respon
return res.send(file);
});
router.delete("/:channel_id/:id/:filename", async (req: Request, res: Response) => {
router.delete("/:channel_id/:message_id/:filename", async (req: Request, res: Response) => {
if (req.headers.signature !== Config.get().security.requestSignature) throw new HTTPError("Invalid request signature");
const { channel_id, id, filename } = req.params as { [key: string]: string };
const path = `attachments/${channel_id}/${id}/${filename}`;
const { channel_id, message_id, filename } = req.params as { [key: string]: string };
const path = `attachments/${channel_id}/${message_id}/${filename}`;
await storage.delete(path);