1
0
Fork 0
mirror of https://github.com/plankanban/planka.git synced 2025-07-18 12:49:43 +02:00

feat: Remove attachments from public access

Closes #219
This commit is contained in:
Maksim Eltyshev 2022-04-26 22:20:20 +05:00
parent 7ef55ec578
commit 7d138b858d
10 changed files with 156 additions and 19 deletions

View file

@ -57,7 +57,7 @@ COPY --from=client-builder /app/build/index.html views
VOLUME /app/public/user-avatars
VOLUME /app/public/project-background-images
VOLUME /app/public/attachments
VOLUME /app/private/attachments
EXPOSE 1337

View file

@ -15,7 +15,7 @@ services:
volumes:
- user-avatars:/app/public/user-avatars
- project-background-images:/app/public/project-background-images
- attachments:/app/public/attachments
- attachments:/app/private/attachments
ports:
- 3000:1337
environment:

8
server/.gitignore vendored
View file

@ -135,9 +135,11 @@ public/user-avatars/*
!public/project-background-images
public/project-background-images/*
!public/project-background-images/.gitkeep
!public/attachments
public/attachments/*
!public/attachments/.gitkeep
private/*
!private/attachments
private/attachments/*
!private/attachments/.gitkeep
views/*
!views/.gitkeep

View file

@ -0,0 +1,67 @@
const fs = require('fs');
const path = require('path');
const Errors = {
ATTACHMENT_NOT_FOUND: {
attachmentNotFound: 'Attachment not found',
},
};
module.exports = {
inputs: {
id: {
type: 'string',
regex: /^[0-9]+$/,
required: true,
},
filename: {
type: 'string',
required: true,
},
},
exits: {
attachmentNotFound: {
responseType: 'notFound',
},
},
async fn(inputs, exits) {
const { currentUser } = this.req;
const { attachment, card, project } = await sails.helpers.attachments
.getProjectPath(inputs.id)
.intercept('pathNotFound', () => Errors.ATTACHMENT_NOT_FOUND);
const isBoardMember = await sails.helpers.users.isBoardMember(currentUser.id, card.boardId);
if (!isBoardMember) {
const isProjectManager = await sails.helpers.users.isProjectManager(
currentUser.id,
project.id,
);
if (!isProjectManager) {
throw Errors.ATTACHMENT_NOT_FOUND; // Forbidden
}
}
if (!attachment.isImage) {
throw Errors.ATTACHMENT_NOT_FOUND;
}
const filePath = path.join(
sails.config.custom.attachmentsPath,
attachment.dirname,
'thumbnails',
inputs.filename,
);
if (!fs.existsSync(filePath)) {
throw Errors.ATTACHMENT_NOT_FOUND;
}
this.res.setHeader('Content-Disposition', `inline; ${inputs.filename}`);
return exits.success(fs.createReadStream(filePath));
},
};

View file

@ -0,0 +1,69 @@
const fs = require('fs');
const path = require('path');
const Errors = {
ATTACHMENT_NOT_FOUND: {
attachmentNotFound: 'Attachment not found',
},
};
module.exports = {
inputs: {
id: {
type: 'string',
regex: /^[0-9]+$/,
required: true,
},
filename: {
type: 'string',
required: true,
},
},
exits: {
attachmentNotFound: {
responseType: 'notFound',
},
},
async fn(inputs, exits) {
const { currentUser } = this.req;
const { attachment, card, project } = await sails.helpers.attachments
.getProjectPath(inputs.id)
.intercept('pathNotFound', () => Errors.ATTACHMENT_NOT_FOUND);
const isBoardMember = await sails.helpers.users.isBoardMember(currentUser.id, card.boardId);
if (!isBoardMember) {
const isProjectManager = await sails.helpers.users.isProjectManager(
currentUser.id,
project.id,
);
if (!isProjectManager) {
throw Errors.ATTACHMENT_NOT_FOUND; // Forbidden
}
}
const filePath = path.join(
sails.config.custom.attachmentsPath,
attachment.dirname,
attachment.filename,
);
if (!fs.existsSync(filePath)) {
throw Errors.ATTACHMENT_NOT_FOUND;
}
let contentDisposition;
if (attachment.isImage || path.extname(attachment.filename) === '.pdf') {
contentDisposition = 'inline';
} else {
contentDisposition = `attachment; ${inputs.filename}`;
}
this.res.setHeader('Content-Disposition', contentDisposition);
return exits.success(fs.createReadStream(filePath));
},
};

View file

@ -52,9 +52,9 @@ module.exports = {
customToJSON() {
return {
..._.omit(this, ['dirname', 'filename', 'isImage']),
url: `${sails.config.custom.attachmentsUrl}/${this.dirname}/${this.filename}`,
url: `${sails.config.custom.attachmentsUrl}/${this.id}/download/${this.filename}`,
coverUrl: this.isImage
? `${sails.config.custom.attachmentsUrl}/${this.dirname}/thumbnails/cover-256.jpg`
? `${sails.config.custom.attachmentsUrl}/${this.id}/download/thumbnails/cover-256.jpg`
: null,
};
},

View file

@ -26,6 +26,6 @@ module.exports.custom = {
projectBackgroundImagesPath: path.join(sails.config.paths.public, 'project-background-images'),
projectBackgroundImagesUrl: `${process.env.BASE_URL}/project-background-images`,
attachmentsPath: path.join(sails.config.paths.public, 'attachments'),
attachmentsPath: path.join(sails.config.appPath, 'private', 'attachments'),
attachmentsUrl: `${process.env.BASE_URL}/attachments`,
};

View file

@ -18,21 +18,10 @@ module.exports.policies = {
'*': 'is-authenticated',
// 'users/index': ['is-authenticated', 'is-admin'],
'users/create': ['is-authenticated', 'is-admin'],
'users/delete': ['is-authenticated', 'is-admin'],
'projects/create': ['is-authenticated', 'is-admin'],
// 'projects/update': ['is-authenticated', 'is-admin'],
// 'projects/update-background-image': ['is-authenticated', 'is-admin'],
// 'projects/delete': ['is-authenticated', 'is-admin'],
// 'project-memberships/create': ['is-authenticated', 'is-admin'],
// 'project-memberships/delete': ['is-authenticated', 'is-admin'],
// 'boards/create': ['is-authenticated', 'is-admin'],
// 'boards/update': ['is-authenticated', 'is-admin'],
// 'boards/delete': ['is-authenticated', 'is-admin'],
'access-tokens/create': true,
};

View file

@ -75,6 +75,16 @@ module.exports.routes = {
'GET /api/notifications/:id': 'notifications/show',
'PATCH /api/notifications/:ids': 'notifications/update',
'GET /attachments/:id/download/:filename': {
action: 'attachments/download',
skipAssets: false,
},
'GET /attachments/:id/download/thumbnails/:filename': {
action: 'attachments/download-thumbnail',
skipAssets: false,
},
'GET /*': {
view: 'index',
skipAssets: true,