diff --git a/assets/openapi.json b/assets/openapi.json
index 56d5a2cd..913920f5 100644
--- a/assets/openapi.json
+++ b/assets/openapi.json
@@ -11201,12 +11201,6 @@
"size": {
"type": "integer"
},
- "url": {
- "type": "string"
- },
- "proxy_url": {
- "type": "string"
- },
"height": {
"type": "integer"
},
@@ -11219,21 +11213,27 @@
"message_id": {
"type": "string"
},
+ "channel_id": {
+ "type": "string"
+ },
"message": {
"$ref": "#/components/schemas/Message"
},
+ "channel": {
+ "$ref": "#/components/schemas/Channel"
+ },
"id": {
"type": "string"
}
},
"required": [
+ "channel",
+ "channel_id",
"filename",
"id",
"message",
"message_id",
- "proxy_url",
- "size",
- "url"
+ "size"
]
},
"Embed": {
diff --git a/assets/schemas.json b/assets/schemas.json
index 82bc8860..3d9692a6 100644
--- a/assets/schemas.json
+++ b/assets/schemas.json
@@ -11890,12 +11890,6 @@
"size": {
"type": "integer"
},
- "url": {
- "type": "string"
- },
- "proxy_url": {
- "type": "string"
- },
"height": {
"type": "integer"
},
@@ -11908,22 +11902,28 @@
"message_id": {
"type": "string"
},
+ "channel_id": {
+ "type": "string"
+ },
"message": {
"$ref": "#/definitions/Message"
},
+ "channel": {
+ "$ref": "#/definitions/Channel"
+ },
"id": {
"type": "string"
}
},
"additionalProperties": false,
"required": [
+ "channel",
+ "channel_id",
"filename",
"id",
"message",
"message_id",
- "proxy_url",
- "size",
- "url"
+ "size"
],
"$schema": "http://json-schema.org/draft-07/schema#"
},
diff --git a/src/api/routes/channels/#channel_id/messages/#message_id/index.ts b/src/api/routes/channels/#channel_id/messages/#message_id/index.ts
index 32f51bea..52271c83 100644
--- a/src/api/routes/channels/#channel_id/messages/#message_id/index.ts
+++ b/src/api/routes/channels/#channel_id/messages/#message_id/index.ts
@@ -190,7 +190,7 @@ router.put(
if (req.file) {
try {
const file = await uploadFile(`/attachments/${req.params.channel_id}`, req.file);
- attachments.push(Attachment.create({ ...file, proxy_url: file.url }));
+ attachments.push(Attachment.create(file));
} catch (error) {
return res.status(400).json(error);
}
diff --git a/src/api/routes/channels/#channel_id/messages/index.ts b/src/api/routes/channels/#channel_id/messages/index.ts
index 20b3ab22..a484c35c 100644
--- a/src/api/routes/channels/#channel_id/messages/index.ts
+++ b/src/api/routes/channels/#channel_id/messages/index.ts
@@ -178,8 +178,6 @@ router.get(
}
await Message.fillReplies(messages);
- const endpoint = Config.get().cdn.endpointPublic;
-
const ret = messages.map((msg) => {
const x = msg.toJSON();
@@ -197,40 +195,32 @@ router.get(
public_flags: 0,
avatar: null,
} as PartialUser;
- x.attachments?.forEach((y: Attachment) => {
- // dynamically set attachment proxy_url in case the endpoint changed
- const uri = y.proxy_url.startsWith("http") ? y.proxy_url : `https://example.org${y.proxy_url}`;
-
- const url = new URL(uri);
- if (endpoint) {
- const newBase = new URL(endpoint);
- url.protocol = newBase.protocol;
- url.hostname = newBase.hostname;
- url.port = newBase.port;
- }
+ x.attachments =
+ msg.attachments?.map((y: Attachment) => {
+ const att = y.toJSON();
- y.proxy_url = url.toString();
+ att.proxy_url = getUrlSignature(
+ new NewUrlSignatureData({
+ url: att.proxy_url,
+ userAgent: req.headers["user-agent"],
+ ip: req.ip,
+ }),
+ )
+ .applyToUrl(att.proxy_url)
+ .toString();
- y.proxy_url = getUrlSignature(
- new NewUrlSignatureData({
- url: y.proxy_url,
- userAgent: req.headers["user-agent"],
- ip: req.ip,
- }),
- )
- .applyToUrl(y.proxy_url)
- .toString();
+ att.url = getUrlSignature(
+ new NewUrlSignatureData({
+ url: att.url,
+ userAgent: req.headers["user-agent"],
+ ip: req.ip,
+ }),
+ )
+ .applyToUrl(att.url)
+ .toString();
- y.url = getUrlSignature(
- new NewUrlSignatureData({
- url: y.url,
- userAgent: req.headers["user-agent"],
- ip: req.ip,
- }),
- )
- .applyToUrl(y.url)
- .toString();
- });
+ return att;
+ }) ?? [];
/**
Some clients ( discord.js ) only check if a property exists within the response,
@@ -312,6 +302,7 @@ router.post(
async (req: Request, res: Response) => {
const { channel_id } = req.params as { [key: string]: string };
const body = req.body as MessageCreateSchema;
+ const messageId = Snowflake.generate();
const attachments: (Attachment | MessageCreateAttachment | MessageCreateCloudAttachment)[] = body.attachments ?? [];
const channel = await Channel.findOneOrFail({
@@ -418,8 +409,8 @@ router.post(
const files = (req.files as Express.Multer.File[]) ?? [];
for (const currFile of files) {
try {
- const file = await uploadFile(`/attachments/${channel.id}`, currFile);
- attachments.push(Attachment.create({ ...file, proxy_url: file.url }));
+ const file = await uploadFile(`/attachments/${channel.id}/${messageId}`, currFile);
+ attachments.push(Attachment.create(file));
} catch (error) {
return res.status(400).json({ message: error?.toString() });
}
@@ -429,6 +420,7 @@ router.post(
if (body.embed) embeds.push(body.embed);
const message = await handleMessage({
...body,
+ id: messageId,
type: 0,
pinned: false,
author_id: req.user_id,
diff --git a/src/api/routes/channels/#channel_id/threads.ts b/src/api/routes/channels/#channel_id/threads.ts
index 2af92f53..ce7a9435 100644
--- a/src/api/routes/channels/#channel_id/threads.ts
+++ b/src/api/routes/channels/#channel_id/threads.ts
@@ -31,6 +31,7 @@ import {
ThreadMember,
Message,
ChannelFlags,
+ Snowflake,
} from "@spacebar/util";
import { ChannelType, MessageType, ThreadCreationSchema, MessageCreateAttachment, MessageCreateCloudAttachment } from "@spacebar/schemas";
@@ -140,8 +141,8 @@ router.post(
const attachments: (Attachment | MessageCreateAttachment | MessageCreateCloudAttachment)[] = body.message.attachments ?? [];
for (const currFile of files) {
try {
- const file = await uploadFile(`/attachments/${channel.id}`, currFile);
- attachments.push(Attachment.create({ ...file, proxy_url: file.url }));
+ const file = await uploadFile(`/attachments/${channel.id}/${thread.id}`, currFile);
+ attachments.push(Attachment.create(file));
} catch (error) {
return res.status(400).json({ message: error?.toString() });
}
diff --git a/src/api/routes/channels/#channel_id/webhooks.ts b/src/api/routes/channels/#channel_id/webhooks.ts
index 0f686173..ee801db7 100644
--- a/src/api/routes/channels/#channel_id/webhooks.ts
+++ b/src/api/routes/channels/#channel_id/webhooks.ts
@@ -46,7 +46,7 @@ router.get(
return res.json(
webhooks.map((webhook) => ({
...webhook,
- url: Config.get().api.endpointPublic + "/webhooks/" + webhook.id + "/" + webhook.token,
+ url: Config.get().api.endpointPublic + "/api/webhooks/" + webhook.id + "/" + webhook.token,
})),
);
},
diff --git a/src/api/routes/interactions/#interaction_id/#interaction_token/callback.ts b/src/api/routes/interactions/#interaction_id/#interaction_token/callback.ts
index 7a781267..d8ec9c26 100644
--- a/src/api/routes/interactions/#interaction_id/#interaction_token/callback.ts
+++ b/src/api/routes/interactions/#interaction_id/#interaction_token/callback.ts
@@ -47,9 +47,9 @@ router.post(
user_id: interaction?.userId,
data: {
id: interactionId,
- nonce: interaction.nonce ?? "", // TODO: did i do this right?
+ nonce: interaction.nonce ?? "", // TODO: did i do this right?
},
- } satisfies InteractionSuccessEvent);
+ } satisfies InteractionSuccessEvent);
switch (body.type) {
case InteractionCallbackType.PONG:
@@ -70,7 +70,7 @@ router.post(
for (const currFile of files) {
try {
const file = await uploadFile(`/attachments/${interaction.channelId}`, currFile);
- attachments.push(Attachment.create({ ...file, proxy_url: file.url }));
+ attachments.push(Attachment.create(file));
} catch (error) {
return res.status(400).json({ message: error?.toString() });
}
diff --git a/src/api/util/handlers/Message.ts b/src/api/util/handlers/Message.ts
index 1fb9a080..afb585c9 100644
--- a/src/api/util/handlers/Message.ts
+++ b/src/api/util/handlers/Message.ts
@@ -171,12 +171,11 @@ async function processMedia(media: UnfurledMediaItem, messageId: string, batchId
const realAtt = Attachment.create({
filename: attEnt.userFilename,
- url: media.url,
- proxy_url: media.proxy_url,
size: attEnt.size,
height: attEnt.height,
width: attEnt.width,
content_type: attEnt.contentType || attEnt.userOriginalContentType,
+ channel_id: channel.id,
});
await realAtt.save();
@@ -396,12 +395,11 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> {
const realAtt = Attachment.create({
filename: attEnt.userFilename,
- url: `${conf.cdn.endpointPublic}/${cloneRespBody.new_path}`,
- proxy_url: `${conf.cdn.endpointPublic}/${cloneRespBody.new_path}`,
size: attEnt.size,
height: attEnt.height,
width: attEnt.width,
content_type: attEnt.contentType || attEnt.userOriginalContentType,
+ channel_id: channel.id,
});
await realAtt.save();
return { attachment: realAtt, index: att.index };
@@ -744,22 +742,22 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> {
const footer = embed.footer;
const footerAttachment = fetchAttachment(footer?.icon_url);
if (footerAttachment !== undefined) {
- footer!.icon_url = footerAttachment.url;
- footer!.proxy_icon_url = footerAttachment.proxy_url;
+ footer!.icon_url = footerAttachment.toJSON().url;
+ footer!.proxy_icon_url = footerAttachment.toJSON().proxy_url;
}
const image = embed.image;
const imageAttachment = fetchAttachment(image?.url);
if (imageAttachment !== undefined) {
- image!.url = imageAttachment.url;
- image!.proxy_url = imageAttachment.proxy_url;
+ image!.url = imageAttachment.toJSON().url;
+ image!.proxy_url = imageAttachment.toJSON().proxy_url;
}
const author = embed.author;
const authorAttachment = fetchAttachment(author?.icon_url);
if (authorAttachment !== undefined) {
- author!.icon_url = authorAttachment.url;
- author!.proxy_icon_url = authorAttachment.proxy_url;
+ author!.icon_url = authorAttachment.toJSON().url;
+ author!.proxy_icon_url = authorAttachment.toJSON().proxy_url;
}
}
message.attachments = message.attachments?.filter((_, index) => {
diff --git a/src/api/util/handlers/Webhook.ts b/src/api/util/handlers/Webhook.ts
index deb0331f..73e609d8 100644
--- a/src/api/util/handlers/Webhook.ts
+++ b/src/api/util/handlers/Webhook.ts
@@ -1,5 +1,5 @@
import { handleMessage, postHandleMessage } from "@spacebar/api";
-import { Attachment, Channel, Config, DiscordApiErrors, emitEvent, FieldErrors, Message, MessageCreateEvent, uploadFile, ValidateName, Webhook } from "@spacebar/util";
+import { Attachment, Channel, Config, DiscordApiErrors, emitEvent, FieldErrors, Message, MessageCreateEvent, Snowflake, uploadFile, ValidateName, Webhook } from "@spacebar/util";
import { Request, Response } from "express";
import { HTTPError } from "lambert-server";
import { MoreThan } from "typeorm";
@@ -7,6 +7,7 @@ import { WebhookExecuteSchema } from "@spacebar/schemas";
export const executeWebhook = async (req: Request, res: Response) => {
const body = req.body as WebhookExecuteSchema;
+ const messageId = Snowflake.generate();
const { webhook_id, token } = req.params as { [key: string]: string };
@@ -87,8 +88,8 @@ export const executeWebhook = async (req: Request, res: Response) => {
const files = (req.files as Express.Multer.File[]) ?? [];
for (const currFile of files) {
try {
- const file = await uploadFile(`/attachments/${sendChannel.id}`, currFile);
- attachments.push(Attachment.create({ ...file, proxy_url: file.url }));
+ const file = await uploadFile(`/attachments/${sendChannel.id}/${messageId}`, currFile);
+ attachments.push(Attachment.create(file));
} catch (error) {
if (wait) res.status(400).json({ message: error?.toString() });
return;
@@ -106,6 +107,7 @@ export const executeWebhook = async (req: Request, res: Response) => {
: undefined,
} as Parameters<typeof handleMessage>[0];
const message = await handleMessage({
+ id: messageId,
...bodyMsg,
username: body.username || webhook.name,
avatar_url: body.avatar_url || webhook.avatar,
diff --git a/src/cdn/Server.ts b/src/cdn/Server.ts
index dff76494..42323556 100644
--- a/src/cdn/Server.ts
+++ b/src/cdn/Server.ts
@@ -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}`);
- }
- }
}
diff --git a/src/cdn/routes/attachments.ts b/src/cdn/routes/attachments.ts
index 70825cf6..d17ac43c 100644
--- a/src/cdn/routes/attachments.ts
+++ b/src/cdn/routes/attachments.ts
@@ -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);
diff --git a/src/schemas/api/messages/Attachments.ts b/src/schemas/api/messages/Attachments.ts
new file mode 100644
index 00000000..4f82dc8e
--- /dev/null
+++ b/src/schemas/api/messages/Attachments.ts
@@ -0,0 +1,27 @@
+/*
+ Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
+ Copyright (C) 2026 Spacebar and Spacebar Contributors
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+export interface PublicAttachment {
+ filename: string; // name of file attached
+ size: number; // size of file in bytes
+ height?: number; // height of file (if image)
+ width?: number; // width of file (if image)
+ content_type?: string;
+ url: string;
+ proxy_url: string;
+}
diff --git a/src/schemas/api/messages/Message.ts b/src/schemas/api/messages/Message.ts
index 04e7f5e1..515514ee 100644
--- a/src/schemas/api/messages/Message.ts
+++ b/src/schemas/api/messages/Message.ts
@@ -18,6 +18,7 @@
import { Attachment, Sticker } from "@spacebar/util";
import { Embed, MessageActivity, MessageComponent, PartialUser, Poll, PublicChannel, Snowflake } from "@spacebar/schemas";
+import { PublicAttachment } from "./Attachments";
export enum MessageType {
DEFAULT = 0,
@@ -174,7 +175,7 @@ export interface PublicMessage {
mentions: PartialUser[];
mention_roles: Snowflake[];
mention_channels?: PublicChannel[]; // TODO: PartialPublicChannel
- attachments: Attachment[];
+ attachments: PublicAttachment[];
embeds: Embed[];
reactions?: Reaction[];
nonce?: number | string;
@@ -237,4 +238,4 @@ export interface MessageReference {
guild_id?: string;
fail_if_not_exists?: boolean;
type?: number;
-}
\ No newline at end of file
+}
diff --git a/src/util/entities/Attachment.ts b/src/util/entities/Attachment.ts
index ab001cbe..ff9643fc 100644
--- a/src/util/entities/Attachment.ts
+++ b/src/util/entities/Attachment.ts
@@ -20,6 +20,7 @@ import { BeforeRemove, Column, Entity, JoinColumn, ManyToOne, RelationId } from
import { Config, deleteFile } from "../util";
import { BaseClass } from "./BaseClass";
import { getUrlSignature, NewUrlUserSignatureData, NewUrlSignatureData } from "../Signing";
+import { PublicAttachment } from "../../schemas/api/messages/Attachments";
@Entity({
name: "attachments",
@@ -45,7 +46,7 @@ export class Attachment extends BaseClass {
message_id: string;
@Column({ nullable: true })
- @RelationId((attachment: Attachment) => attachment.message)
+ @RelationId((attachment: Attachment) => attachment.channel)
channel_id: string;
@JoinColumn({ name: "message_id" })
@@ -55,25 +56,27 @@ export class Attachment extends BaseClass {
message: import("./Message").Message;
@JoinColumn({ name: "channel_id" })
- @ManyToOne(() => require("./Channel").Channel, (message: import("./Message").Message) => message.attachments, {
+ @ManyToOne(() => require("./Channel").Channel, {
onDelete: "CASCADE",
})
channel: import("./Channel").Channel;
@BeforeRemove()
onDelete() {
- return deleteFile(new URL(this.url).pathname);
+ return deleteFile(new URL(this.toJSON().url).pathname);
}
toJSON() {
+ const channelId = this.channel_id ?? this.channel?.id ?? this.message?.channel_id;
+ const messageId = this.message_id ?? this.message?.id;
return {
...this,
- url: `${Config.get().cdn.endpointPublic}/attachments/${this.channel_id}/${this.message_id}/${this.filename}`,
- proxy_url: `${Config.get().cdn.endpointPublic}/attachments/${this.channel_id}/${this.message_id}/${this.filename}`,
+ url: `${Config.get().cdn.endpointPublic}/attachments/${channelId}/${messageId}/${this.filename}`,
+ proxy_url: `${Config.get().cdn.endpointPublic}/attachments/${channelId}/${messageId}/${this.filename}`,
};
}
- signUrls(data: NewUrlUserSignatureData): Attachment {
- const att = this.toJSON();
+ signUrls(data: NewUrlUserSignatureData): PublicAttachment {
+ const att = Attachment.prototype.toJSON.apply(this);
return {
...att,
url: getUrlSignature(new NewUrlSignatureData({ ...data, url: att.url }))
diff --git a/src/util/entities/Message.ts b/src/util/entities/Message.ts
index 23e699b5..7c5d6b6d 100644
--- a/src/util/entities/Message.ts
+++ b/src/util/entities/Message.ts
@@ -284,7 +284,7 @@ export class Message extends BaseClass {
mention_roles: this.mention_roles?.map((role) => role.id) ?? [],
mention_channels: this.mention_channels?.map((ch) => ch.toJSON()) ?? [],
- attachments: this.attachments ?? [],
+ attachments: this.attachments?.map((att) => att.toJSON()) ?? [],
nonce: this.nonce ?? undefined,
tts: this.tts ?? false,
diff --git a/src/util/util/Config.ts b/src/util/util/Config.ts
index 27f0b450..42bb0744 100644
--- a/src/util/util/Config.ts
+++ b/src/util/util/Config.ts
@@ -54,24 +54,33 @@ export class Config {
config = OrmUtils.mergeDeep({}, { ...new ConfigValue() }, config);
// TODO: factor this out someday
- if (process.env.CDN_SIGNATURE_PATH) config.security.cdnSignatureKey = (await fs.readFile(process.env.CDN_SIGNATURE_PATH, "utf-8")).trim();
- if (process.env.LEGACY_JWT_SECRET_PATH) config.security.jwtSecret = (await fs.readFile(process.env.LEGACY_JWT_SECRET_PATH, "utf-8")).trim();
- if (process.env.MAILJET_API_KEY_PATH) config.email.mailjet.apiKey = (await fs.readFile(process.env.MAILJET_API_KEY_PATH, "utf-8")).trim();
- if (process.env.MAILJET_API_SECRET_PATH) config.email.mailjet.apiSecret = (await fs.readFile(process.env.MAILJET_API_SECRET_PATH, "utf-8")).trim();
- if (process.env.SMTP_PASSWORD_PATH) config.email.smtp.password = (await fs.readFile(process.env.SMTP_PASSWORD_PATH, "utf-8")).trim();
- if (process.env.GIF_API_KEY_PATH) config.gif.apiKey = (await fs.readFile(process.env.GIF_API_KEY_PATH, "utf-8")).trim();
+ if (process.env.CDN_SIGNATURE_PATH) config.security.cdnSignatureKey = await Config.readSecret("CDN_SIGNATURE_PATH");
+ if (process.env.LEGACY_JWT_SECRET_PATH) config.security.jwtSecret = await Config.readSecret("LEGACY_JWT_SECRET_PATH");
+ if (process.env.MAILJET_API_KEY_PATH) config.email.mailjet.apiKey = await Config.readSecret("MAILJET_API_KEY_PATH");
+ if (process.env.MAILJET_API_SECRET_PATH) config.email.mailjet.apiSecret = await Config.readSecret("MAILJET_API_SECRET_PATH");
+ if (process.env.SMTP_PASSWORD_PATH) config.email.smtp.password = await Config.readSecret("SMTP_PASSWORD_PATH");
+ if (process.env.GIF_API_KEY_PATH) config.gif.apiKey = await Config.readSecret("GIF_API_KEY_PATH");
if (process.env.RABBITMQ_HOST) config.rabbitmq.host = process.env.RABBITMQ_HOST.trim();
- if (process.env.RABBITMQ_HOST_PATH) config.rabbitmq.host = (await fs.readFile(process.env.RABBITMQ_HOST_PATH, "utf-8")).trim();
- if (process.env.ABUSEIPDB_API_KEY_PATH) config.security.abuseIpDbApiKey = (await fs.readFile(process.env.ABUSEIPDB_API_KEY_PATH, "utf-8")).trim();
- if (process.env.CAPTCHA_SECRET_KEY_PATH) config.security.captcha.secret = (await fs.readFile(process.env.CAPTCHA_SECRET_KEY_PATH, "utf-8")).trim();
- if (process.env.CAPTCHA_SITE_KEY_PATH) config.security.captcha.sitekey = (await fs.readFile(process.env.CAPTCHA_SITE_KEY_PATH, "utf-8")).trim();
- if (process.env.IPDATA_API_KEY_PATH) config.security.ipdataApiKey = (await fs.readFile(process.env.IPDATA_API_KEY_PATH, "utf-8")).trim();
- if (process.env.REQUEST_SIGNATURE_PATH) config.security.requestSignature = (await fs.readFile(process.env.REQUEST_SIGNATURE_PATH, "utf-8")).trim();
+ if (process.env.RABBITMQ_HOST_PATH) config.rabbitmq.host = await Config.readSecret("RABBITMQ_HOST_PATH");
+ if (process.env.ABUSEIPDB_API_KEY_PATH) config.security.abuseIpDbApiKey = await Config.readSecret("ABUSEIPDB_API_KEY_PATH");
+ if (process.env.CAPTCHA_SECRET_KEY_PATH) config.security.captcha.secret = await Config.readSecret("CAPTCHA_SECRET_KEY_PATH");
+ if (process.env.CAPTCHA_SITE_KEY_PATH) config.security.captcha.sitekey = await Config.readSecret("CAPTCHA_SITE_KEY_PATH");
+ if (process.env.IPDATA_API_KEY_PATH) config.security.ipdataApiKey = await Config.readSecret("IPDATA_API_KEY_PATH");
+ if (process.env.REQUEST_SIGNATURE_PATH) config.security.requestSignature = await Config.readSecret("REQUEST_SIGNATURE_PATH");
await this.set(config);
validateFinalConfig(config);
return config;
}
+
+ private static async readSecret(name: string) {
+ process.stdout.write(`[Config] Reading secret ${name}...`);
+ const res = (await fs.readFile(process.env[name]!, "utf-8")).trim();
+ if (process.env.LOG_SECRET_VALUES) process.stdout.write(" " + res);
+ else process.stdout.write(" Done!");
+ process.stdout.write("\n");
+ return res;
+ }
public static get() {
if (!config) {
// If we haven't initialised the config yet, return default config.
|