refactor(media): store filenames, use pre-signed s3/azure URLs, UUIDs

Signed-off-by: Erik Michelson <github@erik.michelson.eu>
This commit is contained in:
Erik Michelson 2024-06-12 18:45:49 +02:00 committed by Philip Molares
parent 4132833b5d
commit 157a0fe278
47 changed files with 869 additions and 389 deletions

View file

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
@ -100,28 +100,33 @@ export class MediaController {
'uploadMedia',
);
}
const upload = await this.mediaService.saveFile(file.buffer, user, note);
const upload = await this.mediaService.saveFile(
file.originalname,
file.buffer,
user,
note,
);
return await this.mediaService.toMediaUploadDto(upload);
}
@Get(':filename')
@OpenApi(404, 500)
@Get(':uuid')
@OpenApi(200, 404, 500)
async getMedia(
@Param('filename') filename: string,
@Param('uuid') uuid: string,
@Res() response: Response,
): Promise<void> {
const mediaUpload = await this.mediaService.findUploadByFilename(filename);
const targetUrl = mediaUpload.fileUrl;
response.redirect(targetUrl);
const mediaUpload = await this.mediaService.findUploadByUuid(uuid);
const dto = await this.mediaService.toMediaUploadDto(mediaUpload);
response.send(dto);
}
@Delete(':filename')
@Delete(':uuid')
@OpenApi(204, 403, 404, 500)
async deleteMedia(
@RequestUser() user: User,
@Param('filename') filename: string,
@Param('uuid') uuid: string,
): Promise<void> {
const mediaUpload = await this.mediaService.findUploadByFilename(filename);
const mediaUpload = await this.mediaService.findUploadByUuid(uuid);
if (
await this.permissionsService.checkMediaDeletePermission(
user,
@ -129,18 +134,18 @@ export class MediaController {
)
) {
this.logger.debug(
`Deleting '${filename}' for user '${user.username}'`,
`Deleting '${uuid}' for user '${user.username}'`,
'deleteMedia',
);
await this.mediaService.deleteFile(mediaUpload);
} else {
this.logger.warn(
`${user.username} tried to delete '${filename}', but is not the owner of upload or connected note`,
`${user.username} tried to delete '${uuid}', but is not the owner of upload or connected note`,
'deleteMedia',
);
const mediaUploadNote = await mediaUpload.note;
throw new PermissionError(
`Neither file '${filename}' nor note '${
`Neither file '${uuid}' nor note '${
mediaUploadNote?.publicId ?? 'unknown'
}'is owned by '${user.username}'`,
);

View file

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
@ -100,28 +100,33 @@ export class MediaController {
`Received filename '${file.originalname}' for note '${note.publicId}' from user '${user.username}'`,
'uploadMedia',
);
const upload = await this.mediaService.saveFile(file.buffer, user, note);
const upload = await this.mediaService.saveFile(
file.originalname,
file.buffer,
user,
note,
);
return await this.mediaService.toMediaUploadDto(upload);
}
@Get(':filename')
@OpenApi(404, 500)
@Get(':uuid')
@OpenApi(200, 404, 500)
async getMedia(
@Param('filename') filename: string,
@Param('uuid') uuid: string,
@Res() response: Response,
): Promise<void> {
const mediaUpload = await this.mediaService.findUploadByFilename(filename);
const targetUrl = mediaUpload.fileUrl;
response.redirect(targetUrl);
const mediaUpload = await this.mediaService.findUploadByUuid(uuid);
const dto = await this.mediaService.toMediaUploadDto(mediaUpload);
response.send(dto);
}
@Delete(':filename')
@Delete(':uuid')
@OpenApi(204, 403, 404, 500)
async deleteMedia(
@RequestUser() user: User,
@Param('filename') filename: string,
@Param('uuid') uuid: string,
): Promise<void> {
const mediaUpload = await this.mediaService.findUploadByFilename(filename);
const mediaUpload = await this.mediaService.findUploadByUuid(uuid);
if (
await this.permissionsService.checkMediaDeletePermission(
user,
@ -129,18 +134,18 @@ export class MediaController {
)
) {
this.logger.debug(
`Deleting '${filename}' for user '${user.username}'`,
`Deleting '${uuid}' for user '${user.username}'`,
'deleteMedia',
);
await this.mediaService.deleteFile(mediaUpload);
} else {
this.logger.warn(
`${user.username} tried to delete '${filename}', but is not the owner of upload or connected note`,
`${user.username} tried to delete '${uuid}', but is not the owner of upload or connected note`,
'deleteMedia',
);
const mediaUploadNote = await mediaUpload.note;
throw new PermissionError(
`Neither file '${filename}' nor note '${
`Neither file '${uuid}' nor note '${
mediaUploadNote?.publicId ?? 'unknown'
}'is owned by '${user.username}'`,
);

View file

@ -1,10 +1,12 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
export const okDescription = 'This request was successful';
export const foundDescription =
'The requested resource was found at another URL';
export const createdDescription =
'The requested resource was successfully created';
export const noContentDescription =

View file

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
@ -8,6 +8,7 @@ import {
ApiBadRequestResponse,
ApiConflictResponse,
ApiCreatedResponse,
ApiFoundResponse,
ApiInternalServerErrorResponse,
ApiNoContentResponse,
ApiNotFoundResponse,
@ -21,6 +22,7 @@ import {
badRequestDescription,
conflictDescription,
createdDescription,
foundDescription,
internalServerErrorDescription,
noContentDescription,
notFoundDescription,
@ -33,6 +35,7 @@ export type HttpStatusCodes =
| 200
| 201
| 204
| 302
| 400
| 401
| 403
@ -130,6 +133,14 @@ export const OpenApi = (
HttpCode(204),
);
break;
case 302:
decoratorsToApply.push(
ApiFoundResponse({
description: description ?? foundDescription,
}),
HttpCode(302),
);
break;
case 400:
decoratorsToApply.push(
ApiBadRequestResponse({