From aee64e9aca9f883afdd67105c93909e9f208956b Mon Sep 17 00:00:00 2001 From: Mathias Koehler Date: Sun, 10 Nov 2024 23:52:03 +0100 Subject: [PATCH] Add signature for proxied URLs; Proxy Avatar and Banner --- packages/backend/src/config.ts | 3 +++ .../src/core/activitypub/ApRendererService.ts | 11 +++++++---- .../src/core/entities/DriveFileEntityService.ts | 7 ++++++- packages/backend/src/server/FileServerService.ts | 12 ++++++++---- 4 files changed, 24 insertions(+), 9 deletions(-) diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts index 3dc49c7eb6..69a6a36453 100644 --- a/packages/backend/src/config.ts +++ b/packages/backend/src/config.ts @@ -96,6 +96,7 @@ type Source = { inboxJobMaxAttempts?: number; mediaProxy?: string; + mediaProxySecret?: string; proxyRemoteFiles?: boolean; videoThumbnailGenerator?: string; @@ -193,6 +194,7 @@ export type Config = { frontendEmbedEntry: string; frontendEmbedManifestExists: boolean; mediaProxy: string; + mediaProxySecret: string; externalMediaProxyEnabled: boolean; videoThumbnailGenerator: string | null; redis: RedisOptions & RedisOptionsSource; @@ -333,6 +335,7 @@ export function loadConfig(): Config { attachLdSignatureForRelays: config.attachLdSignatureForRelays ?? true, checkActivityPubGetSignature: config.checkActivityPubGetSignature, mediaProxy: externalMediaProxy ?? internalMediaProxy, + mediaProxySecret: config.mediaProxySecret, externalMediaProxyEnabled: externalMediaProxy !== null && externalMediaProxy !== internalMediaProxy, videoThumbnailGenerator: config.videoThumbnailGenerator ? config.videoThumbnailGenerator.endsWith('/') ? config.videoThumbnailGenerator.substring(0, config.videoThumbnailGenerator.length - 1) : config.videoThumbnailGenerator diff --git a/packages/backend/src/core/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts index 42ee5bc58a..25d548b7ea 100644 --- a/packages/backend/src/core/activitypub/ApRendererService.ts +++ b/packages/backend/src/core/activitypub/ApRendererService.ts @@ -249,10 +249,13 @@ export class ApRendererService { } @bindThis - public renderImage(file: MiDriveFile): IApImage { + public renderImage(file: MiDriveFile, mode: 'avatar' | 'banner' = 'avatar'): IApImage { return { type: 'Image', - url: this.driveFileEntityService.getPublicUrl(file), + url: this.driveFileEntityService.getProxiedUrl( + this.driveFileEntityService.getPublicUrl(file) + mode, + ), sensitive: file.isSensitive, name: file.comment, }; @@ -517,8 +520,8 @@ export class ApRendererService { summary: profile.description ? this.mfmService.toHtml(mfm.parse(profile.description)) : null, _misskey_summary: profile.description, _misskey_followedMessage: profile.followedMessage, - icon: avatar ? this.renderImage(avatar) : null, - image: banner ? this.renderImage(banner) : null, + icon: avatar ? this.renderImage(avatar, 'avatar') : null, + image: banner ? this.renderImage(banner, 'banner') : null, backgroundUrl: background ? this.renderImage(background) : null, tag, manuallyApprovesFollowers: user.isLocked, diff --git a/packages/backend/src/core/entities/DriveFileEntityService.ts b/packages/backend/src/core/entities/DriveFileEntityService.ts index c485555f90..bab4246662 100644 --- a/packages/backend/src/core/entities/DriveFileEntityService.ts +++ b/packages/backend/src/core/entities/DriveFileEntityService.ts @@ -3,6 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ +import * as crypto from 'node:crypto'; import { forwardRef, Inject, Injectable } from '@nestjs/common'; import { In } from 'typeorm'; import { DI } from '@/di-symbols.js'; @@ -74,12 +75,16 @@ export class DriveFileEntityService { } @bindThis - private getProxiedUrl(url: string, mode?: 'static' | 'avatar'): string { + public getProxiedUrl(url: string, mode?: 'static' | 'avatar' | 'banner'): string { + const signature = crypto.createHmac('sha256', this.config.mediaProxySecret) + .update(url) + .digest('hex')[8]; return appendQuery( `${this.config.mediaProxy}/${mode ?? 'image'}.webp`, query({ url, ...(mode ? { [mode]: '1' } : {}), + sign: signature }), ); } diff --git a/packages/backend/src/server/FileServerService.ts b/packages/backend/src/server/FileServerService.ts index 1a4d0cb48f..6caa935ccc 100644 --- a/packages/backend/src/server/FileServerService.ts +++ b/packages/backend/src/server/FileServerService.ts @@ -4,6 +4,7 @@ */ import * as fs from 'node:fs'; +import * as crypto from 'node:crypto'; import { fileURLToPath } from 'node:url'; import { dirname } from 'node:path'; import { Inject, Injectable } from '@nestjs/common'; @@ -317,10 +318,13 @@ export class FileServerService { ); } - if (!request.headers['user-agent']) { - throw new StatusError('User-Agent is required', 400, 'User-Agent is required'); - } else if (request.headers['user-agent'].toLowerCase().indexOf('misskey/') !== -1) { - throw new StatusError('Refusing to proxy a request from another proxy', 403, 'Proxy is recursive'); + const signature = crypto.createHmac('sha256', this.config.mediaProxySecret) + .update(url) + .digest('hex')[8]; + + if (signature !== request.params.sign) { + reply.code(403); + return; } // Create temp file -- 2.39.5