Add signature for proxied URLs; Proxy Avatar and Banner #8
4 changed files with 24 additions and 9 deletions
|
@ -96,6 +96,7 @@ type Source = {
|
||||||
inboxJobMaxAttempts?: number;
|
inboxJobMaxAttempts?: number;
|
||||||
|
|
||||||
mediaProxy?: string;
|
mediaProxy?: string;
|
||||||
|
mediaProxySecret?: string;
|
||||||
proxyRemoteFiles?: boolean;
|
proxyRemoteFiles?: boolean;
|
||||||
videoThumbnailGenerator?: string;
|
videoThumbnailGenerator?: string;
|
||||||
|
|
||||||
|
@ -193,6 +194,7 @@ export type Config = {
|
||||||
frontendEmbedEntry: string;
|
frontendEmbedEntry: string;
|
||||||
frontendEmbedManifestExists: boolean;
|
frontendEmbedManifestExists: boolean;
|
||||||
mediaProxy: string;
|
mediaProxy: string;
|
||||||
|
mediaProxySecret: string;
|
||||||
externalMediaProxyEnabled: boolean;
|
externalMediaProxyEnabled: boolean;
|
||||||
videoThumbnailGenerator: string | null;
|
videoThumbnailGenerator: string | null;
|
||||||
redis: RedisOptions & RedisOptionsSource;
|
redis: RedisOptions & RedisOptionsSource;
|
||||||
|
@ -333,6 +335,7 @@ export function loadConfig(): Config {
|
||||||
attachLdSignatureForRelays: config.attachLdSignatureForRelays ?? true,
|
attachLdSignatureForRelays: config.attachLdSignatureForRelays ?? true,
|
||||||
checkActivityPubGetSignature: config.checkActivityPubGetSignature,
|
checkActivityPubGetSignature: config.checkActivityPubGetSignature,
|
||||||
mediaProxy: externalMediaProxy ?? internalMediaProxy,
|
mediaProxy: externalMediaProxy ?? internalMediaProxy,
|
||||||
|
mediaProxySecret: config.mediaProxySecret,
|
||||||
externalMediaProxyEnabled: externalMediaProxy !== null && externalMediaProxy !== internalMediaProxy,
|
externalMediaProxyEnabled: externalMediaProxy !== null && externalMediaProxy !== internalMediaProxy,
|
||||||
videoThumbnailGenerator: config.videoThumbnailGenerator ?
|
videoThumbnailGenerator: config.videoThumbnailGenerator ?
|
||||||
config.videoThumbnailGenerator.endsWith('/') ? config.videoThumbnailGenerator.substring(0, config.videoThumbnailGenerator.length - 1) : config.videoThumbnailGenerator
|
config.videoThumbnailGenerator.endsWith('/') ? config.videoThumbnailGenerator.substring(0, config.videoThumbnailGenerator.length - 1) : config.videoThumbnailGenerator
|
||||||
|
|
|
@ -249,10 +249,13 @@ export class ApRendererService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public renderImage(file: MiDriveFile): IApImage {
|
public renderImage(file: MiDriveFile, mode: 'avatar' | 'banner' = 'avatar'): IApImage {
|
||||||
return {
|
return {
|
||||||
type: 'Image',
|
type: 'Image',
|
||||||
url: this.driveFileEntityService.getPublicUrl(file),
|
url: this.driveFileEntityService.getProxiedUrl(
|
||||||
|
this.driveFileEntityService.getPublicUrl(file)
|
||||||
|
mode,
|
||||||
|
),
|
||||||
sensitive: file.isSensitive,
|
sensitive: file.isSensitive,
|
||||||
name: file.comment,
|
name: file.comment,
|
||||||
};
|
};
|
||||||
|
@ -517,8 +520,8 @@ export class ApRendererService {
|
||||||
summary: profile.description ? this.mfmService.toHtml(mfm.parse(profile.description)) : null,
|
summary: profile.description ? this.mfmService.toHtml(mfm.parse(profile.description)) : null,
|
||||||
_misskey_summary: profile.description,
|
_misskey_summary: profile.description,
|
||||||
_misskey_followedMessage: profile.followedMessage,
|
_misskey_followedMessage: profile.followedMessage,
|
||||||
icon: avatar ? this.renderImage(avatar) : null,
|
icon: avatar ? this.renderImage(avatar, 'avatar') : null,
|
||||||
image: banner ? this.renderImage(banner) : null,
|
image: banner ? this.renderImage(banner, 'banner') : null,
|
||||||
backgroundUrl: background ? this.renderImage(background) : null,
|
backgroundUrl: background ? this.renderImage(background) : null,
|
||||||
tag,
|
tag,
|
||||||
manuallyApprovesFollowers: user.isLocked,
|
manuallyApprovesFollowers: user.isLocked,
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import * as crypto from 'node:crypto';
|
||||||
import { forwardRef, Inject, Injectable } from '@nestjs/common';
|
import { forwardRef, Inject, Injectable } from '@nestjs/common';
|
||||||
import { In } from 'typeorm';
|
import { In } from 'typeorm';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
|
@ -74,12 +75,16 @@ export class DriveFileEntityService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@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(
|
return appendQuery(
|
||||||
`${this.config.mediaProxy}/${mode ?? 'image'}.webp`,
|
`${this.config.mediaProxy}/${mode ?? 'image'}.webp`,
|
||||||
query({
|
query({
|
||||||
url,
|
url,
|
||||||
...(mode ? { [mode]: '1' } : {}),
|
...(mode ? { [mode]: '1' } : {}),
|
||||||
|
sign: signature
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as fs from 'node:fs';
|
import * as fs from 'node:fs';
|
||||||
|
import * as crypto from 'node:crypto';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
import { dirname } from 'node:path';
|
import { dirname } from 'node:path';
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
@ -317,10 +318,13 @@ export class FileServerService {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!request.headers['user-agent']) {
|
const signature = crypto.createHmac('sha256', this.config.mediaProxySecret)
|
||||||
throw new StatusError('User-Agent is required', 400, 'User-Agent is required');
|
.update(url)
|
||||||
} else if (request.headers['user-agent'].toLowerCase().indexOf('misskey/') !== -1) {
|
.digest('hex')[8];
|
||||||
throw new StatusError('Refusing to proxy a request from another proxy', 403, 'Proxy is recursive');
|
|
||||||
|
if (signature !== request.params.sign) {
|
||||||
|
reply.code(403);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create temp file
|
// Create temp file
|
||||||
|
|
Loading…
Reference in a new issue