summary refs log tree commit diff
path: root/src/cdn
diff options
context:
space:
mode:
authorEmma [it/its]@Rory& <root@rory.gay>2023-12-11 01:12:54 +0100
committerEmma [it/its]@Rory& <root@rory.gay>2023-12-11 01:12:54 +0100
commit0a8ceb9e6349284e75545a01ffad608b020f78e2 (patch)
tree17a9163f963eddabf9168b0b630096b2f7535b64 /src/cdn
parentPrettier: use editorconfig (diff)
downloadserver-dev/emma-refactors.tar.xz
Actually run prettier dev/emma-refactors
Diffstat (limited to 'src/cdn')
-rw-r--r--src/cdn/Server.ts22
-rw-r--r--src/cdn/routes/attachments.ts142
-rw-r--r--src/cdn/routes/avatars.ts67
-rw-r--r--src/cdn/routes/guild-profiles.ts16
-rw-r--r--src/cdn/routes/role-icons.ts78
-rw-r--r--src/cdn/util/FileStorage.ts6
-rw-r--r--src/cdn/util/S3Storage.ts6
-rw-r--r--src/cdn/util/Storage.ts12
8 files changed, 129 insertions, 220 deletions
diff --git a/src/cdn/Server.ts b/src/cdn/Server.ts

