mirror of
https://github.com/hexastack/hexabot
synced 2025-01-22 10:35:37 +00:00
feat: enforce security to access own attachment
This commit is contained in:
parent
c27f37a6e6
commit
4fac5d4fc9
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user