From 8bf85e2eb6a1d2dfb3a93d8bcf1235a4fd368e78 Mon Sep 17 00:00:00 2001 From: Maksim Eltyshev Date: Fri, 22 Apr 2022 16:21:09 +0500 Subject: [PATCH 01/10] ref: Rename .env file to sample, stop tracking .env file --- Dockerfile | 4 +++- README.md | 2 +- server/{.env => .env.sample} | 0 server/.gitignore | 1 + 4 files changed, 5 insertions(+), 2 deletions(-) rename server/{.env => .env.sample} (100%) diff --git a/Dockerfile b/Dockerfile index a86c447..062221c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -47,9 +47,11 @@ RUN apk -U upgrade \ && apk del vips-dependencies --purge COPY docker-start.sh start.sh -RUN chmod +x start.sh COPY server . +RUN chmod +x start.sh \ + && cp .env.sample .env + COPY --from=client-builder /app/build public COPY --from=client-builder /app/build/index.html views diff --git a/README.md b/README.md index 5bbf475..e4971c9 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ Either use a local database or start the provided development database: docker-compose -f docker-compose-dev.yml up ``` -Edit `DATABASE_URL` in `.env` file if needed, then initialize the database: +Create `server/.env` based on `server/.env.sample` and edit `DATABASE_URL` if needed, then initialize the database: ``` npm run server:db:init diff --git a/server/.env b/server/.env.sample similarity index 100% rename from server/.env rename to server/.env.sample diff --git a/server/.gitignore b/server/.gitignore index f812e2e..39fabc1 100644 --- a/server/.gitignore +++ b/server/.gitignore @@ -44,6 +44,7 @@ # ################################################ +.env config/local.js ################################################ From f03bb5a78f42d46c8b199a3fa0d061864e9bf714 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20F=C3=BCrnschu=C3=9F?= Date: Mon, 25 Apr 2022 12:45:24 +0200 Subject: [PATCH 02/10] feat: Update German translation (#228) --- client/src/locales/de/core.js | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/client/src/locales/de/core.js b/client/src/locales/de/core.js index 75b73b5..e2a350a 100644 --- a/client/src/locales/de/core.js +++ b/client/src/locales/de/core.js @@ -17,6 +17,7 @@ export default { actions: 'Aktionen', addAttachment_title: 'Anhang hinzufügen', addComment: 'Kommentar hinzufügen', + addManager_title: 'Manager hinzufügen', addMember_title: 'Mitglied hinzufügen', addUser_title: 'Benutzer hinzufügen', administrator: 'Administrator', @@ -36,11 +37,16 @@ export default { areYouSureYouWantToDeleteThisTask: 'Sind Sie sicher, dass Sie diese Aufgabe löschen möchten?', areYouSureYouWantToDeleteThisUser: 'Sind Sie sicher, dass Sie diesen Benutzer löschen möchten?', - areYouSureYouWantToRemoveThisMemberFromProject: - 'Sind Sie sicher, dass Sie dieses Mitglied aus dem Projekt entfernen möchten??', + areYouSureYouWantToLeaveBoard: 'Sind Sie sicher, dass Sie das Board verlassen möchten?', + areYouSureYouWantToLeaveProject: 'Sind Sie sicher, dass Sie das Projekt verlassen möchten?', + areYouSureYouWantToRemoveThisManagerFromProject: + 'Sind Sie sicher, dass Sie diesen Manager aus dem Projekt entfernen möchten?', + areYouSureYouWantToRemoveThisMemberFromBoard: + 'Sind Sie sicher, dass Sie dieses Mitglied aus dem Board entfernen möchten?', attachment: 'Anhang', attachments: 'Anhänge', authentication: 'Authentifizierung', + background: 'Hintergrund', board: 'Board', boardNotFound_title: 'Board nicht gefunden', cardActions_title: 'Kartenaktionen', @@ -53,6 +59,7 @@ export default { createProject_title: 'Projekt erstellen', createTextFile_title: 'Textdatei erstellen', currentPassword: 'Derzeitiges Password', + dangerZone_title: 'Gefährlicher Bereich', date: 'Datum', dueDate_title: 'Fälligkeitsdatum', deleteAttachment_title: 'Anhang löschen', @@ -86,11 +93,15 @@ export default { filterByLabels_title: 'Nach Label filtern', filterByMembers_title: 'Nach Mitgliedern filtern', fromComputer_title: 'Vom Computer', + general: 'Allgemein', hours: 'Stunden', invalidCurrentPassword: 'Das aktuelle Passwort ist falsch', labels: 'Labels', + leaveBoard_title: 'Board verlassen', + leaveProject_title: 'Projekt verlassen', list: 'Listen', listActions_title: 'Aufgaben auflisten', + managers: 'Manager', members: 'Mitglieder', minutes: 'Minuten', moveCard_title: 'Karte verschieben', @@ -113,6 +124,7 @@ export default { 'Tipp: Drücken Sie STRG-V (Cmd-V auf Mac), um einen Anhang aus der Zwischenablage hinzuzufügen.', project: 'Projekt', projectNotFound_title: 'Projekt nicht gefunden', + removeManager_title: 'Manager entfernen', removeMember_title: 'Mitglied entfernen', seconds: 'Sekunden', selectBoard: 'Board auswählen', @@ -180,6 +192,8 @@ export default { editTimer_title: 'Timer bearbeiten', editTitle_title: 'Titel bearbeiten', editUsername_title: 'Benutzername ändern', + leaveBoard: 'Board verlassen', + leaveProject: 'Projekt verlassen', logOut_title: 'Ausloggen', makeCover_title: 'als Vorschau festlegen', move: 'Verschieben', @@ -187,7 +201,9 @@ export default { remove: 'Löschen', removeBackground: 'Hintergrund löschen', removeCover_title: 'Vorschau löschen', + removeFromBoard: 'Vom Board entfernen', removeFromProject: 'Vom Projekt entfernen', + removeManager: 'Manager entfernen', removeMember: 'Mitglied entfernen', save: 'Speichern', showAllAttachments: 'Alle Anhänge anzeigen ({{hidden}} versteckt)', From 536064fdfeaac88889292846bc2041b9f35afd43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20F=C3=BCrnschu=C3=9F?= Date: Mon, 25 Apr 2022 13:00:14 +0200 Subject: [PATCH 03/10] fix: Fix missed table name (#227) --- .../migrations/20180721234154_create_project_manager_table.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/db/migrations/20180721234154_create_project_manager_table.js b/server/db/migrations/20180721234154_create_project_manager_table.js index b975742..1410416 100755 --- a/server/db/migrations/20180721234154_create_project_manager_table.js +++ b/server/db/migrations/20180721234154_create_project_manager_table.js @@ -16,4 +16,4 @@ module.exports.up = (knex) => table.index('user_id'); }); -module.exports.down = (knex) => knex.schema.dropTable('project_membership'); +module.exports.down = (knex) => knex.schema.dropTable('project_manager'); From 486e663a3d1dab7bb6be1bbda0d853becc1535ba Mon Sep 17 00:00:00 2001 From: Maksim Eltyshev Date: Tue, 26 Apr 2022 18:01:55 +0500 Subject: [PATCH 04/10] feat: Store accessToken in cookies instead of localStorage --- client/package-lock.json | 14 ++++++++++++++ client/package.json | 1 + client/src/api/access-tokens.js | 2 +- client/src/api/actions.js | 4 ++-- client/src/api/attachments.js | 12 ++++++------ client/src/api/board-memberships.js | 7 +++---- client/src/api/boards.js | 11 +++++------ client/src/api/card-labels.js | 6 ++---- client/src/api/card-memberships.js | 7 +++---- client/src/api/cards.js | 20 ++++++++++---------- client/src/api/comment-actions.js | 12 ++++++------ client/src/api/http.js | 4 ++-- client/src/api/labels.js | 7 +++---- client/src/api/lists.js | 7 +++---- client/src/api/notifications.js | 11 +++++------ client/src/api/project-managers.js | 7 +++---- client/src/api/projects.js | 14 +++++++------- client/src/api/socket.js | 3 +-- client/src/api/tasks.js | 6 +++--- client/src/api/users.js | 22 ++++++++++------------ client/src/constants/Config.js | 14 +++++++++++++- client/src/reducers/auth.js | 7 ------- client/src/sagas/core/request.js | 9 ++------- client/src/sagas/index.js | 6 +++--- client/src/utils/access-token-storage.js | 23 +++++++++++++++++++---- server/api/hooks/current-user/index.js | 13 +++++++++---- server/config/security.js | 2 +- 27 files changed, 137 insertions(+), 114 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index ad85a35..228f268 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -14,6 +14,7 @@ "i18next": "^21.6.16", "i18next-browser-languagedetector": "^6.1.4", "initials": "^3.1.2", + "js-cookie": "^3.0.1", "lodash": "^4.17.20", "node-sass": "^7.0.1", "prop-types": "^15.8.1", @@ -13534,6 +13535,14 @@ "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.6.4.tgz", "integrity": "sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==" }, + "node_modules/js-cookie": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.1.tgz", + "integrity": "sha512-+0rgsUXZu4ncpPxRL+lNEptWMOWl9etvPHc/koSRp6MPwpRYAhmk0dUG00J4bxVV3r9uUzfo24wW0knS07SKSw==", + "engines": { + "node": ">=12" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -33059,6 +33068,11 @@ "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.6.4.tgz", "integrity": "sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==" }, + "js-cookie": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.1.tgz", + "integrity": "sha512-+0rgsUXZu4ncpPxRL+lNEptWMOWl9etvPHc/koSRp6MPwpRYAhmk0dUG00J4bxVV3r9uUzfo24wW0knS07SKSw==" + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", diff --git a/client/package.json b/client/package.json index 6f45810..cb2c3e8 100755 --- a/client/package.json +++ b/client/package.json @@ -71,6 +71,7 @@ "i18next": "^21.6.16", "i18next-browser-languagedetector": "^6.1.4", "initials": "^3.1.2", + "js-cookie": "^3.0.1", "lodash": "^4.17.20", "node-sass": "^7.0.1", "prop-types": "^15.8.1", diff --git a/client/src/api/access-tokens.js b/client/src/api/access-tokens.js index 6cd1063..0e5c988 100755 --- a/client/src/api/access-tokens.js +++ b/client/src/api/access-tokens.js @@ -2,7 +2,7 @@ import http from './http'; /* Actions */ -const createAccessToken = (data, headers) => http.post('/access-tokens', data, headers); +const createAccessToken = (data) => http.post('/access-tokens', data); export default { createAccessToken, diff --git a/client/src/api/actions.js b/client/src/api/actions.js index 63fa549..cc1d6c0 100755 --- a/client/src/api/actions.js +++ b/client/src/api/actions.js @@ -9,8 +9,8 @@ export const transformAction = (action) => ({ /* Actions */ -const getActions = (cardId, data, headers) => - socket.get(`/cards/${cardId}/actions`, data, headers).then((body) => ({ +const getActions = (cardId, data) => + socket.get(`/cards/${cardId}/actions`, data).then((body) => ({ ...body, items: body.items.map(transformAction), })); diff --git a/client/src/api/attachments.js b/client/src/api/attachments.js index f3edba4..8ed6472 100755 --- a/client/src/api/attachments.js +++ b/client/src/api/attachments.js @@ -10,20 +10,20 @@ export const transformAttachment = (attachment) => ({ /* Actions */ -const createAttachment = (cardId, data, requestId, headers) => - http.post(`/cards/${cardId}/attachments?requestId=${requestId}`, data, headers).then((body) => ({ +const createAttachment = (cardId, data, requestId) => + http.post(`/cards/${cardId}/attachments?requestId=${requestId}`, data).then((body) => ({ ...body, item: transformAttachment(body.item), })); -const updateAttachment = (id, data, headers) => - socket.patch(`/attachments/${id}`, data, headers).then((body) => ({ +const updateAttachment = (id, data) => + socket.patch(`/attachments/${id}`, data).then((body) => ({ ...body, item: transformAttachment(body.item), })); -const deleteAttachment = (id, headers) => - socket.delete(`/attachments/${id}`, undefined, headers).then((body) => ({ +const deleteAttachment = (id) => + socket.delete(`/attachments/${id}`).then((body) => ({ ...body, item: transformAttachment(body.item), })); diff --git a/client/src/api/board-memberships.js b/client/src/api/board-memberships.js index 3ef1ce8..0fa087b 100644 --- a/client/src/api/board-memberships.js +++ b/client/src/api/board-memberships.js @@ -2,11 +2,10 @@ import socket from './socket'; /* Actions */ -const createBoardMembership = (boardId, data, headers) => - socket.post(`/boards/${boardId}/memberships`, data, headers); +const createBoardMembership = (boardId, data) => + socket.post(`/boards/${boardId}/memberships`, data); -const deleteBoardMembership = (id, headers) => - socket.delete(`/board-memberships/${id}`, undefined, headers); +const deleteBoardMembership = (id) => socket.delete(`/board-memberships/${id}`); export default { createBoardMembership, diff --git a/client/src/api/boards.js b/client/src/api/boards.js index b605ed2..4e618ce 100755 --- a/client/src/api/boards.js +++ b/client/src/api/boards.js @@ -4,11 +4,10 @@ import { transformAttachment } from './attachments'; /* Actions */ -const createBoard = (projectId, data, headers) => - socket.post(`/projects/${projectId}/boards`, data, headers); +const createBoard = (projectId, data) => socket.post(`/projects/${projectId}/boards`, data); -const getBoard = (id, headers) => - socket.get(`/boards/${id}`, undefined, headers).then((body) => ({ +const getBoard = (id) => + socket.get(`/boards/${id}`).then((body) => ({ ...body, included: { ...body.included, @@ -17,9 +16,9 @@ const getBoard = (id, headers) => }, })); -const updateBoard = (id, data, headers) => socket.patch(`/boards/${id}`, data, headers); +const updateBoard = (id, data) => socket.patch(`/boards/${id}`, data); -const deleteBoard = (id, headers) => socket.delete(`/boards/${id}`, undefined, headers); +const deleteBoard = (id) => socket.delete(`/boards/${id}`); export default { createBoard, diff --git a/client/src/api/card-labels.js b/client/src/api/card-labels.js index 92715d6..d4fc0fa 100644 --- a/client/src/api/card-labels.js +++ b/client/src/api/card-labels.js @@ -2,11 +2,9 @@ import socket from './socket'; /* Actions */ -const createCardLabel = (cardId, data, headers) => - socket.post(`/cards/${cardId}/labels`, data, headers); +const createCardLabel = (cardId, data) => socket.post(`/cards/${cardId}/labels`, data); -const deleteCardLabel = (cardId, labelId, headers) => - socket.delete(`/cards/${cardId}/labels/${labelId}`, undefined, headers); +const deleteCardLabel = (cardId, labelId) => socket.delete(`/cards/${cardId}/labels/${labelId}`); export default { createCardLabel, diff --git a/client/src/api/card-memberships.js b/client/src/api/card-memberships.js index 2deb9d9..2333030 100644 --- a/client/src/api/card-memberships.js +++ b/client/src/api/card-memberships.js @@ -2,11 +2,10 @@ import socket from './socket'; /* Actions */ -const createCardMembership = (cardId, data, headers) => - socket.post(`/cards/${cardId}/memberships`, data, headers); +const createCardMembership = (cardId, data) => socket.post(`/cards/${cardId}/memberships`, data); -const deleteCardMembership = (cardId, userId, headers) => - socket.delete(`/cards/${cardId}/memberships?userId=${userId}`, undefined, headers); +const deleteCardMembership = (cardId, userId) => + socket.delete(`/cards/${cardId}/memberships?userId=${userId}`); export default { createCardMembership, diff --git a/client/src/api/cards.js b/client/src/api/cards.js index b2f3f2c..353f8f7 100755 --- a/client/src/api/cards.js +++ b/client/src/api/cards.js @@ -35,8 +35,8 @@ export const transformCardData = (data) => ({ /* Actions */ -const getCards = (boardId, data, headers) => - socket.get(`/board/${boardId}/cards`, data, headers).then((body) => ({ +const getCards = (boardId, data) => + socket.get(`/board/${boardId}/cards`, data).then((body) => ({ ...body, items: body.items.map(transformCard), included: { @@ -45,26 +45,26 @@ const getCards = (boardId, data, headers) => }, })); -const createCard = (boardId, data, headers) => - socket.post(`/boards/${boardId}/cards`, transformCardData(data), headers).then((body) => ({ +const createCard = (boardId, data) => + socket.post(`/boards/${boardId}/cards`, transformCardData(data)).then((body) => ({ ...body, item: transformCard(body.item), })); -const getCard = (id, headers) => - socket.get(`/cards/${id}`, undefined, headers).then((body) => ({ +const getCard = (id) => + socket.get(`/cards/${id}`).then((body) => ({ ...body, item: transformCard(body.item), })); -const updateCard = (id, data, headers) => - socket.patch(`/cards/${id}`, transformCardData(data), headers).then((body) => ({ +const updateCard = (id, data) => + socket.patch(`/cards/${id}`, transformCardData(data)).then((body) => ({ ...body, item: transformCard(body.item), })); -const deleteCard = (id, headers) => - socket.delete(`/cards/${id}`, undefined, headers).then((body) => ({ +const deleteCard = (id) => + socket.delete(`/cards/${id}`).then((body) => ({ ...body, item: transformCard(body.item), })); diff --git a/client/src/api/comment-actions.js b/client/src/api/comment-actions.js index bce8eb7..40f67db 100755 --- a/client/src/api/comment-actions.js +++ b/client/src/api/comment-actions.js @@ -3,20 +3,20 @@ import { transformAction } from './actions'; /* Actions */ -const createCommentAction = (cardId, data, headers) => - socket.post(`/cards/${cardId}/comment-actions`, data, headers).then((body) => ({ +const createCommentAction = (cardId, data) => + socket.post(`/cards/${cardId}/comment-actions`, data).then((body) => ({ ...body, item: transformAction(body.item), })); -const updateCommentAction = (id, data, headers) => - socket.patch(`/comment-actions/${id}`, data, headers).then((body) => ({ +const updateCommentAction = (id, data) => + socket.patch(`/comment-actions/${id}`, data).then((body) => ({ ...body, item: transformAction(body.item), })); -const deleteCommentAction = (id, headers) => - socket.delete(`/comment-actions/${id}`, undefined, headers).then((body) => ({ +const deleteCommentAction = (id) => + socket.delete(`/comment-actions/${id}`).then((body) => ({ ...body, item: transformAction(body.item), })); diff --git a/client/src/api/http.js b/client/src/api/http.js index 646528d..9776807 100755 --- a/client/src/api/http.js +++ b/client/src/api/http.js @@ -6,7 +6,7 @@ const http = {}; // TODO: add all methods ['POST'].forEach((method) => { - http[method.toLowerCase()] = (url, data, headers) => { + http[method.toLowerCase()] = (url, data) => { const formData = Object.keys(data).reduce((result, key) => { result.append(key, data[key]); @@ -15,8 +15,8 @@ const http = {}; return fetch(`${Config.SERVER_BASE_URL}/api${url}`, { method, - headers, body: formData, + ...Config.FETCH_OPTIONS, }) .then((response) => response.json().then((body) => ({ diff --git a/client/src/api/labels.js b/client/src/api/labels.js index e1101ef..80d9201 100755 --- a/client/src/api/labels.js +++ b/client/src/api/labels.js @@ -2,12 +2,11 @@ import socket from './socket'; /* Actions */ -const createLabel = (boardId, data, headers) => - socket.post(`/boards/${boardId}/labels`, data, headers); +const createLabel = (boardId, data) => socket.post(`/boards/${boardId}/labels`, data); -const updateLabel = (id, data, headers) => socket.patch(`/labels/${id}`, data, headers); +const updateLabel = (id, data) => socket.patch(`/labels/${id}`, data); -const deleteLabel = (id, headers) => socket.delete(`/labels/${id}`, undefined, headers); +const deleteLabel = (id) => socket.delete(`/labels/${id}`); export default { createLabel, diff --git a/client/src/api/lists.js b/client/src/api/lists.js index 6c1ee6a..59722ed 100755 --- a/client/src/api/lists.js +++ b/client/src/api/lists.js @@ -2,12 +2,11 @@ import socket from './socket'; /* Actions */ -const createList = (boardId, data, headers) => - socket.post(`/boards/${boardId}/lists`, data, headers); +const createList = (boardId, data) => socket.post(`/boards/${boardId}/lists`, data); -const updateList = (id, data, headers) => socket.patch(`/lists/${id}`, data, headers); +const updateList = (id, data) => socket.patch(`/lists/${id}`, data); -const deleteList = (id, headers) => socket.delete(`/lists/${id}`, undefined, headers); +const deleteList = (id) => socket.delete(`/lists/${id}`); export default { createList, diff --git a/client/src/api/notifications.js b/client/src/api/notifications.js index 9068a19..246165f 100755 --- a/client/src/api/notifications.js +++ b/client/src/api/notifications.js @@ -4,8 +4,8 @@ import { transformAction } from './actions'; /* Actions */ -const getNotifications = (headers) => - socket.get('/notifications', undefined, headers).then((body) => ({ +const getNotifications = () => + socket.get('/notifications').then((body) => ({ ...body, included: { ...body.included, @@ -14,8 +14,8 @@ const getNotifications = (headers) => }, })); -const getNotification = (id, headers) => - socket.get(`/notifications/${id}`, undefined, headers).then((body) => ({ +const getNotification = (id) => + socket.get(`/notifications/${id}`).then((body) => ({ ...body, included: { ...body.included, @@ -24,8 +24,7 @@ const getNotification = (id, headers) => }, })); -const updateNotifications = (ids, data, headers) => - socket.patch(`/notifications/${ids.join(',')}`, data, headers); +const updateNotifications = (ids, data) => socket.patch(`/notifications/${ids.join(',')}`, data); export default { getNotifications, diff --git a/client/src/api/project-managers.js b/client/src/api/project-managers.js index a835bf2..6701a76 100755 --- a/client/src/api/project-managers.js +++ b/client/src/api/project-managers.js @@ -2,11 +2,10 @@ import socket from './socket'; /* Actions */ -const createProjectManager = (projectId, data, headers) => - socket.post(`/projects/${projectId}/managers`, data, headers); +const createProjectManager = (projectId, data) => + socket.post(`/projects/${projectId}/managers`, data); -const deleteProjectManager = (id, headers) => - socket.delete(`/project-managers/${id}`, undefined, headers); +const deleteProjectManager = (id) => socket.delete(`/project-managers/${id}`); export default { createProjectManager, diff --git a/client/src/api/projects.js b/client/src/api/projects.js index 5a22bee..863d3f5 100755 --- a/client/src/api/projects.js +++ b/client/src/api/projects.js @@ -3,18 +3,18 @@ import socket from './socket'; /* Actions */ -const getProjects = (headers) => socket.get('/projects', undefined, headers); +const getProjects = () => socket.get('/projects'); -const createProject = (data, headers) => socket.post('/projects', data, headers); +const createProject = (data) => socket.post('/projects', data); -const getProject = (id, headers) => socket.get(`/projects/${id}`, undefined, headers); +const getProject = (id) => socket.get(`/projects/${id}`); -const updateProject = (id, data, headers) => socket.patch(`/projects/${id}`, data, headers); +const updateProject = (id, data) => socket.patch(`/projects/${id}`, data); -const updateProjectBackgroundImage = (id, data, headers) => - http.post(`/projects/${id}/background-image`, data, headers); +const updateProjectBackgroundImage = (id, data) => + http.post(`/projects/${id}/background-image`, data); -const deleteProject = (id, headers) => socket.delete(`/projects/${id}`, undefined, headers); +const deleteProject = (id) => socket.delete(`/projects/${id}`); export default { getProjects, diff --git a/client/src/api/socket.js b/client/src/api/socket.js index 2902661..8e4aa7a 100755 --- a/client/src/api/socket.js +++ b/client/src/api/socket.js @@ -16,13 +16,12 @@ const { socket } = io; socket.connect = socket._connect; // eslint-disable-line no-underscore-dangle ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'].forEach((method) => { - socket[method.toLowerCase()] = (url, data, headers) => + socket[method.toLowerCase()] = (url, data) => new Promise((resolve, reject) => { socket.request( { method, data, - headers, url: `/api${url}`, }, (_, { body, error }) => { diff --git a/client/src/api/tasks.js b/client/src/api/tasks.js index 584ec3e..2b07896 100755 --- a/client/src/api/tasks.js +++ b/client/src/api/tasks.js @@ -2,11 +2,11 @@ import socket from './socket'; /* Actions */ -const createTask = (cardId, data, headers) => socket.post(`/cards/${cardId}/tasks`, data, headers); +const createTask = (cardId, data) => socket.post(`/cards/${cardId}/tasks`, data); -const updateTask = (id, data, headers) => socket.patch(`/tasks/${id}`, data, headers); +const updateTask = (id, data) => socket.patch(`/tasks/${id}`, data); -const deleteTask = (id, headers) => socket.delete(`/tasks/${id}`, undefined, headers); +const deleteTask = (id) => socket.delete(`/tasks/${id}`); export default { createTask, diff --git a/client/src/api/users.js b/client/src/api/users.js index ca77bd9..cb286e8 100755 --- a/client/src/api/users.js +++ b/client/src/api/users.js @@ -3,27 +3,25 @@ import socket from './socket'; /* Actions */ -const getUsers = (headers) => socket.get('/users', undefined, headers); +const getUsers = () => socket.get('/users'); -const createUser = (data, headers) => socket.post('/users', data, headers); +const createUser = (data) => socket.post('/users', data); -const getUser = (id, headers) => socket.get(`/users/${id}`, undefined, headers); +const getUser = (id) => socket.get(`/users/${id}`); -const getCurrentUser = (headers) => socket.get('/users/me', undefined, headers); +const getCurrentUser = () => socket.get('/users/me'); -const updateUser = (id, data, headers) => socket.patch(`/users/${id}`, data, headers); +const updateUser = (id, data) => socket.patch(`/users/${id}`, data); -const updateUserEmail = (id, data, headers) => socket.patch(`/users/${id}/email`, data, headers); +const updateUserEmail = (id, data) => socket.patch(`/users/${id}/email`, data); -const updateUserPassword = (id, data, headers) => - socket.patch(`/users/${id}/password`, data, headers); +const updateUserPassword = (id, data) => socket.patch(`/users/${id}/password`, data); -const updateUserUsername = (id, data, headers) => - socket.patch(`/users/${id}/username`, data, headers); +const updateUserUsername = (id, data) => socket.patch(`/users/${id}/username`, data); -const updateUserAvatar = (id, data, headers) => http.post(`/users/${id}/avatar`, data, headers); +const updateUserAvatar = (id, data) => http.post(`/users/${id}/avatar`, data); -const deleteUser = (id, headers) => socket.delete(`/users/${id}`, undefined, headers); +const deleteUser = (id) => socket.delete(`/users/${id}`); export default { getUsers, diff --git a/client/src/constants/Config.js b/client/src/constants/Config.js index 5bf8722..ff7111a 100755 --- a/client/src/constants/Config.js +++ b/client/src/constants/Config.js @@ -2,12 +2,24 @@ const SERVER_BASE_URL = process.env.REACT_APP_SERVER_BASE_URL || (process.env.NODE_ENV === 'production' ? '' : 'http://localhost:1337'); -const POSITION_GAP = 65535; +const FETCH_OPTIONS = + process.env.NODE_ENV === 'production' + ? undefined + : { + credentials: 'include', + }; + +const ACCESS_TOKEN_KEY = 'accessToken'; +const ACCESS_TOKEN_EXPIRES = 365; +const POSITION_GAP = 65535; const ACTIONS_LIMIT = 10; export default { SERVER_BASE_URL, + FETCH_OPTIONS, + ACCESS_TOKEN_KEY, + ACCESS_TOKEN_EXPIRES, POSITION_GAP, ACTIONS_LIMIT, }; diff --git a/client/src/reducers/auth.js b/client/src/reducers/auth.js index af88015..d5b3c3c 100755 --- a/client/src/reducers/auth.js +++ b/client/src/reducers/auth.js @@ -1,19 +1,12 @@ -import { getAccessToken } from '../utils/access-token-storage'; import ActionTypes from '../constants/ActionTypes'; const initialState = { - accessToken: getAccessToken(), userId: null, }; // eslint-disable-next-line default-param-last export default (state = initialState, { type, payload }) => { switch (type) { - case ActionTypes.AUTHENTICATE__SUCCESS: - return { - ...state, - accessToken: payload.accessToken, - }; case ActionTypes.SOCKET_RECONNECT_HANDLE: case ActionTypes.CORE_INITIALIZE: return { diff --git a/client/src/sagas/core/request.js b/client/src/sagas/core/request.js index 3021d32..7cc2e40 100755 --- a/client/src/sagas/core/request.js +++ b/client/src/sagas/core/request.js @@ -1,6 +1,5 @@ -import { call, fork, join, put, select, take } from 'redux-saga/effects'; +import { call, fork, join, put, take } from 'redux-saga/effects'; -import { accessTokenSelector } from '../../selectors'; import { logout } from '../../actions'; import ErrorCodes from '../../constants/ErrorCodes'; @@ -13,12 +12,8 @@ function* queueRequest(method, ...args) { } catch {} // eslint-disable-line no-empty } - const accessToken = yield select(accessTokenSelector); - try { - return yield call(method, ...args, { - Authorization: `Bearer ${accessToken}`, - }); + return yield call(method, ...args); } catch (error) { if (error.code === ErrorCodes.UNAUTHORIZED) { yield put(logout()); // TODO: next url diff --git a/client/src/sagas/index.js b/client/src/sagas/index.js index 84e84ae..099e49e 100755 --- a/client/src/sagas/index.js +++ b/client/src/sagas/index.js @@ -1,11 +1,11 @@ -import { call, select } from 'redux-saga/effects'; +import { call } from 'redux-saga/effects'; import loginSaga from './login'; import coreSaga from './core'; -import { accessTokenSelector } from '../selectors'; +import { getAccessToken } from '../utils/access-token-storage'; export default function* rootSaga() { - const accessToken = yield select(accessTokenSelector); + const accessToken = yield call(getAccessToken); if (!accessToken) { yield call(loginSaga); diff --git a/client/src/utils/access-token-storage.js b/client/src/utils/access-token-storage.js index f42fb4d..e3a8adf 100755 --- a/client/src/utils/access-token-storage.js +++ b/client/src/utils/access-token-storage.js @@ -1,11 +1,26 @@ -const ACCESS_TOKEN_KEY = 'accessToken'; +import Cookies from 'js-cookie'; -export const getAccessToken = () => localStorage.getItem(ACCESS_TOKEN_KEY); +import Config from '../constants/Config'; export const setAccessToken = (accessToken) => { - localStorage.setItem(ACCESS_TOKEN_KEY, accessToken); + Cookies.set(Config.ACCESS_TOKEN_KEY, accessToken, { + expires: Config.ACCESS_TOKEN_EXPIRES, + }); +}; + +export const getAccessToken = () => { + // TODO: remove migration + const accessToken = localStorage.getItem(Config.ACCESS_TOKEN_KEY); + if (accessToken) { + localStorage.removeItem(Config.ACCESS_TOKEN_KEY); + + setAccessToken(accessToken); + return accessToken; + } + + return Cookies.get(Config.ACCESS_TOKEN_KEY); }; export const removeAccessToken = () => { - localStorage.removeItem(ACCESS_TOKEN_KEY); + Cookies.remove(Config.ACCESS_TOKEN_KEY); }; diff --git a/server/api/hooks/current-user/index.js b/server/api/hooks/current-user/index.js index a0913a6..3268ca4 100644 --- a/server/api/hooks/current-user/index.js +++ b/server/api/hooks/current-user/index.js @@ -34,11 +34,16 @@ module.exports = function defineCurrentUserHook(sails) { before: { '/*': { async fn(req, res, next) { - const { authorization: authorizationHeader } = req.headers; - - if (authorizationHeader && TOKEN_PATTERN.test(authorizationHeader)) { - const accessToken = authorizationHeader.replace(TOKEN_PATTERN, ''); + let accessToken; + if (req.headers.authorization) { + if (TOKEN_PATTERN.test(req.headers.authorization)) { + accessToken = req.headers.authorization.replace(TOKEN_PATTERN, ''); + } + } else if (req.cookies.accessToken) { + accessToken = req.cookies.accessToken; + } + if (accessToken) { req.currentUser = await getUser(accessToken); } diff --git a/server/config/security.js b/server/config/security.js index acdb1ef..3bb733f 100644 --- a/server/config/security.js +++ b/server/config/security.js @@ -31,7 +31,7 @@ module.exports.security = { allRoutes: true, allowOrigins: ['http://localhost:3000'], allowRequestHeaders: ['Authorization'], - allowCredentials: false, + allowCredentials: true, }, /** From 36e4bef21bbf20059068031d3069b2c77f962818 Mon Sep 17 00:00:00 2001 From: Maksim Eltyshev Date: Tue, 26 Apr 2022 22:20:20 +0500 Subject: [PATCH 05/10] feat: Remove attachments from public access Closes #219 --- Dockerfile | 2 +- docker-compose.yml | 2 +- server/.gitignore | 8 ++- .../attachments/download-thumbnail.js | 67 ++++++++++++++++++ .../api/controllers/attachments/download.js | 69 +++++++++++++++++++ server/api/models/Attachment.js | 4 +- server/config/custom.js | 2 +- server/config/policies.js | 11 --- server/config/routes.js | 10 +++ .../{public => private}/attachments/.gitkeep | 0 10 files changed, 156 insertions(+), 19 deletions(-) create mode 100644 server/api/controllers/attachments/download-thumbnail.js create mode 100644 server/api/controllers/attachments/download.js rename server/{public => private}/attachments/.gitkeep (100%) diff --git a/Dockerfile b/Dockerfile index 062221c..f6e6520 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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 diff --git a/docker-compose.yml b/docker-compose.yml index 4e72e69..0c813fc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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: diff --git a/server/.gitignore b/server/.gitignore index 39fabc1..fa26571 100644 --- a/server/.gitignore +++ b/server/.gitignore @@ -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 diff --git a/server/api/controllers/attachments/download-thumbnail.js b/server/api/controllers/attachments/download-thumbnail.js new file mode 100644 index 0000000..00c6f6d --- /dev/null +++ b/server/api/controllers/attachments/download-thumbnail.js @@ -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)); + }, +}; diff --git a/server/api/controllers/attachments/download.js b/server/api/controllers/attachments/download.js new file mode 100644 index 0000000..4c7d466 --- /dev/null +++ b/server/api/controllers/attachments/download.js @@ -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)); + }, +}; diff --git a/server/api/models/Attachment.js b/server/api/models/Attachment.js index dd1b649..0a0f318 100644 --- a/server/api/models/Attachment.js +++ b/server/api/models/Attachment.js @@ -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, }; }, diff --git a/server/config/custom.js b/server/config/custom.js index 1cea3b0..8d47ae7 100644 --- a/server/config/custom.js +++ b/server/config/custom.js @@ -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`, }; diff --git a/server/config/policies.js b/server/config/policies.js index 9a329aa..9095e72 100644 --- a/server/config/policies.js +++ b/server/config/policies.js @@ -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, }; diff --git a/server/config/routes.js b/server/config/routes.js index 922251f..1bcd4db 100644 --- a/server/config/routes.js +++ b/server/config/routes.js @@ -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, diff --git a/server/public/attachments/.gitkeep b/server/private/attachments/.gitkeep similarity index 100% rename from server/public/attachments/.gitkeep rename to server/private/attachments/.gitkeep From 2e045217ec82d593b445da65b02b8751c10dbd1f Mon Sep 17 00:00:00 2001 From: Maksim Eltyshev Date: Thu, 28 Apr 2022 00:17:15 +0500 Subject: [PATCH 06/10] fix: Fix application crash after deleting project --- server/api/helpers/projects/delete-one.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/server/api/helpers/projects/delete-one.js b/server/api/helpers/projects/delete-one.js index 39c832d..76a453c 100644 --- a/server/api/helpers/projects/delete-one.js +++ b/server/api/helpers/projects/delete-one.js @@ -10,6 +10,12 @@ module.exports = { }, async fn(inputs) { + const boardIds = await sails.helpers.projects.getBoardIds(inputs.record.id); + + await BoardMembership.destroy({ + boardId: boardIds, + }).fetch(); + const projectManagers = await ProjectManager.destroy({ projectId: inputs.record.id, }).fetch(); @@ -19,14 +25,14 @@ module.exports = { if (project) { const managerUserIds = sails.helpers.utils.mapRecords(projectManagers, 'userId'); - const boardIds = await sails.helpers.projects.getBoardIds(project.id); - const boardRooms = boardIds.map((boardId) => `board:${boardId}`); - const memberUserIds = await sails.helpers.boards.getMemberUserIds(boardIds); const userIds = _.union(managerUserIds, memberUserIds); userIds.forEach((userId) => { - sails.sockets.removeRoomMembersFromRooms(`user:${userId}`, boardRooms); + sails.sockets.removeRoomMembersFromRooms( + `user:${userId}`, + boardIds.map((boardId) => `board:${boardId}`), + ); sails.sockets.broadcast( `user:${userId}`, From 445b0fc682a19c4e0f823faf2fe3d96ed7ffa29a Mon Sep 17 00:00:00 2001 From: Maksim Eltyshev Date: Thu, 28 Apr 2022 14:15:36 +0500 Subject: [PATCH 07/10] fix: Change way to fix application crash when deleting project --- server/api/controllers/projects/index.js | 27 ++++++++++++++++------- server/api/helpers/projects/delete-one.js | 14 ++++-------- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/server/api/controllers/projects/index.js b/server/api/controllers/projects/index.js index 539025f..4e4c47a 100755 --- a/server/api/controllers/projects/index.js +++ b/server/api/controllers/projects/index.js @@ -3,25 +3,36 @@ module.exports = { const { currentUser } = this.req; const managerProjectIds = await sails.helpers.users.getManagerProjectIds(currentUser.id); + const managerProjects = await sails.helpers.projects.getMany(managerProjectIds); - const boardMemberships = await sails.helpers.users.getBoardMemberships(currentUser.id); - const membershipBoardIds = await sails.helpers.utils.mapRecords(boardMemberships, 'boardId'); + let boardMemberships = await sails.helpers.users.getBoardMemberships(currentUser.id); - const membershipBoards = await sails.helpers.boards.getMany({ + let membershipBoardIds = sails.helpers.utils.mapRecords(boardMemberships, 'boardId'); + + let membershipBoards = await sails.helpers.boards.getMany({ id: membershipBoardIds, projectId: { '!=': managerProjectIds, }, }); - const membershipProjectIds = sails.helpers.utils.mapRecords( - membershipBoards, - 'projectId', - true, + let membershipProjectIds = sails.helpers.utils.mapRecords(membershipBoards, 'projectId', true); + const membershipProjects = await sails.helpers.projects.getMany(membershipProjectIds); + + membershipProjectIds = sails.helpers.utils.mapRecords(membershipProjects); + + membershipBoards = membershipBoards.filter((membershipBoard) => + membershipProjectIds.includes(membershipBoard.projectId), + ); + + membershipBoardIds = sails.helpers.utils.mapRecords(membershipBoards); + + boardMemberships = boardMemberships.filter((boardMembership) => + membershipBoardIds.includes(boardMembership.boardId), ); const projectIds = [...managerProjectIds, ...membershipProjectIds]; - const projects = await sails.helpers.projects.getMany(projectIds); + const projects = [...managerProjects, ...membershipProjects]; const projectManagers = await sails.helpers.projects.getProjectManagers(projectIds); diff --git a/server/api/helpers/projects/delete-one.js b/server/api/helpers/projects/delete-one.js index 76a453c..39c832d 100644 --- a/server/api/helpers/projects/delete-one.js +++ b/server/api/helpers/projects/delete-one.js @@ -10,12 +10,6 @@ module.exports = { }, async fn(inputs) { - const boardIds = await sails.helpers.projects.getBoardIds(inputs.record.id); - - await BoardMembership.destroy({ - boardId: boardIds, - }).fetch(); - const projectManagers = await ProjectManager.destroy({ projectId: inputs.record.id, }).fetch(); @@ -25,14 +19,14 @@ module.exports = { if (project) { const managerUserIds = sails.helpers.utils.mapRecords(projectManagers, 'userId'); + const boardIds = await sails.helpers.projects.getBoardIds(project.id); + const boardRooms = boardIds.map((boardId) => `board:${boardId}`); + const memberUserIds = await sails.helpers.boards.getMemberUserIds(boardIds); const userIds = _.union(managerUserIds, memberUserIds); userIds.forEach((userId) => { - sails.sockets.removeRoomMembersFromRooms( - `user:${userId}`, - boardIds.map((boardId) => `board:${boardId}`), - ); + sails.sockets.removeRoomMembersFromRooms(`user:${userId}`, boardRooms); sails.sockets.broadcast( `user:${userId}`, From caf960255710c8423b1ac451e39ba17af9da3cb8 Mon Sep 17 00:00:00 2001 From: Maksim Eltyshev Date: Thu, 28 Apr 2022 14:23:11 +0500 Subject: [PATCH 08/10] chore: Update version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c04238d..5fd9567 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "planka", - "version": "1.0.0", + "version": "1.1.2", "private": true, "homepage": "https://plankanban.github.io/planka", "repository": { From e29c9e6dda7ef900ce400fa7766451618c3faead Mon Sep 17 00:00:00 2001 From: Aliaksandr Shulyak <26464290+ThelonKarrde@users.noreply.github.com> Date: Fri, 29 Apr 2022 07:15:59 -0500 Subject: [PATCH 09/10] ci: GitHub actions config for docker builds (#221) --- .../workflows/build-and-push-docker-image.yml | 42 +++++++++++++++++++ docker-compose.yml | 2 +- 2 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/build-and-push-docker-image.yml diff --git a/.github/workflows/build-and-push-docker-image.yml b/.github/workflows/build-and-push-docker-image.yml new file mode 100644 index 0000000..a210dd4 --- /dev/null +++ b/.github/workflows/build-and-push-docker-image.yml @@ -0,0 +1,42 @@ +name: Build and push Docker image + +on: + release: + types: [created] + +jobs: + build-and-push-docker-image: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v1 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Set version + uses: actions/github-script@v6 + id: set-version + with: + result-encoding: string + script: return context.payload.release.tag_name.replace('v', '') + + - name: Build and push + uses: docker/build-push-action@v2 + with: + context: . + platforms: linux/amd64,linux/arm64,linux/arm/v7 + push: true + tags: | + ghcr.io/plankanban/planka:latest + ghcr.io/plankanban/planka:${{ steps.set-version.outputs.result }} diff --git a/docker-compose.yml b/docker-compose.yml index 0c813fc..f7b90e5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,7 @@ version: '3' services: planka: - image: meltyshev/planka:latest + image: ghcr.io/plankanban/planka:latest command: > bash -c "for i in `seq 1 30`; do From b0b6587eeee67e67d9d84d3ce42f922bf5df89dc Mon Sep 17 00:00:00 2001 From: Maksim Eltyshev Date: Fri, 29 Apr 2022 17:21:39 +0500 Subject: [PATCH 10/10] fix: Fix attachment headers Closes #231 --- .../controllers/attachments/download-thumbnail.js | 4 +++- server/api/controllers/attachments/download.js | 14 ++++---------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/server/api/controllers/attachments/download-thumbnail.js b/server/api/controllers/attachments/download-thumbnail.js index 00c6f6d..0d7444b 100644 --- a/server/api/controllers/attachments/download-thumbnail.js +++ b/server/api/controllers/attachments/download-thumbnail.js @@ -61,7 +61,9 @@ module.exports = { throw Errors.ATTACHMENT_NOT_FOUND; } - this.res.setHeader('Content-Disposition', `inline; ${inputs.filename}`); + this.res.type(attachment.filename); + this.res.set('Cache-Control', 'private, max-age=900'); // TODO: move to config + return exits.success(fs.createReadStream(filePath)); }, }; diff --git a/server/api/controllers/attachments/download.js b/server/api/controllers/attachments/download.js index 4c7d466..871f750 100644 --- a/server/api/controllers/attachments/download.js +++ b/server/api/controllers/attachments/download.js @@ -14,10 +14,6 @@ module.exports = { regex: /^[0-9]+$/, required: true, }, - filename: { - type: 'string', - required: true, - }, }, exits: { @@ -56,14 +52,12 @@ module.exports = { throw Errors.ATTACHMENT_NOT_FOUND; } - let contentDisposition; - if (attachment.isImage || path.extname(attachment.filename) === '.pdf') { - contentDisposition = 'inline'; - } else { - contentDisposition = `attachment; ${inputs.filename}`; + this.res.type(attachment.filename); + if (!attachment.isImage && path.extname(attachment.filename) !== '.pdf') { + this.res.set('Content-Disposition', 'attachment'); } + this.res.set('Cache-Control', 'private, max-age=900'); // TODO: move to config - this.res.setHeader('Content-Disposition', contentDisposition); return exits.success(fs.createReadStream(filePath)); }, };