Sign media per user

This commit is contained in:
Emma [it/its]@Rory& 2025-07-03 09:25:04 +02:00 committed by Rory&
parent 8922ca1518
commit 387cef269a
6 changed files with 57 additions and 11 deletions

View File

@ -30,12 +30,13 @@ import {
emitEvent, emitEvent,
getPermission, getPermission,
getRights, getRights,
uploadFile, uploadFile, Config, getUrlSignature,
} from "@spacebar/util"; } from "@spacebar/util";
import { Request, Response, Router } from "express"; import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server"; import { HTTPError } from "lambert-server";
import multer from "multer"; import multer from "multer";
import { handleMessage, postHandleMessage, route } from "../../../../../util"; import { handleMessage, postHandleMessage, route } from "../../../../../util";
import { URL } from "url";
const router = Router(); const router = Router();
// TODO: message content/embed string length limit // TODO: message content/embed string length limit
@ -244,7 +245,7 @@ router.put(
console.error("[Message] post-message handler failed", e), console.error("[Message] post-message handler failed", e),
); );
return res.json(message); return res.json(message.withSignedAttachments(req));
}, },
); );

View File

@ -17,14 +17,20 @@
*/ */
import { Server, ServerOptions } from "lambert-server"; import { Server, ServerOptions } from "lambert-server";
import { Config, initDatabase, registerRoutes, Sentry } from "@spacebar/util"; import {
Attachment,
Config,
initDatabase,
registerRoutes,
Sentry,
} from "@spacebar/util";
import { CORS, BodyParser } from "@spacebar/api";
import path from "path"; import path from "path";
import avatarsRoute from "./routes/avatars"; import avatarsRoute from "./routes/avatars";
import guildProfilesRoute from "./routes/guild-profiles"; import guildProfilesRoute from "./routes/guild-profiles";
import iconsRoute from "./routes/role-icons"; import iconsRoute from "./routes/role-icons";
import { CORS } from "../api/middlewares/CORS";
import { BodyParser } from "../api/middlewares/BodyParser";
import morgan from "morgan"; import morgan from "morgan";
import { Like, Or } from "typeorm";
export type CDNServerOptions = ServerOptions; export type CDNServerOptions = ServerOptions;
@ -38,6 +44,7 @@ export class CDNServer extends Server {
async start() { async start() {
await initDatabase(); await initDatabase();
await Config.init(); await Config.init();
await this.cleanupSignaturesInDb();
await Sentry.init(this.app); await Sentry.init(this.app);
const logRequests = process.env["LOG_REQUESTS"] != undefined; const logRequests = process.env["LOG_REQUESTS"] != undefined;
@ -124,4 +131,26 @@ export class CDNServer extends Server {
async stop() { async stop() {
return super.stop(); return super.stop();
} }
async cleanupSignaturesInDb() {
this.log("verbose", "[Server] Cleaning up signatures in database");
const attachmentsToFix = await Attachment.find({
where: { url: Like("%?ex=%") },
});
if (attachmentsToFix.length === 0) {
this.log("verbose", "[Server] No attachments to fix");
return;
}
this.log(
"verbose",
`[Server] 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();
this.log("verbose", `[Server] Fixed attachment ${attachment.id}`);
}
}
} }

View File

@ -69,12 +69,7 @@ router.post(
} }
} }
let finalUrl = `${endpoint}/${path}`; const finalUrl = `${endpoint}/${path}`;
if (Config.get().security.cdnSignUrls) {
const signatureData = getUrlSignature(path, req);
finalUrl = `${finalUrl}?ex=${signatureData.expiresAt}&is=${signatureData.issuedAt}&hm=${signatureData.hash}&ip=${req.ip}`;
}
const file = { const file = {
id, id,
@ -82,6 +77,7 @@ router.post(
filename: filename, filename: filename,
size, size,
url: finalUrl, url: finalUrl,
path,
width, width,
height, height,
}; };

View File

@ -21,6 +21,7 @@ import { createHmac, timingSafeEqual } from "crypto";
import ms, { StringValue } from "ms"; import ms, { StringValue } from "ms";
import { ParsedQs } from "qs"; import { ParsedQs } from "qs";
import { Request } from "express"; import { Request } from "express";
import attachments from "../cdn/routes/attachments";
export const getUrlSignature = ( export const getUrlSignature = (
path: string, path: string,

View File

@ -28,6 +28,8 @@ import { URL } from "url";
import { deleteFile } from "../util/cdn"; import { deleteFile } from "../util/cdn";
import { BaseClass } from "./BaseClass"; import { BaseClass } from "./BaseClass";
import { dbEngine } from "../util/Database"; import { dbEngine } from "../util/Database";
import { Request } from "express";
import { resignUrl } from "../Signing";
@Entity({ @Entity({
name: "attachments", name: "attachments",
@ -73,4 +75,11 @@ export class Attachment extends BaseClass {
onDelete() { onDelete() {
return deleteFile(new URL(this.url).pathname); return deleteFile(new URL(this.url).pathname);
} }
signUrls(req: Request) {
return {
url: this.url + resignUrl(this.url, req),
proxy_url: this.proxy_url + resignUrl(this.proxy_url, req),
}
}
} }

View File

@ -40,6 +40,7 @@ import { Webhook } from "./Webhook";
import { Sticker } from "./Sticker"; import { Sticker } from "./Sticker";
import { Attachment } from "./Attachment"; import { Attachment } from "./Attachment";
import { dbEngine } from "../util/Database"; import { dbEngine } from "../util/Database";
import { Request } from "express";
export enum MessageType { export enum MessageType {
DEFAULT = 0, DEFAULT = 0,
@ -260,6 +261,15 @@ export class Message extends BaseClass {
content: this.content ?? "", content: this.content ?? "",
}; };
} }
withSignedAttachments(req: Request) {
return {
...this,
attachments: this.attachments?.map((attachment: Attachment) =>
attachment.signUrls(req)
)
};
}
} }
export interface MessageComponent { export interface MessageComponent {