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 path from 'path';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
ForbiddenException,
|
||||||
Inject,
|
Inject,
|
||||||
Injectable,
|
Injectable,
|
||||||
NotFoundException,
|
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.
|
* 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 token The signed token used to verify and locate the attachment.
|
||||||
* @param req - The HTTP express request object.
|
* @param req - The HTTP express request object.
|
||||||
* @return A streamable file of the attachment.
|
* @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 {
|
try {
|
||||||
const {
|
const {
|
||||||
exp: _exp,
|
exp: _exp,
|
||||||
@ -336,6 +349,15 @@ export default abstract class ChannelHandler<
|
|||||||
...result
|
...result
|
||||||
} = this.jwtService.verify(token, this.jwtSignOptions);
|
} = this.jwtService.verify(token, this.jwtSignOptions);
|
||||||
const attachment = plainToClass(Attachment, result);
|
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);
|
return await this.attachmentService.download(attachment);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.logger.error('Failed to download attachment', err);
|
this.logger.error('Failed to download attachment', err);
|
||||||
|
@ -1345,4 +1345,44 @@ export default abstract class BaseWebChannelHandler<
|
|||||||
};
|
};
|
||||||
return subscriber;
|
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