Add signature for proxied URLs; Proxy Avatar and Banner

This commit is contained in:
Mathias Koehler 2024-11-10 23:52:03 +01:00
parent cd2e597223
commit aee64e9aca
Signed by untrusted user who does not match committer: interru
GPG key ID: E01E350F5E2A884A
4 changed files with 24 additions and 9 deletions

View file

@ -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

View file

@ -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,

View file

@ -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
}),
);
}

View file

@ -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