index 255452a0..7cead16d 100644 --- a/src/cdn/Server.ts +++ b/src/cdn/Server.ts
@@ -43,16 +43,10 @@ export class CDNServer extends Server { // TODO: use better CSP policy res.set( "Content-security-policy", - "default-src * data: blob: filesystem: about: ws: wss: 'unsafe-inline' 'unsafe-eval'; script-src * data: blob: 'unsafe-inline' 'unsafe-eval'; connect-src * data: blob: 'unsafe-inline'; img-src * data: blob: 'unsafe-inline'; frame-src * data: blob: ; style-src * data: blob: 'unsafe-inline'; font-src * data: blob: 'unsafe-inline';", - ); - res.set( - "Access-Control-Allow-Headers", - req.header("Access-Control-Request-Headers") || "*", - ); - res.set( - "Access-Control-Allow-Methods", - req.header("Access-Control-Request-Methods") || "*", + "default-src * data: blob: filesystem: about: ws: wss: 'unsafe-inline' 'unsafe-eval'; script-src * data: blob: 'unsafe-inline' 'unsafe-eval'; connect-src * data: blob: 'unsafe-inline'; img-src * data: blob: 'unsafe-inline'; frame-src * data: blob: ; style-src * data: blob: 'unsafe-inline'; font-src * data: blob: 'unsafe-inline';" ); + res.set("Access-Control-Allow-Headers", req.header("Access-Control-Request-Headers") || "*"); + res.set("Access-Control-Allow-Methods", req.header("Access-Control-Request-Methods") || "*"); next(); }); this.app.use(bodyParser.json({ inflate: true, limit: "10mb" })); @@ -95,16 +89,10 @@ export class CDNServer extends Server { this.app.use("/channel-icons/", avatarsRoute); this.log("verbose", "[Server] Route /channel-icons registered"); - this.app.use( - "/guilds/:guild_id/users/:user_id/avatars", - guildProfilesRoute, - ); + this.app.use("/guilds/:guild_id/users/:user_id/avatars", guildProfilesRoute); this.log("verbose", "[Server] Route /guilds/avatars registered"); - this.app.use( - "/guilds/:guild_id/users/:user_id/banners", - guildProfilesRoute, - ); + this.app.use("/guilds/:guild_id/users/:user_id/banners", guildProfilesRoute); this.log("verbose", "[Server] Route /guilds/banners registered"); Sentry.errorHandler(this.app); diff --git a/src/cdn/routes/attachments.ts b/src/cdn/routes/attachments.ts
index 19bb0b90..3db41da6 100644 --- a/src/cdn/routes/attachments.ts +++ b/src/cdn/routes/attachments.ts
@@ -26,93 +26,75 @@ import imageSize from "image-size"; const router = Router(); -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"); - if (!req.file) throw new HTTPError("file missing"); - - const { buffer, mimetype, size, originalname } = req.file; - const { channel_id } = req.params; - const filename = originalname - .replaceAll(" ", "_") - .replace(/[^a-zA-Z0-9._]+/g, ""); - const id = Snowflake.generate(); - const path = `attachments/${channel_id}/${id}/${filename}`; - - const endpoint = - Config.get()?.cdn.endpointPublic || "http://localhost:3001"; - - await storage.set(path, buffer); - let width; - let height; - if (mimetype.includes("image")) { - const dimensions = imageSize(buffer); - if (dimensions) { - width = dimensions.width; - height = dimensions.height; - } +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"); + if (!req.file) throw new HTTPError("file missing"); + + const { buffer, mimetype, size, originalname } = req.file; + const { channel_id } = req.params; + const filename = originalname.replaceAll(" ", "_").replace(/[^a-zA-Z0-9._]+/g, ""); + const id = Snowflake.generate(); + const path = `attachments/${channel_id}/${id}/${filename}`; + + const endpoint = Config.get()?.cdn.endpointPublic || "http://localhost:3001"; + + await storage.set(path, buffer); + let width; + let height; + if (mimetype.includes("image")) { + const dimensions = imageSize(buffer); + if (dimensions) { + width = dimensions.width; + height = dimensions.height; } + } - const file = { - id, - content_type: mimetype, - filename: filename, - size, - url: `${endpoint}/${path}`, - width, - height, - }; - - return res.json(file); - }, -); - -router.get( - "/:channel_id/:id/:filename", - async (req: Request, res: Response) => { - const { channel_id, id, filename } = req.params; - // const { format } = req.query; - - const path = `attachments/${channel_id}/${id}/${filename}`; - const file = await storage.get(path); - if (!file) throw new HTTPError("File not found"); - const type = await FileType.fromBuffer(file); - let content_type = type?.mime || "application/octet-stream"; - - if (SANITIZED_CONTENT_TYPE.includes(content_type)) { - content_type = "application/octet-stream"; - } + const file = { + id, + content_type: mimetype, + filename: filename, + size, + url: `${endpoint}/${path}`, + width, + height, + }; + + return res.json(file); +}); + +router.get("/:channel_id/:id/:filename", async (req: Request, res: Response) => { + const { channel_id, id, filename } = req.params; + // const { format } = req.query; + + const path = `attachments/${channel_id}/${id}/${filename}`; + const file = await storage.get(path); + if (!file) throw new HTTPError("File not found"); + const type = await FileType.fromBuffer(file); + let content_type = type?.mime || "application/octet-stream"; + + if (SANITIZED_CONTENT_TYPE.includes(content_type)) { + content_type = "application/octet-stream"; + } - res.set("Content-Type", content_type); - res.set("Cache-Control", "public, max-age=31536000"); + res.set("Content-Type", content_type); + res.set("Cache-Control", "public, max-age=31536000"); - return res.send(file); - }, -); + return res.send(file); +}); -router.delete( - "/:channel_id/:id/:filename", - async (req: Request, res: Response) => { - if (req.headers.signature !== Config.get().security.requestSignature) - throw new HTTPError("Invalid request signature"); +router.delete("/:channel_id/: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; - const path = `attachments/${channel_id}/${id}/${filename}`; + const { channel_id, id, filename } = req.params; + const path = `attachments/${channel_id}/${id}/${filename}`; - await storage.delete(path); + await storage.delete(path); - return res.send({ success: true }); - }, -); + return res.send({ success: true }); +}); export default router; diff --git a/src/cdn/routes/avatars.ts b/src/cdn/routes/avatars.ts
index 6af3243f..0887f9c7 100644 --- a/src/cdn/routes/avatars.ts +++ b/src/cdn/routes/avatars.ts
@@ -30,51 +30,36 @@ import { multer } from "../util/multer"; // TODO: delete old icons const ANIMATED_MIME_TYPES = ["image/apng", "image/gif", "image/gifv"]; -const STATIC_MIME_TYPES = [ - "image/png", - "image/jpeg", - "image/webp", - "image/svg+xml", - "image/svg", -]; +const STATIC_MIME_TYPES = ["image/png", "image/jpeg", "image/webp", "image/svg+xml", "image/svg"]; const ALLOWED_MIME_TYPES = [...ANIMATED_MIME_TYPES, ...STATIC_MIME_TYPES]; const router = Router(); -router.post( - "/:user_id", - multer.single("file"), - async (req: Request, res: Response) => { - if (req.headers.signature !== Config.get().security.requestSignature) - throw new HTTPError("Invalid request signature"); - if (!req.file) throw new HTTPError("Missing file"); - const { buffer, size } = req.file; - const { user_id } = req.params; - - let hash = crypto - .createHash("md5") - .update(Snowflake.generate()) - .digest("hex"); - - const type = await FileType.fromBuffer(buffer); - if (!type || !ALLOWED_MIME_TYPES.includes(type.mime)) - throw new HTTPError("Invalid file type"); - if (ANIMATED_MIME_TYPES.includes(type.mime)) hash = `a_${hash}`; // animated icons have a_ infront of the hash - - const path = `avatars/${user_id}/${hash}`; - const endpoint = - Config.get().cdn.endpointPublic || "http://localhost:3001"; - - await storage.set(path, buffer); - - return res.json({ - id: hash, - content_type: type.mime, - size, - url: `${endpoint}${req.baseUrl}/${user_id}/${hash}`, - }); - }, -); +router.post("/:user_id", multer.single("file"), async (req: Request, res: Response) => { + if (req.headers.signature !== Config.get().security.requestSignature) + throw new HTTPError("Invalid request signature"); + if (!req.file) throw new HTTPError("Missing file"); + const { buffer, size } = req.file; + const { user_id } = req.params; + + let hash = crypto.createHash("md5").update(Snowflake.generate()).digest("hex"); + + const type = await FileType.fromBuffer(buffer); + if (!type || !ALLOWED_MIME_TYPES.includes(type.mime)) throw new HTTPError("Invalid file type"); + if (ANIMATED_MIME_TYPES.includes(type.mime)) hash = `a_${hash}`; // animated icons have a_ infront of the hash + + const path = `avatars/${user_id}/${hash}`; + const endpoint = Config.get().cdn.endpointPublic || "http://localhost:3001"; + + await storage.set(path, buffer); + + return res.json({ + id: hash, + content_type: type.mime, + size, + url: `${endpoint}${req.baseUrl}/${user_id}/${hash}`, + }); +}); router.get("/:user_id", async (req: Request, res: Response) => { let { user_id } = req.params; diff --git a/src/cdn/routes/guild-profiles.ts b/src/cdn/routes/guild-profiles.ts
index 1ee5eeca..01377b9d 100644 --- a/src/cdn/routes/guild-profiles.ts +++ b/src/cdn/routes/guild-profiles.ts
@@ -30,13 +30,7 @@ import { storage } from "../util/Storage"; // TODO: delete old icons const ANIMATED_MIME_TYPES = ["image/apng", "image/gif", "image/gifv"]; -const STATIC_MIME_TYPES = [ - "image/png", - "image/jpeg", - "image/webp", - "image/svg+xml", - "image/svg", -]; +const STATIC_MIME_TYPES = ["image/png", "image/jpeg", "image/webp", "image/svg+xml", "image/svg"]; const ALLOWED_MIME_TYPES = [...ANIMATED_MIME_TYPES, ...STATIC_MIME_TYPES]; const router = Router(); @@ -48,14 +42,10 @@ router.post("/", multer.single("file"), async (req: Request, res: Response) => { const { buffer, size } = req.file; const { guild_id, user_id } = req.params; - let hash = crypto - .createHash("md5") - .update(Snowflake.generate()) - .digest("hex"); + let hash = crypto.createHash("md5").update(Snowflake.generate()).digest("hex"); const type = await FileType.fromBuffer(buffer); - if (!type || !ALLOWED_MIME_TYPES.includes(type.mime)) - throw new HTTPError("Invalid file type"); + if (!type || !ALLOWED_MIME_TYPES.includes(type.mime)) throw new HTTPError("Invalid file type"); if (ANIMATED_MIME_TYPES.includes(type.mime)) hash = `a_${hash}`; // animated icons have a_ infront of the hash const path = `guilds/${guild_id}/users/${user_id}/avatars/${hash}`; diff --git a/src/cdn/routes/role-icons.ts b/src/cdn/routes/role-icons.ts
index 8040405a..f599a154 100644 --- a/src/cdn/routes/role-icons.ts +++ b/src/cdn/routes/role-icons.ts
@@ -30,50 +30,35 @@ import { multer } from "../util/multer"; // TODO: generate different sizes of icon // TODO: generate different image types of icon -const STATIC_MIME_TYPES = [ - "image/png", - "image/jpeg", - "image/webp", - "image/svg+xml", - "image/svg", -]; +const STATIC_MIME_TYPES = ["image/png", "image/jpeg", "image/webp", "image/svg+xml", "image/svg"]; const ALLOWED_MIME_TYPES = [...STATIC_MIME_TYPES]; const router = Router(); -router.post( - "/:role_id", - multer.single("file"), - async (req: Request, res: Response) => { - if (req.headers.signature !== Config.get().security.requestSignature) - throw new HTTPError("Invalid request signature"); - if (!req.file) throw new HTTPError("Missing file"); - const { buffer, size } = req.file; - const { role_id } = req.params; - - const hash = crypto - .createHash("md5") - .update(Snowflake.generate()) - .digest("hex"); - - const type = await FileType.fromBuffer(buffer); - if (!type || !ALLOWED_MIME_TYPES.includes(type.mime)) - throw new HTTPError("Invalid file type"); - - const path = `role-icons/${role_id}/${hash}.png`; - const endpoint = - Config.get().cdn.endpointPublic || "http://localhost:3001"; - - await storage.set(path, buffer); - - return res.json({ - id: hash, - content_type: type.mime, - size, - url: `${endpoint}${req.baseUrl}/${role_id}/${hash}`, - }); - }, -); +router.post("/:role_id", multer.single("file"), async (req: Request, res: Response) => { + if (req.headers.signature !== Config.get().security.requestSignature) + throw new HTTPError("Invalid request signature"); + if (!req.file) throw new HTTPError("Missing file"); + const { buffer, size } = req.file; + const { role_id } = req.params; + + const hash = crypto.createHash("md5").update(Snowflake.generate()).digest("hex"); + + const type = await FileType.fromBuffer(buffer); + if (!type || !ALLOWED_MIME_TYPES.includes(type.mime)) throw new HTTPError("Invalid file type"); + + const path = `role-icons/${role_id}/${hash}.png`; + const endpoint = Config.get().cdn.endpointPublic || "http://localhost:3001"; + + await storage.set(path, buffer); + + return res.json({ + id: hash, + content_type: type.mime, + size, + url: `${endpoint}${req.baseUrl}/${role_id}/${hash}`, + }); +}); router.get("/:role_id", async (req: Request, res: Response) => { const { role_id } = req.params; @@ -97,19 +82,10 @@ router.get("/:role_id/:hash", async (req: Request, res: Response) => { const role_icon_hash = hash.split(".")[0]; let file: Buffer | null = null; - const extensions_to_try = [ - requested_extension, - "png", - "jpg", - "jpeg", - "webp", - "svg", - ]; + const extensions_to_try = [requested_extension, "png", "jpg", "jpeg", "webp", "svg"]; for (let i = 0; i < extensions_to_try.length; i++) { - file = await storage.get( - `role-icons/${role_id}/${role_icon_hash}.${extensions_to_try[i]}`, - ); + file = await storage.get(`role-icons/${role_id}/${role_icon_hash}.${extensions_to_try[i]}`); if (file) break; } diff --git a/src/cdn/util/FileStorage.ts b/src/cdn/util/FileStorage.ts
index 10b36743..5e53081b 100644 --- a/src/cdn/util/FileStorage.ts +++ b/src/cdn/util/FileStorage.ts
@@ -30,8 +30,7 @@ function getPath(path: string) { const root = process.env.STORAGE_LOCATION || "../"; const filename = join(root, path); - if (path.indexOf("\0") !== -1 || !filename.startsWith(root)) - throw new Error("invalid path"); + if (path.indexOf("\0") !== -1 || !filename.startsWith(root)) throw new Error("invalid path"); return filename; } @@ -53,8 +52,7 @@ export class FileStorage implements Storage { async set(path: string, value: Buffer) { path = getPath(path); - if (!fs.existsSync(dirname(path))) - fs.mkdirSync(dirname(path), { recursive: true }); + if (!fs.existsSync(dirname(path))) fs.mkdirSync(dirname(path), { recursive: true }); const ret = Readable.from(value); const cleaned_file = fs.createWriteStream(path); diff --git a/src/cdn/util/S3Storage.ts b/src/cdn/util/S3Storage.ts
index 81acd945..fd079ef0 100644 --- a/src/cdn/util/S3Storage.ts +++ b/src/cdn/util/S3Storage.ts
@@ -29,11 +29,7 @@ const readableToBuffer = (readable: Readable): Promise<Buffer> => }); export class S3Storage implements Storage { - public constructor( - private client: S3, - private bucket: string, - private basePath?: string, - ) {} + public constructor(private client: S3, private bucket: string, private basePath?: string) {} /** * Always return a string, to ensure consistency. diff --git a/src/cdn/util/Storage.ts b/src/cdn/util/Storage.ts
index 26289af6..609e38e9 100644 --- a/src/cdn/util/Storage.ts +++ b/src/cdn/util/Storage.ts
@@ -49,16 +49,12 @@ if (process.env.STORAGE_PROVIDER === "file" || !process.env.STORAGE_PROVIDER) { bucket = process.env.STORAGE_BUCKET; if (!region) { - console.error( - `[CDN] You must provide a region when using the S3 storage provider.`, - ); + console.error(`[CDN] You must provide a region when using the S3 storage provider.`); process.exit(1); } if (!bucket) { - console.error( - `[CDN] You must provide a bucket when using the S3 storage provider.`, - ); + console.error(`[CDN] You must provide a bucket when using the S3 storage provider.`); process.exit(1); } @@ -66,9 +62,7 @@ if (process.env.STORAGE_PROVIDER === "file" || !process.env.STORAGE_PROVIDER) { let location = process.env.STORAGE_LOCATION; if (!location) { - console.warn( - `[CDN] STORAGE_LOCATION unconfigured for S3 provider, defaulting to the bucket root...`, - ); + console.warn(`[CDN] STORAGE_LOCATION unconfigured for S3 provider, defaulting to the bucket root...`); location = undefined; }