diff --git a/api/src/channel/lib/Handler.ts b/api/src/channel/lib/Handler.ts index d1525758..037f64fd 100644 --- a/api/src/channel/lib/Handler.ts +++ b/api/src/channel/lib/Handler.ts @@ -9,6 +9,7 @@ import path from 'path'; import { + ForbiddenException, Inject, Injectable, NotFoundException, @@ -316,6 +317,19 @@ export default abstract class ChannelHandler< } } + /** + * Checks if the request is authorized to download a given attachment file. + * Can be overriden by the channel handler to customize, by default it shouldn't + * allow any client to download a subscriber attachment for example. + * + * @param attachment The attachment object + * @param req - The HTTP express request object. + * @return True, if requester is authorized to download the attachment + */ + public async hasDownloadAccess(attachment: Attachment, _req: Request) { + return attachment.access === 'public'; + } + /** * Downloads an attachment using a signed token. * @@ -326,9 +340,8 @@ export default abstract class ChannelHandler< * @param token The signed token used to verify and locate the attachment. * @param req - The HTTP express request object. * @return A streamable file of the attachment. - * @throws NotFoundException if the attachment cannot be found or the token is invalid. */ - public async download(token: string, _req: Request) { + public async download(token: string, req: Request) { try { const { exp: _exp, @@ -336,6 +349,15 @@ export default abstract class ChannelHandler< ...result } = this.jwtService.verify(token, this.jwtSignOptions); const attachment = plainToClass(Attachment, result); + + // Check access + const canDownload = await this.hasDownloadAccess(attachment, req); + if (!canDownload) { + throw new ForbiddenException( + 'You are not authorized to download the attachment', + ); + } + return await this.attachmentService.download(attachment); } catch (err) { this.logger.error('Failed to download attachment', err); diff --git a/api/src/extensions/channels/web/base-web-channel.ts b/api/src/extensions/channels/web/base-web-channel.ts index 454a2dd5..3e0f911b 100644 --- a/api/src/extensions/channels/web/base-web-channel.ts +++ b/api/src/extensions/channels/web/base-web-channel.ts @@ -1345,4 +1345,44 @@ export default abstract class BaseWebChannelHandler< }; return subscriber; } + + /** + * Checks if the request is authorized to download a given attachment file. + * + * @param attachment The attachment object + * @param req - The HTTP express request object. + * @return True, if requester is authorized to download the attachment + */ + public async hasDownloadAccess(attachment: Attachment, req: Request) { + const subscriberId = req.session?.web?.profile?.id as string; + if (attachment.access === 'public') { + return true; + } else if (!subscriberId) { + this.logger.warn( + `Unauthorized access attempt to attachment ${attachment.id}`, + ); + return false; + } else if ( + attachment.createdByRef === 'Subscriber' && + subscriberId === attachment.createdBy + ) { + // Either subscriber wants to access the attachment he sent + return true; + } else { + // Or, he would like to access an attachment sent to him privately + const message = await this.messageService.findOne({ + ['recipient' as any]: subscriberId, + $or: [ + { 'message.attachment.payload.id': attachment.id }, + { + 'message.attachment': { + $elemMatch: { 'payload.id': attachment.id }, + }, + }, + ], + }); + + return !!message; + } + } }