mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-10 05:29:08 +08:00
feat: improve attachment headers (#13709)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Safer, consistent file downloads with automatic attachment headers and filenames. - Smarter MIME detection for uploads (avatars, workspace blobs, Copilot files/transcripts). - Sensible default buffer limit when reading uploads. - **Bug Fixes** - Prevents risky content from rendering inline by forcing downloads and adding no‑sniff protection. - More accurate content types when original metadata is missing or incorrect. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -1,7 +1,11 @@
|
||||
import { Controller, Get, Param, Res } from '@nestjs/common';
|
||||
import type { Response } from 'express';
|
||||
|
||||
import { ActionForbidden, UserAvatarNotFound } from '../../base';
|
||||
import {
|
||||
ActionForbidden,
|
||||
applyAttachHeaders,
|
||||
UserAvatarNotFound,
|
||||
} from '../../base';
|
||||
import { Public } from '../auth/guard';
|
||||
import { AvatarStorage } from '../storage';
|
||||
|
||||
@@ -30,6 +34,10 @@ export class UserAvatarController {
|
||||
res.setHeader('last-modified', metadata.lastModified.toISOString());
|
||||
res.setHeader('content-length', metadata.contentLength);
|
||||
}
|
||||
applyAttachHeaders(res, {
|
||||
contentType: metadata?.contentType,
|
||||
filename: `${id}`,
|
||||
});
|
||||
|
||||
body.pipe(res);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@ import { isNil, omitBy } from 'lodash-es';
|
||||
import {
|
||||
CannotDeleteOwnAccount,
|
||||
type FileUpload,
|
||||
readBufferWithLimit,
|
||||
sniffMime,
|
||||
Throttle,
|
||||
UserNotFound,
|
||||
} from '../../base';
|
||||
@@ -98,20 +100,20 @@ export class UserResolver {
|
||||
@Args({ name: 'avatar', type: () => GraphQLUpload })
|
||||
avatar: FileUpload
|
||||
) {
|
||||
if (!avatar.mimetype.startsWith('image/')) {
|
||||
throw new Error('Invalid file type');
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
throw new UserNotFound();
|
||||
}
|
||||
|
||||
const avatarBuffer = await readBufferWithLimit(avatar.createReadStream());
|
||||
const contentType = sniffMime(avatarBuffer, avatar.mimetype);
|
||||
if (!contentType || !contentType.startsWith('image/')) {
|
||||
throw new Error(`Invalid file type: ${contentType || 'unknown'}`);
|
||||
}
|
||||
|
||||
const avatarUrl = await this.storage.put(
|
||||
`${user.id}-avatar-${Date.now()}`,
|
||||
avatar.createReadStream(),
|
||||
{
|
||||
contentType: avatar.mimetype,
|
||||
}
|
||||
avatarBuffer,
|
||||
{ contentType }
|
||||
);
|
||||
|
||||
if (user.avatarUrl) {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Controller, Get, Logger, Param, Query, Res } from '@nestjs/common';
|
||||
import type { Response } from 'express';
|
||||
|
||||
import {
|
||||
applyAttachHeaders,
|
||||
BlobNotFound,
|
||||
CallMetric,
|
||||
CommentAttachmentNotFound,
|
||||
@@ -83,6 +84,10 @@ export class WorkspacesController {
|
||||
} else {
|
||||
this.logger.warn(`Blob ${workspaceId}/${name} has no metadata`);
|
||||
}
|
||||
applyAttachHeaders(res, {
|
||||
contentType: metadata?.contentType,
|
||||
filename: name,
|
||||
});
|
||||
|
||||
res.setHeader('cache-control', 'public, max-age=2592000, immutable');
|
||||
body.pipe(res);
|
||||
@@ -215,6 +220,10 @@ export class WorkspacesController {
|
||||
`Comment attachment ${workspaceId}/${docId}/${key} has no metadata`
|
||||
);
|
||||
}
|
||||
applyAttachHeaders(res, {
|
||||
contentType: metadata?.contentType,
|
||||
filename: key,
|
||||
});
|
||||
|
||||
res.setHeader('cache-control', 'private, max-age=2592000, immutable');
|
||||
body.pipe(res);
|
||||
|
||||
Reference in New Issue
Block a user