Fix SECURITY ISSUE 3: Unauthenticated (or any) user can update board sort.

Thanks to Siam Thanat Hack (STH) !
This commit is contained in:
Lauri Ojansivu 2025-11-02 10:13:45 +02:00
parent 0a2e6a0c38
commit ea310d7508
6 changed files with 119 additions and 23 deletions

View file

@ -1711,9 +1711,10 @@ if (Meteor.isServer) {
// All logged in users are allowed to reorder boards by dragging at All Boards page and Public Boards page.
Boards.allow({
update(userId, board, fieldNames) {
return _.contains(fieldNames, 'sort');
return canUpdateBoardSort(userId, board, fieldNames);
},
fetch: [],
// Need members to verify membership in policy
fetch: ['members'],
});
// The number of users that have starred this board is managed by trusted code

View file

@ -809,17 +809,13 @@ Users.helpers({
return ret;
},
boards() {
return Boards.userBoards(this._id, null, {}, { sort: { sort: 1 } });
// Fetch unsorted; sorting is per-user via profile.boardSortIndex
return Boards.userBoards(this._id, null, {}, {});
},
starredBoards() {
const { starredBoards = [] } = this.profile || {};
return Boards.userBoards(
this._id,
false,
{ _id: { $in: starredBoards } },
{ sort: { sort: 1 } },
);
return Boards.userBoards(this._id, false, { _id: { $in: starredBoards } }, {});
},
hasStarred(boardId) {
@ -834,12 +830,7 @@ Users.helpers({
invitedBoards() {
const { invitedBoards = [] } = this.profile || {};
return Boards.userBoards(
this._id,
false,
{ _id: { $in: invitedBoards } },
{ sort: { sort: 1 } },
);
return Boards.userBoards(this._id, false, { _id: { $in: invitedBoards } }, {});
},
isInvitedTo(boardId) {
@ -858,6 +849,32 @@ Users.helpers({
}
return ret;
},
/**
* Get per-user board sort index for a board, or null when not set
*/
getBoardSortIndex(boardId) {
const mapping = (this.profile && this.profile.boardSortIndex) || {};
const v = mapping[boardId];
return typeof v === 'number' ? v : null;
},
/**
* Sort an array of boards by per-user mapping; fallback to title asc
*/
sortBoardsForUser(boardsArr) {
const mapping = (this.profile && this.profile.boardSortIndex) || {};
const arr = (boardsArr || []).slice();
arr.sort((a, b) => {
const ia = typeof mapping[a._id] === 'number' ? mapping[a._id] : Number.POSITIVE_INFINITY;
const ib = typeof mapping[b._id] === 'number' ? mapping[b._id] : Number.POSITIVE_INFINITY;
if (ia !== ib) return ia - ib;
const ta = (a.title || '').toLowerCase();
const tb = (b.title || '').toLowerCase();
if (ta < tb) return -1;
if (ta > tb) return 1;
return 0;
});
return arr;
},
hasSortBy() {
// if use doesn't have dragHandle, then we can let user to choose sort list by different order
return !this.hasShowDesktopDragHandles();
@ -1306,6 +1323,19 @@ Users.mutations({
},
};
},
/**
* Set per-user board sort index for a board
* Stored at profile.boardSortIndex[boardId] = sortIndex (Number)
*/
setBoardSortIndex(boardId, sortIndex) {
const mapping = (this.profile && this.profile.boardSortIndex) || {};
mapping[boardId] = sortIndex;
return {
$set: {
'profile.boardSortIndex': mapping,
},
};
},
toggleAutoWidth(boardId) {
const { autoWidthBoards = {} } = this.profile || {};
autoWidthBoards[boardId] = !autoWidthBoards[boardId];